У middleware Laravel ThrottlesExceptions нещодавно з'явилася корисна можливість — метод failWhen(). Цей додаток забезпечує розробникам точний контроль над поведінкою при невдачі завдань, що особливо важливо для управління ланцюгами виконання завдань.
Раніше обробка винятків у завданнях обмежувалася вибором між продовженням виконання та повним видаленням завдання. Існуючий метод deleteWhen() видаляє завдання з черги, що зручно для певних випадків, але не враховує складні робочі процеси:
public function middleware(): array
{
return [
(new ThrottlesExceptions(2, 10 * 60))
->deleteWhen(CustomerNotFoundException::class)
];
}
Новий метод failWhen() долає це обмеження, дозволяючи відзначити завдання як невдало виконане, зберігаючи інформацію про помилку та зупиняючи виконання ланцюга завдань:
public function middleware(): array
{
return [
(new ThrottlesExceptions(2, 10 * 60))
->deleteWhen(CustomerNotFoundException::class)
->failWhen(fn (\Throwable $e) => $e instanceof SystemCriticalException)
];
}
Розглянемо систему управління запасами в електронній комерції, яка обробляє оновлення продуктів через ланцюги завдань. При виникненні критичних виключень потрібно зупинити весь ланцюг, а не продовжувати з потенційно неконсистентними даними:
<?php
namespace App\Jobs;
use App\Exceptions\DatabaseConnectionException;
use App\Exceptions\InventoryLockedException;
use App\Exceptions\ProductDiscontinuedException;
use App\Exceptions\TemporaryNetworkException;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\ThrottlesExceptions;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class UpdateProductInventory implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 3;
public function __construct(
public Product $product,
public int $quantity
) {}
public function handle(InventoryService $service): void
{
Log::info('Updating product inventory', [
'product_id' => $this->product->id,
'quantity' => $this->quantity
]);
try {
$result = $service->updateStock($this->product, $this->quantity);
$this->product->update([
'stock_quantity' => $result->newQuantity,
'last_updated' => now()
]);
} catch (TemporaryNetworkException $e) {
Log::warning('Network issue during inventory update', [
'product_id' => $this->product->id,
'error' => $e->getMessage()
]);
throw $e;
} catch (DatabaseConnectionException $e) {
Log::critical('Database connection failed', [
'product_id' => $this->product->id,
'error' => $e->getMessage()
]);
throw $e;
} catch (InventoryLockedException $e) {
Log::error('Product inventory locked', [
'product_id' => $this->product->id,
'locked_until' => $e->getLockedUntil()
]);
throw $e;
}
}
public function middleware(): array
{
return [
(new ThrottlesExceptions(3, 5 * 60))
->deleteWhen(ProductDiscontinuedException::class)
->failWhen(function (\Throwable $e) {
return $e instanceof DatabaseConnectionException ||
$e instanceof InventoryLockedException ||
($e instanceof TemporaryNetworkException && $e->isPermanent());
})
->when(fn (\Throwable $e) => $e instanceof TemporaryNetworkException)
->report(function (\Throwable $e) {
return $e instanceof DatabaseConnectionException ||
$e instanceof InventoryLockedException;
})
];
}
public function failed(\Throwable $exception): void
{
Log::error('Product inventory update failed permanently', [
'product_id' => $this->product->id,
'exception' => $exception->getMessage(),
'chain_halted' => true
]);
$this->product->update([
'update_status' => 'failed',
'last_error' => $exception->getMessage()
]);
NotifyInventoryTeam::dispatch($this->product, $exception);
}
}
class InventoryController extends Controller
{
public function updateProductStock(Product $product, UpdateStockRequest $request)
{
Bus::chain([
new UpdateProductInventory($product, $request->quantity),
new RecalculateProductMetrics($product),
new UpdateSearchIndex($product),
new NotifyStockChange($product),
])->dispatch();
return response()->json([
'message' => 'Inventory update initiated',
'product_id' => $product->id
]);
}
}
class TemporaryNetworkException extends Exception
{
public function isPermanent(): bool
{
return str_contains($this->getMessage(), 'service permanently unavailable') ||
str_contains($this->getMessage(), 'endpoint deprecated');
}
}
class DatabaseConnectionException extends Exception {}
class InventoryLockedException extends Exception
{
public function getLockedUntil(): Carbon
{
return now()->addMinutes(30);
}
}
class ProductDiscontinuedException extends Exception {}
Ця реалізація ілюструє стратегічне використання обох методів. Метод deleteWhen() обробляє продукти, які слід повністю видалити з процесу, тоді як failWhen() управляє критичними системними помилками, що потребують зупинки ланцюга завдань та детального моніторингу помилок.
Головна відмінність у їхній поведінці:
deleteWhen(): Повністю видаляє завдання, дозволяючи ланцюгу продовжити виконання.failWhen(): Позначає завдання як невдало виконане, зупиняє ланцюг, зберігаючи інформацію про помилку для аналізу.Цей підхід забезпечує всебічну обробку помилок з належними треками аудиту. Невдалі завдання залишаються доступними для нових досліджень, а метод failed() дозволяє налаштувати процедури відновлення у разі невдач, при цьому гарантуючи належну зупинку ланцюгів завдань.
Метод failWhen() приймає як класи винятків, так і замикання, що надає змогу реалізувати складну логіку для визначення, які винятки повинні призупинити виконання ланцюга завдань, а які підлягають повторним спробам або видаленню.
Вам цікаво дізнатися, як спростити інтеграцію RabbitMQ у вашому Laravel-додатку? У нашій статті ми розглянемо пакет Simple RabbitMQ, який дозволяє легко налаштувати багатозʼєднання, публікувати повідомлення та обробляти черги за допомогою простого синтаксису. Читайте далі, щоб дізнатися більше!
Laravel пропонує зручні методи для роботи з датами, які значно спрощують запити до бази даних. Досліджуйте, як ці інтуїтивно зрозумілі функції допомагають створювати чіткі та зрозумілі умови для роботи з часовими даними!
Laravel пропонує потужні можливості повнотекстового пошуку за допомогою методів whereFullText та orWhereFullText, що дозволяють здійснювати складні запити до бази даних. Дізнайтеся, як реалізувати ефективний пошук для вашого блогу чи системи управління контентом