Laravel Lock: Distributed Locks для Models та Routes

Перекладено ШІ 0 Laravel News 18 серпня, 2026

Пакет Laravel Lock спрощує роботу з атомарними блокуваннями, пропонуючи зручний fluent builder та автоматичне звільнення ресурсів. Ви можете легко захистити свої моделі та маршрути від станів гонитви, вибираючи між cache або database драйверами для зберігання локів.

Коли два воркери черги одночасно обробляють одне відправлення, вони обидва позначають його як «відправлене», і клієнт отримує посилку двічі. Стандартний Cache::lock() вирішує цю проблему, але з ним доводиться власноруч прописувати формат ключів, токени власників та звільнення блокування у блоці finally. Пакет Laravel Lock від Md Mahedi Zaman Zaber спрощує цей процес за допомогою builder, який приймає назву дії та об'єкт, зберігаючи блокування в кеші або базі даних.

# Основні можливості

  • Fluent builder: Lock::for('shipment_dispatch', $shipment)->ttl(120)->acquire() повертає boolean, а метод block() обгортає callback-функцію, автоматично звільняючи блокування.
  • Блокування на рівні моделей: Трейт HasLocks додає метод $shipment->lock('dispatch'). Ключ автоматично містить morph class моделі та її primary key.
  • Route middleware: Псевдонім lock активує блокування перед виконанням контролера та звільняє його після завершення, навіть якщо виникла помилка.
  • Два драйвери зберігання: cache (для будь-якого сховища кешу Laravel) та database (для таблиці locks, дані в якій зберігаються навіть після очищення кешу).
  • Очікування: Методи acquire() та block() дозволяють вказати час у секундах для повторних спроб перед скасуванням.
  • Інспектування: Об'єкт LockInfo (тільки для читання) надає доступ до ключа, токена власника, часу експірації та допоміжних методів на кшталт remainingSeconds() чи isOwnedBy().

# Отримання та звільнення блокувань

Фасад Lock створює очікуване блокування на основі назви дії та (опціонально) об'єкта. Зберігайте цей об'єкт у змінній, оскільки звільнення має виконуватися тим самим екземпляром:

use ZaberDev\Lock\Facades\Lock;
 
$lock = Lock::for('shipment_dispatch', $shipment)->ttl(120);
 
if ($lock->acquire()) {
    try {
        $carrier->dispatch($shipment);
    } finally {
        $lock->release();
    }
}

Кожен екземпляр builder генерує унікальний UUID токен. Драйвери перевіряють цей токен перед видаленням запису. Якщо ви створите новий екземпляр Lock::for(...) і спробуєте викликати release() на ньому, він матиме інший токен і нічого не видалить. Для синхронізації між різними процесами токен можна встановити вручну через owner('worker-7').

Стандартний TTL — 60 секунд. Окрім ttl(), доступні методи forSeconds() та forMinutes(), а метод refresh() дозволяє продовжити активне блокування без його попереднього звільнення.

Метод block() виконує всю роботу за один виклик і повертає результат виконання callback-функції:

$manifest = Lock::for('shipment_dispatch', $shipment)->block(function () use ($shipment, $carrier) {
    return $carrier->dispatch($shipment);
});

Якщо блокування вже встановлене, block() викидає LockAcquisitionException. В чергах це призводить до помилки виконання job замість мовчазного пропуску роботи.

Обидва методи підтримують очікування. acquire() приймає кількість секунд для спроб, а block() — третій аргумент із затримкою 250 мс між спробами:

$lock->acquire(blockSeconds: 5);
 
Lock::for('stock_allocation', $warehouse)->block($callback, 60, 5);

Для перевірки стану без захоплення існують методи isLocked(), isOwnedByCurrent(), remaining() та info(). Метод forceRelease() видаляє блокування незалежно від власника — корисно для очищення після аварійної зупинки воркера.

# Блокування моделей

Додайте трейт HasLocks до моделі, щоб автоматично генерувати ціль блокування:

use Illuminate\Database\Eloquent\Model;
use ZaberDev\Lock\HasLocks;
 
class Shipment extends Model
{
    use HasLocks;
}
$lock = $shipment->lock('dispatch')->ttl(120);
 
$shipment->isLocked('dispatch');
 
$shipment->forceReleaseLock('dispatch');

Ключ формується з назви дії, morph class (із заміною зворотних слешів на підкреслення) та primary key: dispatch:App_Models_Shipment:42. Пакет також підтримує скалярні значення та інтерфейс Lockable для Value Objects.

При використанні бази даних HasLocks додає зв'язок locks() для перегляду активних блокувань моделі:

$shipment->locks()->where('expires_at', '>', now())->get();

# Захист маршрутів

Middleware lock дозволяє захищати endpoint. Ви можете вказати дію, TTL та драйвер:

Route::post('/warehouse/reconcile', [ReconcileController::class, 'store'])
    ->middleware('lock:warehouse_reconcile,300');

Щоб обмежити блокування конкретним записом, використовуйте параметри маршруту. Middleware автоматично замінить {shipment} на значення параметра або primary key моделі:

Route::post('/shipments/{shipment}/dispatch', [ShipmentController::class, 'dispatch'])
    ->middleware('lock:shipment_dispatch:{shipment},60');

Якщо блокування вже існує, middleware викидає LockAcquisitionException з кодом 423 (Locked). Оскільки це не HTTP-виняток, за замовчуванням клієнт отримає 500 помилку. Рекомендується обробити цей виняток вручну:

use ZaberDev\Lock\Exceptions\LockAcquisitionException;
 
$exceptions->render(function (LockAcquisitionException $e) {
    return response()->json([
        'message' => 'Already processing. Try again in a moment.',
        'retry_after' => $e->lockInfo?->remainingSeconds(),
    ], 429);
});

# Сховища: кеш або база даних

Драйвер за замовчуванням налаштовується в config/locks.php через змінну LOCK_DRIVER. Метод using() дозволяє змінити драйвер для конкретного випадку:

Lock::for('inventory_sync', $warehouse)->using('cache')->ttl(15)->acquire();

Драйвер cache швидкий і використовує атомарність Cache::add(). Драйвер database надійніший, оскільки використовує транзакції та lockForUpdate(), а дані зберігаються навіть після перезавантаження Redis.

Застарілі записи в базі видаляються під час читання або через механізм Prunable:

use ZaberDev\Lock\Models\LockModel;
Schedule::command('model:prune', ['--model' => LockModel::class])->daily();

Пакет підтримує події: LockAcquired, LockFailed та LockReleased. Моніторинг LockFailed допомагає виявляти та виправляти race conditions у вашому застосунку.

# Встановлення

Laravel Lock потребує PHP 8.2+ та підтримує Laravel 11, 12 та 13:

composer require zaber-dev/laravel-lock

Публікація конфігурації та міграцій:

php artisan vendor:publish --tag=locks-config
php artisan vendor:publish --tag=locks-migrations
php artisan migrate

Сирці та документація доступні в репозиторії Laravel Lock на GitHub.

Популярні

Інше, що варто прочитати

12 Оновлено 26 червня, 2026

Laravel: шлях до створення справді дієздатних AI-агентів

Чи готові ви підвищити ефективність своїх проектів на Laravel і спростити інтеграцію штучного інтелекту? У нашій статті ви дізнаєтеся, як Vizra ADK може революціонізувати ваш підхід до розробки, розширюючи можливості, забезпечуючи тестування та надійність для ваших AI-агентів

19 Оновлено 26 червня, 2026

Використання штучного інтелекту для управління перекладами в Laravel

Досліджуйте нові можливості локалізації вашого Laravel-додатку з пакунками, які використовують штучний інтелект, такими як ChatGPT та Claude. Які рішення можуть спростити ваш процес перекладу та зробити його більш точним? Читайте далі, щоб дізнатися більше!

16 Оновлено 26 червня, 2026

Простий пакет RabbitMQ для Laravel

Вам цікаво дізнатися, як спростити інтеграцію RabbitMQ у вашому Laravel-додатку? У нашій статті ми розглянемо пакет Simple RabbitMQ, який дозволяє легко налаштувати багатозʼєднання, публікувати повідомлення та обробляти черги за допомогою простого синтаксису. Читайте далі, щоб дізнатися більше!