4

I have a laravel application which uses Batch Jobs to push a bunch of orders to an external system. For one import batch, I create an Import entry in the db and associate all orders to it which should be pushed. This allows me to show the current state of the import in a UI.

The external system is unreliable and therefore pushes sometimes fail. To combat that, I'm using the job retry mechanisms laravel provides. For each import I now want to display all orders that could not be pushed.

Here's the import class:

class Import extends Model
{
    use HasFactory;

    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }

    // We can't use a real relationship because Laravel's Batch is not an eloquent model. This is a good enough workaround.
    public function getBatchAttribute(): ?Batch
    {
        if ($this->batch_id === null) {
            return null;
        }

        return Bus::findBatch($this->batch_id);
    }

    public function getFailedOrdersAttribute()
    {
        if ($this->batch === null) {
            return [];
        }

        return DB::table('failed_jobs')
            ->whereIn('uuid', $this->batch->failedJobIds)
            ->get()
            ->map(function ($failed) {
                $payload = json_decode($failed->payload);
                /** @var PushOrderToParity $job */
                $job = unserialize($payload->data->command);
                $failed->order = $job->getOrder();

                return $failed;
            });
    }
}

The failed_orders and batch attributes allow me to query the failed jobs like this in a blade view:

    @if($import->batch !== null && $import->batch->failedJobs > 0)
        <h2 class="font-display text-xl mt-6 mb-4">Failed ({{ $import->batch->failedJobs }}):</h2>

        <x-table
            :headers="['#', 'Order Number', 'Customer Number', 'Error Message', 'Failed at']">
            @foreach($import->failed_orders as $failed)
                <tr>
                    <td class="px-6 py-4 whitespace-nowrap">{{ $failed->order->id }}</td>
                    <td class="px-6 py-4 whitespace-nowrap">{{ $failed->order->number }}</td>
                    <td class="px-6 py-4 whitespace-nowrap">{{ $failed->order->customer_number }}</td>
                    <td class="px-6 py-4">{{ substr($failed->exception, 0, strpos($failed->exception, 'Stack trace:')) }}</td>
                    <td class="px-6 py-4 whitespace-nowrap">{{ $failed->failed_at }}</td>
                </tr>
            @endforeach
        </x-table>
    @endif

While that works well, I have a few problems with it:

  1. Because Laravel's \Illuminate\Bus\Batch class is not an eloquent model, I can't create an eloquent relation between Import and \Illuminate\Bus\Batch. Instead, I'm manually saving the batch id in the import whenever I'm creating a new Batch. Then, I'm using the getBatchAttribute() modifier to be able to do things like $import->batch.
  2. There is no model for failed jobs either, so I've added another attribute modifier for that. The map method feels clunky, first doing a json_decode and then a call to unserialze to get the actual job payload.

Are there better ways to solve these two, or are they fine as-is and I'm complaining on a high level?

kolaente
  • 1,252
  • 9
  • 22

0 Answers0