Remove Relationships from a Model Loaded to a Laravel Job

Let’s learn how to pass an eloquent model in to a job class by omitting its foreign relationships when queue processer re-retrieve that eloquent model for processing.

Problem

  1. There is a Author Model with 3 foreign relationships.
  2. You are retrieving that model with its relationships models for processing in the controller class.
  3. Dispatching a job by passing the Author model as a parameter.
  4. Eloquent models passed to job classes is serialized and deserialized when processing.
  5. As a result job class will re-retrieve Author Model with all the foreign relationships when processing the job.
  6. Problem occur if you do not use relationship models for job processing. Because excess data is retrieved without any use.
  7. Then how to remove relationship models when retrieving Author Model.

Solution

  1. Using WithoutRelations attribute or method in the constructor method of the job class.
  2. Only the main model without relationship models will be re-retrieved when processing the job.

Full working Example

In this example we are going to setup a full Laravel project that required to demonstrate the use of WithoutRelations attribute or method.

First install a fresh Laravel project by referring the official Laravel Documentation. Next Install below debugger package.

composer require --dev symfony/var-dumper

Create models, migrations and factories

Create two models with migrations and factory classes. Please do not care about data types, column names used here.

php artisan make:model Auther -mf
php artisan make:model Book -mf

Modify created migration files

Lets modify created migration files. First open author migration file add following code.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('authors', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('name');
            $table->integer('age');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('authors');
    }
};

Next open book migration file and add following code.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            $table->string('title');
            $table->integer('price');
            $table->unsignedBigInteger('author_id');
            $table->foreign('author_id')->references('id')->on('authors');
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('books');
    }
};

Modify factory classes to seed random data

Once migration files are completed open factories folder. Here we are using seeding feature to quickly add data to database for testing.

Go to factories folder and modify AuthorFactory to add random data to name and age field

namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;

class AuthorFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'age' => fake()->numberBetween(20,80),
        ];
    }
}

Next modify BookFactory to add random books with a price tag.

namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;

class BookFactory extends Factory
{
    public function definition(): array
    {
        return [
            'title' => fake()->name(),
            'price'=> fake()->numberBetween(200, 700)
        ];
    }
}

Create seeders

Open default database Seeder file and add above factories to generate 10 authors with books.

namespace Database\Seeders;

use App\Models\Author;
use App\Models\Book;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $auhtors = Author::factory(10)->create();

        foreach($auhtors as $author){
            Book::factory(10)->create(['author_id' => $author->id]);
        }
    }
}

Running migrations and seeding

Now we have created migrations, models and seeding. Let’s run and add testing data to database.

php artisan migrate

Next seed testing data to database.

php artisan db:seed

Create a sample job class

Let’s create a sample job class to use eloquent model as the part of the job handling process.

php artisan make:job ProcessBooks

Dispatch created job

Lets use web.php file instead of creating a controller class. Run this job class when this application received a get request to root domain.

Here we are sending Author model with books relationship data to job class.

<?php

use App\Jobs\ProcessBooks;
use App\Models\Author;
use App\Models\Book;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    dump('Received a request on the root domain');
    $authors = Author::with('books')->find(1);
    ProcessBooks::dispatch($authors);    
});

Modify Job class to accept eloquent model

Open ProcessBook job class and modify constructor method to accept Author model when using this job.

<?php

namespace App\Jobs;

use App\Models\Author;
use App\Models\Book;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Foundation\Queue\Queueable;

class ProcessBooks implements ShouldQueue
{
    use Queueable;

    public function __construct(public Author $author){}

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        dump('at job handler'); 
        dump($this->author);
        
    }
}

Testing final result

Run below command to serve web application.

php artisan serve

Run below command to listen for queued jobs.

php artisan queue:work

Then send a get request to root domain of the application to trigger created job and see the result within the terminal where you run queue:work command. There you will see all list of books related to author since we passed those data at the beginning of the job dispatch.

How to remove relationships from the eloquent model pass to job class.

Open ProcessBooks job class and add WithoutRelations attribute like below code.

<?php

namespace App\Jobs;

use App\Models\Author;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Attributes\WithoutRelations;

class ProcessBooks implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new job instance.
     */
    public function __construct(
       #[WithoutRelations]
        public Author $author)
    {    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        dump('at job handler'); 
        dump($this->author);
    }
}

Then run the application. You will see relations key of the model return empty value.

Empty Relations attribute on laravel jobs