< BACK TO BLOG

Laravel serialization circular relation

Published October 23, 2021

I faced this issue at work and I spent a lot of time debugging it! so I hope this would help you.

This issue will only happen in queued events and jobs that are fired asynchronously, and it's sort of an edge case, a gotcha if you will.

What will happen is that the job will throw a Illuminate\Queue\MaxAttemptsExceededException.

This has been traced by multiple people and they all ended up solving it by overriding the method getQueuedRelations() method in the model class, here are some of these issues on github:

The proposed solutions all circle around the idea of unloading the relations before serialization, let's take this example job:

use App\Models\Example;
use App\Services\AudioProcessor;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ExampleJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $example;

    public function __construct(Example $example)
    {
        $this->example = $example;
    }
}

once we dispatch this job ExampleJob::dispatch() what happens is that the models in the class are serialized, this is due to the trait SerializesModels, and this is where the issue happens.

Once serialized, here's what a model would look like

{
    "parameter": {
        "class": "App\Models\Example",
        "id": 1,
        "relations": []
    }
}

And there lies the problem, there are a lot of reasons to cause the infinite relations issue, in my case it was because I was using a recently created model, and that model used $touches on another model, and the affected model also had another $touches on a many to many relations, and that caused infinite relations for me.

I would assume there are other causes, but the silver bullet is to either unload the relations

public function __construct(Example $example)
{
    $this->example = $example->withoutRelations();
}

or override getQueueableRelations() on the model, credit:

// app/Models/Example.php
public function getQueueableRelations()
{
    $relations = [];

    foreach ($this->getRelations() as $key => $relation) {
        if (!method_exists($this, $key)) {
            continue;
        }

        $relations[] = $key;
    }

    return array_unique($relations);
}