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:
- Because Laravel's
\Illuminate\Bus\Batch
class is not an eloquent model, I can't create an eloquent relation betweenImport
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 thegetBatchAttribute()
modifier to be able to do things like$import->batch
. - There is no model for failed jobs either, so I've added another attribute modifier for that. The
map
method feels clunky, first doing ajson_decode
and then a call tounserialze
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?