Laravel & MongoDB – relationships with ObjectId

I’ve used Laravel and jenssegers/laravel-mongodb package for a long time. It’s wonderful thing, because it allows to use MongoDB easily, integrate most of things with Eloquent and Laravel Models, but also offers hybrid relationships with different database drivers. In theory it supports all relationships in MongoDB database, but in practise, there are a lot of potential issues because of current implementation. 

On MonogDB you can use many fields to create relations and use them in $lookups (aggregations) after that, but the most common practise is to use ObjectId – it’s default field type for keys (_id) and of course you can use different, but probably you will use that one. The problem is jenssegers package uses string for all relationships – and it works (but not always), until you want to use custom aggregations and lookups. These is an example of our parent model with some child relationship:

use Jenssegers\Mongodb\Relations\HasMany;

class ParentModel 
{
    public function children(): HasMany
    {
        return $this->hasMany(ChildModel::class, 'parent_model_id');
    }

}

And then you want to add some children to parent model instance:

$child = new ChildModel();
$parentModel->children()->save($child);

What will be the effect? Child record will have parent_model_id field of course, but it will be string field, not ObjectId. It’s fine until you will need to use $lookup – in that case, simple join will NOT work, because you have ObjectId key (_id) in Parent collection and string relation field on Child collection. We will need to prepare additional field using $addFields in Parrent collection before $lookup stage or in Child collection during $lookup pipeline. It’s not efficient way to solve that problem.

So, how to handle with that? Solution is easy and already available on new package development version: skip automatic casting key to string. Unfortunately, looks like it is not maintained actively, and we will wait a bit longer for updated version. But we can add required change right now. Just overwrite ParentModel getIdAttribute to fix that and always return clean value, without any modification:

class ParentModel
{
    public function getIdAttribute($value = null)
    {
        $value = parent::getIdAttribute($value);
        return new ObjectId($value);
    }
}

After that change code to add child:

$parentModel->children()->save($child);

will not use string in parent_model_id field anymore. It will be ObjectId and everything will work correctly: build-in Laravel relationships and also $lookups, without any additional fields.

Any drawbacks? Yes, after such change, you need to remember to cast key if you want to send it to client as a string or use in comparisons:

$parentModel->getKey() === (string)$parentModel->getKey() 
// before change: true
// after change: false, because left is ObjectId instance

// JSON resource:

return [
    'id' => (string) $parentModel->getKey(),

    // or
    'id' => $parentModel->getKey()->toString(),
]

But I think it’s not a big deal – it’s better to always have one type instead of many different in the same field. 

EDIT 27.08.2023

Post has been updated to modify getIdAttribute function and add parent method call -> it will provide additional operations handled by laravel-mongodb package.

Thanks Roman for feedback!

Why and how use Laravel Resources

Laravel is a pretty nice PHP framework and provides a lot of useful features. One of them is Resource class. Very often we get some data from an ex. database and send it to our app client. Sending the whole model is very bad option and there are many reasons for that:

  • we probably do not want to disclose our models structure
  • some data may be confidential – maybe our client will not use that fields, but every user will be able to look at request, response and get that data
  • many clients (ex. mobile devices) do not need all the data

Without Resource class we have to create our own class and make some transformations, prepare data from a database for clients. If we decide to use a built-in Resource, it will be much, much simpler. We only have to return such class instances and pass our data into the constructor, and then write what fields we really want to use. There is simple example of Laravel Resource which will prepare our data to send as a JSON:

declare(strict_types=1);

namespace App\Modules\MyModule\Resources;

use Illuminate\Http\Resources\Json\Resource;

class MyModelResource extends Resource
{
   /**
    * @var MyModel
    */
   public $resource;

   public function toArray($request)
   {
      return [
       “id” => $this->resource->getKey(),
       “name” => $this->resource->my_model_name,
       “count” => $this->resource->my_model_attribute,
       (...)
      ];
   }

}

That comment with $var MyModel is optional, but helps some IDEs recognize what model will we use inside our Resource. And there is usage in our controller:

public function getModel(Request $request): JsonResponse
{
   // here some code for get model (or inject it with route)
   $model = MyModel::find($request->id);   

   return response()->json(
       new MyModelResource($model)
   );
}

Simple, clean and elegant, because we transform our model in a separate place. If it’s required, we can modify our data in a more complex way, no problem with that. We can also use… Resource inside Resource, so it’s ok to make something like that:

public function toArray($request)
{
    return [
        "id" => $this->resource->getKey(),
        "name" => $this->resource->my_model_name,
        "relation" => new MyModelRelationResource($this->resource->relation),
        (...)
  ];
}

Collection Resources

What if we have a lot of items and want to send them as a JSON? It’s also not a problem, because we can use a built-in ResourceCollection class to achieve that. There is another example:

declare(strict_types=1);

namespace App\Modules\MyModule\Resources;


use Illuminate\Http\Resources\Json\ResourceCollection;

class MyModuleCollectionResource extends ResourceCollection
{
   public function toArray($request)
   {
       return MyModuleResource::collection($this->collection);
   }
}

Of course we can modify or transform the collection in the same way as in a normal Resource, just a response array. Usage in controller:

public function getModels(Request $request): JsonResponse
{
   $models = MyModel::all();   

   return response()->json(
       new MyModuleCollectionResource($models)
   );
}

As you can see, it’s very easy to use, allows us to make everything more organized and solve a lot of issues. 

Laravel – sort collection, JSON returns object

Sorting is very common operation in many languages and a lot of data. Of course the best way is to sort on database, because it’s created for such operations, but sometimes I work with smaller parts of data and do that on API server side. Since when I’ve used Laravel, I often use Collections instead of arrays – they methods are very clean, simple and offer a lot of possibilities. I also often sort collections before outputting them as, for example JSON – natural sorting is more than useful in so many cases. But there is one important thing: if you have associative array (or collection) and use sorting, you client will not receive valid array, but object. This short post will show you, how to deal with it.

Continue reading “Laravel – sort collection, JSON returns object”

Small tip – attaching projects on PHPStorm / WebStorm

This post is very, very small tip about nice feature, that I recently disovered on PHPStorm, but it’s also available on other JetBrains software. All their IDEs supports projects, we can create a lot of them and open many in the same way in separate windows, or by replacing currently opened project. But when we try to do this – using File menu and option to open new project – there is one additional option, to attach project. What is it and what can we achive using this option?

When we attach two separate projects, we have both on project list and tree view, also, repositories are common, if names of branches are the same. If you will then try to change branch, for example from master to dev, IDE will do this for both repositories. If we want to commit changes and then push them, IDE will display all attached projects. It’s very nice options for much faster work: for example I can attach project for frontend and backend in the same time, and work in whole project, without any distractions, without chaning window, screens etc. Do you know any interesting features in these or similar IDEs? Write comment and describe them!