Search code examples
mongodblaravelormlaravel-5.3jenssegers-mongodb

Laravel MongoDB library 'jenssegers/laravel-mongodb' hasMany relationship is not working


I am using MongoDB library https://github.com/jenssegers/laravel-mongodb version 3.1.0-alpha in Laravel 5.3.28 I have two collections in MongoDB and I want to make a hasMany relation b/w them. Means each Employee performs many tasks. I have used reference and added employee_ids in the task collection.

Below are my code:

MongoDB:

1st Collection: Employee

{
    "_id" : ObjectId("586ca8c71a72cb07a681566d"),
    "employee_name" : "John",
    "employee_description" : "test description",
    "employee_email" : "[email protected]",
    "updated_at" : "2017-01-04 11:45:20",
    "created_at" : "2017-01-04 11:45:20"
},
{
    "_id" : ObjectId("586ca8d31a72cb07a6815671"),
    "employee_name" : "Carlos",
    "employee_description" : "test description",
    "employee_email" : "[email protected]",
    "updated_at" : "2017-01-04 11:45:20",
    "created_at" : "2017-01-04 11:45:20"
}

2nd Collection: Task

{
    "_id" : ObjectId("586ccbcf1a72cb07a6815b04"),
    "task_name" : "New Task",
    "task_description" : "test description",
    "task_status" : 1,
    "task_start" : "2017-04-01 12:00:00",
    "task_end" : "2017-04-01 02:00:00",
    "task_created_at" : "2017-04-01 02:17:00",
    "task_updated_at" : "2017-04-01 02:17:00",
    "employee_id" : [ 
        ObjectId("586ca8c71a72cb07a681566d"), 
        ObjectId("586ca8d31a72cb07a6815671")
    ]
},
{
    "_id" : ObjectId("586cd3261a72cb07a6815c69"),
    "task_name" : "2nd Task",
    "task_description" : "test description",
    "task_status" : 1,
    "task_start" : "2017-04-01 12:00:00",
    "task_end" : "2017-04-01 02:00:00",
    "task_created_at" : "2017-04-01 02:17:00",
    "task_updated_at" : "2017-04-01 02:17:00",
    "employee_id" : ObjectId("586ca8c71a72cb07a681566d")
}

Laravel: Model: Employee:

<?php
namespace App\Models;

use Jenssegers\Mongodb\Eloquent\Model as Eloquent;

class Employee extends Eloquent {

    protected $collection = 'employee';
    protected $primaryKey = '_id';

    public function tasks()
    {
        return $this->hasMany('App\Models\Task');
    }
}

Laravel: Model: Task:

<?php
namespace App\Models;

use Jenssegers\Mongodb\Eloquent\Model as Eloquent;

class Task extends Eloquent {

    protected $collection = 'task';
    protected $primaryKey = '_id';

    public function employees()
    {
        return $this->belongsTo('App\Models\Employee');
    }
} 

I want to get tasks assigned to the specific employee.

Controller:

public function EmployeeData($data)
{
    $employees = Employee::with('tasks')->where('_id', new \MongoDB\BSON\ObjectID('586ca8d31a72cb07a6815671'))->get();
        echo "<pre>";
        print_r($employees);exit;
}

Output:

Illuminate\Database\Eloquent\Collection Object
(
    [items:protected] => Array
        (
            [0] => App\Models\Employee Object
                (
                    [connection:protected] => mongodb
                    [collection:protected] => lt_employees
                    [primaryKey:protected] => _id
                    [employee_id:App\Models\Employee:private] => 
                    [employee_name:App\Models\Employee:private] => 
                    [employee_description:App\Models\Employee:private] => 
                    [employee_email:App\Models\Employee:private] => 
                    [employee_created_at:App\Models\Employee:private] => 
                    [employee_updated_at:App\Models\Employee:private] => 
                    [parentRelation:protected] => 
                    [table:protected] => 
                    [keyType:protected] => int
                    [perPage:protected] => 15
                    [incrementing] => 1
                    [timestamps] => 1
                    [attributes:protected] => Array
                        (
                            [_id] => MongoDB\BSON\ObjectID Object
                                (
                                    [oid] => 586ca8d31a72cb07a6815671
                                )

                            [employee_name] => Carlos
                            [employee_description] => test description
                            [employee_email] => [email protected]
                            [updated_at] => 2017-01-04 11:45:20
                            [created_at] => 2017-01-04 11:45:20
                        )

                    [original:protected] => Array
                        (
                            [_id] => MongoDB\BSON\ObjectID Object
                                (
                                    [oid] => 586ca8d31a72cb07a6815671
                                )

                            [employee_name] => Carlos
                            [employee_description] => test description
                            [employee_email] => [email protected]
                            [updated_at] => 2017-01-04 11:45:20
                            [created_at] => 2017-01-04 11:45:20
                        )

                    [relations:protected] => Array
                        (
                            [tasks] => Illuminate\Database\Eloquent\Collection Object
                                (
                                    [items:protected] => Array
                                        (
                                        )

                                )

                        )

                    [hidden:protected] => Array
                        (
                        )

                    [visible:protected] => Array
                        (
                        )

                    [appends:protected] => Array
                        (
                        )

                    [fillable:protected] => Array
                        (
                        )

                    [guarded:protected] => Array
                        (
                            [0] => *
                        )

                    [dates:protected] => Array
                        (
                        )

                    [dateFormat:protected] => 
                    [casts:protected] => Array
                        (
                        )

                    [touches:protected] => Array
                        (
                        )

                    [observables:protected] => Array
                        (
                        )

                    [with:protected] => Array
                        (
                        )

                    [exists] => 1
                    [wasRecentlyCreated] => 
                )

        )

)

In the output, relation tasks items are empty.

Can anyone suggest me that the relation b/w collections are correct?

Update

I have used belongsToManyin the relation. Now my models are:

In the Employee Model:

    public function tasks()
    {
        return $this->belongsToMany('App\Models\Task');
    }

In the Task Model:

    public function employees()
    {
        return $this->belongsToMany('App\Models\Employee');
    }

These are the documents:

Employee collection

{
    "_id" : ObjectId("586ca8c71a72cb07a681566d"),
    "employee_name" : "Carlos",
    "employee_description" : "test description",
    "employee_email" : "[email protected]",
    "updated_at" : "2017-01-04 11:45:20",
    "created_at" : "2017-01-04 11:45:20",
    "task_ids" : [ 
        ObjectId("586ccbcf1a72cb07a6815b04"), 
        ObjectId("586cd3261a72cb07a6815c69")
    ]
}, 
{
    "_id" : ObjectId("586ca8d31a72cb07a6815671"),
    "employee_name" : "John",
    "employee_description" : "test description",
    "employee_email" : "[email protected]",
    "updated_at" : "2017-01-04 11:45:20",
    "created_at" : "2017-01-04 11:45:20"
}

Task collection

{
    "_id" : ObjectId("586ccbcf1a72cb07a6815b04"),
    "task_name" : "New Task",
    "task_description" : "test description",
    "task_status" : 1,
    "task_start" : "2017-04-01 12:00:00",
    "task_end" : "2017-04-01 02:00:00",
    "task_created_at" : "2017-04-01 02:17:00",
    "task_updated_at" : "2017-04-01 02:17:00",
    "employee_ids" : [ 
        ObjectId("586ca8c71a72cb07a681566d"), 
        ObjectId("586ca8d31a72cb07a6815671")
    ]
},
{
    "_id" : ObjectId("586cd3261a72cb07a6815c69"),
    "task_name" : "2nd Task",
    "task_description" : "test description",
    "task_status" : 1,
    "task_start" : "2017-04-01 12:00:00",
    "task_end" : "2017-04-01 02:00:00",
    "task_created_at" : "2017-04-01 02:17:00",
    "task_updated_at" : "2017-04-01 02:17:00",
    "employee_ids" : ObjectId("586ca8c71a72cb07a681566d")
}

I get the first employee with these documents:

$employee = Employee::with('tasks')->first();
dd($employee);

And I gotthe output with empty relation:

Employee {#176
  #connection: "mongodb"
  #collection: "employee"
  #primaryKey: "_id"
  -employee_id: null
  -employee_name: null
  -employee_description: null
  -employee_email: null
  -employee_created_at: null
  -employee_updated_at: null
  #parentRelation: null
  #table: null
  #keyType: "int"
  #perPage: 15
  +incrementing: true
  +timestamps: true
  #attributes: array:10 [
    "_id" => ObjectID {#170}
    "employee_name" => "Carlos"
    "employee_description" => "test description"
    "employee_email" => "[email protected]"
    "updated_at" => "2017-01-04 11:45:20"
    "created_at" => "2017-01-04 11:45:20"
    "task_ids" => array:2 [
      0 => ObjectID {#174}
      1 => ObjectID {#175}
    ]
  ]
  #original: array:10 [
    "_id" => ObjectID {#170}
    "employee_name" => "Carlos"
    "employee_description" => "test description"
    "employee_email" => "[email protected]"
    "updated_at" => "2017-01-04 11:45:20"
    "created_at" => "2017-01-04 11:45:20"
    "task_ids" => array:2 [
      0 => ObjectID {#174}
      1 => ObjectID {#175}
    ]
  ]
  #relations: array:1 [
    "tasks" => Collection {#173
      #items: []
    }
  ]
  #hidden: []
  #visible: []
  #appends: []
  #fillable: []
  #guarded: array:1 [
    0 => "*"
  ]
  #dates: []
  #dateFormat: null
  #casts: []
  #touches: []
  #observables: []
  #with: []
  +exists: true
  +wasRecentlyCreated: false
}

Solution

  • I understood by your other question, that a task can belong to many employees, right? So you should be using belongsToMany relationship in your Task model. Also your example "task" collection shows that in one document employee_id is an array and in the other document it is an ObjectId, when both should be arrays.

    Anyway, I've had a hard time trying to figure this out, but I've seen that you can't use hasMany as the inverse of belongsToMany, because belongsToMany creates an array of ids, and hasMany doesn't work well with arrays. I would say that we would need something like hasManyInArray, but when I associate a belongsToMany relationship, the "parent" document gets created an array of ids, which leads me to think that the parent should also use belongsToMany even though it doesn't "belong to" but actually "has". So when you would associate an employee to a task like this:

    $task->employees()->save($employee);
    

    The "employee" document will end up having a "task_ids" attribute with the only task id it should have. So that seems to be the way to go with Jenssegers: to use belongsToMany in both models:

    Laravel: Model: Employee:

    <?php
    namespace App\Models;
    
    use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
    
    class Employee extends Eloquent
    {
        protected $collection = 'employee';
    
        public function tasks()
        {
            return $this->belongsToMany(Task::class);
        }
    }
    

    Laravel: Model: Task:

    <?php
    namespace App\Models;
    
    use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
    
    class Task extends Eloquent
    {
        protected $collection = 'task';
    
        public function employees()
        {
            return $this->belongsToMany(Employee::class);
        }
    }
    

    And you would use this like:

    // Give a task a new employee
    $task->employees()->save($employee);
    
    // Or give an employee a new task
    $employee->tasks()->save($task);
    

    The only thing about this is that when you look at the database, you will see that your employee documents have an array called "task_ids", and inside it, the id of the only task each employee have. I hope this helped.


    Just some side notes, you know that you don't have to define the name of the primary key on each model, right? You don't need this:

    protected $primaryKey = '_id';
    

    Also you don't have to define the name of the collection (i.e. protected $collection = 'employee';), unless you really want them to be in singular (by default they are in plural).


    I got up in the middle of the night (it's 3:52AM here) and checked something on the computer and then checked SO an saw your question, I hope this time I answered soon enough for you, we seem to be in different timezones.

    Update

    These are the documents I created for testing:

    employee collection

    {
        "_id" : ObjectId("5870ba1973b55b03d913ba54"),
        "name" : "Jon",
        "updated_at" : ISODate("2017-01-07T09:51:21.316Z"),
        "created_at" : ISODate("2017-01-07T09:51:21.316Z"),
        "task_ids" : [ 
            "5870ba1973b55b03d913ba56"
        ]
    },
    {
        "_id" : ObjectId("5870ba1973b55b03d913ba55"),
        "name" : "Doe",
        "updated_at" : ISODate("2017-01-07T09:51:21.317Z"),
        "created_at" : ISODate("2017-01-07T09:51:21.317Z"),
        "task_ids" : [ 
            "5870ba1973b55b03d913ba56"
        ]
    }
    

    task collection

    {
        "_id" : ObjectId("5870ba1973b55b03d913ba56"),
        "name" : "New Task",
        "updated_at" : ISODate("2017-01-07T09:51:21.317Z"),
        "created_at" : ISODate("2017-01-07T09:51:21.317Z"),
        "employee_ids" : [ 
            "5870ba1973b55b03d913ba54", 
            "5870ba1973b55b03d913ba55"
        ]
    }
    

    With these documents I get the first employee like this:

    $employee = Employee::with('tasks')->first();
    dd($employee);
    

    And in the output we can see the relations attribute is an array:

    Employee {#186 ▼
      #collection: "employee"
      #primaryKey: "_id"
      // Etc.....
      #relations: array:1 [▼
        "tasks" => Collection {#199 ▼
          #items: array:1 [▼
            0 => Task {#198 ▼
              #collection: "task"
              #primaryKey: "_id"
              // Etc....
              #attributes: array:5 [▼
                "_id" => ObjectID {#193}
                "name" => "New Task"
                "updated_at" => UTCDateTime {#195}
                "created_at" => UTCDateTime {#197}
                "employee_ids" => array:2 [▶]
              ]
            }
          ]
        }
      ]
    }
    

    Update 2

    The belongsToMany method isn't in the file you mention because that class (i.e. Jenssegers\Mongodb\Eloquent\Model) extends Laravel's Eloquent Model class, and that's where the belongsToMany method is.

    Ok so that must be why it's not working for you, because the arrays have to be strings instead of ObjectIds. Why is this? Because that's how the Jenssegers library work, it saves the Ids as strings. I've also found this behaviour strange, but that's how it works. Remember that you are supposed to relate objects using the Jenssegers library, not by creating the data manually in the database. How can you index the ids? Just create a normal index in MongoDB, like tasks.createIndex({task_ids: 1}). Here's the documentation on how to create indexes: https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/. You can also create indexes on migrations, here are the docs on migrations, make sure to read Jenssegers notes on migrations too.

    You can access the tasks realtion like this: $employee->tasks;. You access relations by getting a property with the same name of the method you declared your relation with, so if you have:

    class Post
    {
        public function owner()
        {
            return $this->belongsTo(User::class);
        }
    }
    

    You get the relation as $post->owner;. Here's the documentation on relations: https://laravel.com/docs/5.3/eloquent-relationships