1

I am using Laravel Job Batching feature and I have dashboard where I display the progress of Batch(Processed, Failed, Pending jobs … etc.).

Each user has it's own dashboard and I want to display the progress of Batch based on logged in user, but I can't see any relationship with User model with batch table job_batches.

Is it possible to somehow make relationship with those tables? or any alternative?

Thanks

Kalim
  • 487
  • 6
  • 18

2 Answers2

3

That is possible, but there is a lot of hoops to go through. This could also be a question about, a general approach to extending functionality of Laravel.

Some quick assumption is that you use some sort of Authentication when creating the batches, so you can do Auth::user()->id.

Create your user_id for the job_batches table with a migration.

    Schema::table('job_batches', function (Blueprint $table) {
        $table->unsignedBigInteger('user_id')->after('name')->nullable();
        $table->foreign('user_id')->references('id')->on('users');
    });

Laravel uses a BatchRepository to create the Batches in the job_batches table, extend this and add our logic to insert Users into the row. I have added the custom repository, to App\Repositories namespace. In general use the current logic and update the user_id after the core Laravel logic has been executed.

<?php

namespace App\Repostories;

use Illuminate\Bus\DatabaseBatchRepository;
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Auth;

class BatchRepository extends DatabaseBatchRepository
{
    public function store(PendingBatch $batch)
    {
        $batch = parent::store($batch); // TODO: Change the autogenerated stub

        $this->connection->table($this->table)
            ->where('id', $batch->id)->update([
                'user_id' => Auth::user()->id,
            ]);

        return $batch;
    }
}

For Laravel to use your new class, you need to extend the current class in the Container. Third parameter is the table name, assuming you are using the default table. This is done in a provider. Either put it in existing provider, or create a new one, remembers to register it.

use Illuminate\Bus\BatchFactory;
use Illuminate\Bus\BatchRepository;
use Illuminate\Database\Connection;
use App\Repostories\BatchRepository as CustomBatchRepository;

...

public function register()
{
    $this->app->extend(BatchRepository::class, function () {
        return new CustomBatchRepository(resolve(BatchFactory::class), resolve(Connection::class), 'job_batches');
    });
}

Tested with the following snippet, this will add user_id to the table rows.

Bus::batch([new TestJob(), new TestJob()])->dispatch();

The relationship

BatchRepositories returns a Batch that is not an Eloquent Model. So i would suggest creating your own Eloquent model for relationship purposes and make logic to convert it into the Batch when you want to have the batch functionality at hand eg. finished().

Firstly Eloquent Model for your Batch.php. Meanwhile also preparing the toBatch() functionality, to convert Eloquent model to Batch class.

namespace App;

use Illuminate\Bus\BatchRepository;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Batch extends Model
{
    use HasFactory;

    protected $table = 'job_batches';

    public function toBatch()
    {
        return resolve(BatchRepository::class)->toBatch($this);
    }
}

Create your relationship method on your User.php.

public function batches()
{
    return $this->hasMany(Batch::class);
}

I tested the relationship setup with the following snippet, which worked.

User::first()->batches->first()->toBatch();

Secondly imagine having multiple batches, you would be able to get the Batch classes with higher order functions easily. Or else use them as a proper relationship.

User::first()->batches->map->toBatch();

Note

Be careful to import correct Batch and BatchRepository classes. I added imports to secure you include the correct ones, also the following snippet to the provider, makes you able to instantiate my custom batch repository.

use App\Repostories\BatchRepository as CustomBatchRepository;

$this->app->bind(CustomBatchRepository::class, function () {
    return new CustomBatchRepository(resolve(BatchFactory::class), resolve(Connection::class), 'job_batches');
});

At your own risk, you can see my solution, in a rough testing ground created for this question. There is a controller and relationship on the user. Not certain if there is leftovers for other StackoverFlow projects.

mrhn
  • 17,961
  • 4
  • 27
  • 46
1

Only I modify this for when is a job executed locally:

$this->connection->table($this->table)
        ->where('id', $batch->id)->update([
            'user_id' => Auth::user()->id ?? null,
        ]);
sefirosweb
  • 11
  • 1