_ _ _
| | (_) | |
___ _ _ _ __ ___| |__ _ __ ___ _ __ _ _______ __| |
/ __| | | | '_ \ / __| '_ \| '__/ _ \| '_ \| |_ / _ \/ _` |
\__ \ |_| | | | | (__| | | | | | (_) | | | | |/ / __/ (_| |
|___/\__, |_| |_|\___|_| |_|_| \___/|_| |_|_/___\___|\__,_|
__/ |
|___/ for Laravel
This package installs a global synchronized
function into your Laravel application that ensures that the given callable is always executed one at a time, even if multiple requests are coming in.
So if your application receives ten requests in parallel, and part of your code is wrapped in the synchronized
function, that block will be executed sequentially.
This function uses the Cache Atomic Locks feature of Laravel. To utilize this feature, your application must be using the memcached
, redis
, dynamodb
, database
, or file
cache driver as your application's default cache driver. In addition, if relevant, all web servers must be communicating with the same central cache server.
In its most elegant form, you can pass a simple closure to the synchronized
function:
$ticketNumber = synchronized(fn () => Cache::increment('ticket-number'));
Internally, synchronized
generates an Atomic Lock Key (which is simply a hashed string) based on the location of and variables in the callable. This is just like how the ✨magic✨ once
function works.
In some cases, you may want to provide your own Atomic Lock Key. A contrived example is provided below:
$nameOnTicket = Request::get('name-on-ticket');
$ticket = synchronized(function () use ($nameOnTicket) {
// This is bad, because everytime $nameOnTicket has a
// different value, the Atomic Lock Key will be different.
return [
'name' => $nameOnTicket,
'number' => Cache::increment('ticket-number'),
];
});
Everytime $nameOnTicket
has a different value, a different Atomic Lock Key will be autogenerated, and it will probably not work as you intended.
To resolve this, you may tweak your closure to be less dependent on outside variables OR provide your own $key
as the second variable.
$nameOnTicket = Request::get('name-on-ticket');
$ticket = synchronized(function () use ($nameOnTicket) {
// Now it doesn't matter what the value of
// $nameOnTicket is, since the Atomic Lock Key is fixed.
return [
'name' => $nameOnTicket,
'number' => Cache::increment('ticket-number'),
];
}, 'atomic-ticket-number-increment');
Alternatively, you may provide an instance of a saved Eloquent model to use as the Atomic Lock Key. This approach means the callback will be executed one at a time for every unique record in your database.
use App\Models\TicketDispenser;
$dispenser = TicketDispenser::find(Request::get('ticket-dispenser-id'));
$ticket = synchronized(fn () => $dispenser->nextTicket(), $dispenser);