PHP code example of siberfx / laravel-mutex-lock

1. Go to this page and download the library: Download siberfx/laravel-mutex-lock library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

siberfx / laravel-mutex-lock example snippets


Mutex::synchronized("wallet:{$wallet->id}", fn () => $wallet->withdraw($amount));

use Siberfx\MutexLock\Facades\Mutex;

$result = Mutex::synchronized('reports:monthly', function () {
    // Only one process at a time runs this block.
    return Report::generateMonthly();
});

use Siberfx\MutexLock\Facades\Mutex;

$invoice = Mutex::synchronized("invoice:{$order->id}", function () use ($order) {
    return $order->invoice ?? $order->invoice()->create([
        'number' => Invoice::nextNumber(),
        'total'  => $order->total,
    ]);
});

try {
    Mutex::synchronized('import', fn () => $importer->run());
} catch (ImportFailed $e) {
    // The lock has already been released at this point.
    report($e);
}

Mutex::synchronized('report', $callback);                // configured default (mutex-lock.timeout, 10s)
Mutex::synchronized('report', $callback, timeout: 30);   // wait up to 30 seconds
Mutex::synchronized('report', $callback, timeout: 2.5);  // fractional seconds are fine
Mutex::synchronized('report', $callback, timeout: 0);    // try once, don't wait
Mutex::synchronized('report', $callback, timeout: INF);  // wait as long as it takes

use Malkusch\Lock\Exception\LockAcquireTimeoutException;
use Siberfx\MutexLock\Facades\Mutex;

public function store(Request $request)
{
    try {
        $import = Mutex::synchronized(
            "import:{$request->user()->id}",
            fn () => Import::start($request->file('csv')),
            timeout: 0,
        );
    } catch (LockAcquireTimeoutException) {
        return back()->withErrors('An import is already running. Please wait for it to finish.');
    }

    return to_route('imports.show', $import);
}

function whenFree(string $name, callable $callback, mixed $default = null): mixed
{
    try {
        return Mutex::synchronized($name, $callback, timeout: 0);
    } catch (LockAcquireTimeoutException) {
        return $default;
    }
}

$shipped = Mutex::check(
    "order:{$order->id}:ship",
    fn () => ! $order->fresh()->shipped_at,       // cheap check, no lock
)->then(
    fn () => $order->ship(),                      // runs under the lock
    fn () => false,                               // optional: runs if either check fails
);

Mutex::synchronized('sync:crm', $callback, connection: 'pgsql');
Mutex::synchronized('legacy', $callback, timeout: 5, connection: 'legacy_mysql');

use Siberfx\MutexLock\MutexManager;

final class TransferMoney
{
    public function __construct(private readonly MutexManager $mutex) {}

    public function __invoke(Account $from, Account $to, int $cents): void
    {
        // Lock both accounts in a stable order to avoid deadlocks.
        [$first, $second] = collect([$from->id, $to->id])->sort()->values()->all();

        $this->mutex->synchronized("account:{$first}", fn () =>
            $this->mutex->synchronized("account:{$second}", fn () =>
                DB::transaction(function () use ($from, $to, $cents) {
                    $from->refresh()->decrement('balance', $cents);
                    $to->refresh()->increment('balance', $cents);
                })
            )
        );
    }
}

$mutex = Mutex::make('nightly-export', timeout: 5);

$exporter = new Exporter($mutex);   // accepts any php-lock Mutex

$mutex->synchronized(fn () => $exporter->run());
$mutex->check(fn () => $exporter->isPending())->then(fn () => $exporter->run());

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Siberfx\MutexLock\Jobs\Middleware\WithMutex;

class RecalculateInvoice implements ShouldQueue
{
    use Queueable;

    public int $tries = 10;

    public function __construct(public Invoice $invoice) {}

    public function middleware(): array
    {
        return [
            (new WithMutex("invoice:{$this->invoice->id}"))->releaseAfter(15),
        ];
    }

    public function handle(): void
    {
        $this->invoice->recalculate();
    }
}

public function withdraw(Wallet $wallet, int $cents): Transaction
{
    return Mutex::synchronized("wallet:{$wallet->id}", function () use ($wallet, $cents) {
        $wallet->refresh(); // always re-read state *inside* the lock

        throw_if($wallet->balance < $cents, InsufficientFunds::class);

        return DB::transaction(function () use ($wallet, $cents) {
            $wallet->decrement('balance', $cents);

            return $wallet->transactions()->create(['amount' => -$cents]);
        });
    });
}

public function __invoke(Request $request)
{
    $eventId = $request->input('id');

    Mutex::check(
        "webhook:{$eventId}",
        fn () => ! ProcessedWebhook::whereKey($eventId)->exists(),
    )->then(function () use ($eventId, $request) {
        DB::transaction(function () use ($eventId, $request) {
            HandleStripeEvent::dispatchSync($request->all());
            ProcessedWebhook::create(['id' => $eventId]);
        });
    });

    return response()->noContent();
}

public function reserve(Product $product, int $qty): Reservation
{
    return Mutex::synchronized("stock:{$product->sku}", function () use ($product, $qty) {
        $available = $product->stock - $product->reservations()->active()->sum('qty');

        if ($available < $qty) {
            throw new OutOfStock($product, $available);
        }

        return $product->reservations()->create([
            'qty'        => $qty,
            'expires_at' => now()->addMinutes(15),
        ]);
    }, timeout: 3);
}

use Illuminate\Console\Command;
use Malkusch\Lock\Exception\LockAcquireTimeoutException;
use Siberfx\MutexLock\Facades\Mutex;

class SyncProducts extends Command
{
    protected $signature = 'products:sync';

    public function handle(): int
    {
        try {
            return Mutex::synchronized('command:products:sync', function () {
                $this->info('Syncing…');
                app(ProductSync::class)->run();

                return self::SUCCESS;
            }, timeout: 0);
        } catch (LockAcquireTimeoutException) {
            $this->warn('Another sync is already running. Skipping.');

            return self::SUCCESS;
        }
    }
}

// routes/console.php
Schedule::command('products:sync')->everyFiveMinutes()->onOneServer();

function rememberLocked(string $key, int $ttl, Closure $callback): mixed
{
    return Cache::get($key) ?? Mutex::check(
        "cache:{$key}",
        fn () => ! Cache::has($key),
    )->then(
        fn () => tap($callback(), fn ($value) => Cache::put($key, $value, $ttl)),
        fn () => Cache::get($key),
    );
}

$stats = rememberLocked('dashboard:stats', 300, fn () => Stats::expensiveQuery());

Mutex::check('tenant:bootstrap:' . $tenant->id, fn () => ! $tenant->bootstrapped_at)
    ->then(function () use ($tenant) {
        $tenant->createDefaultRoles();
        $tenant->seedSettings();
        $tenant->update(['bootstrapped_at' => now()]);
    });

Mutex::synchronized("tenant:{$tenant->id}:invoice-numbering", fn () => $tenant->nextInvoiceNumber());

return [
    'connection' => env('MUTEX_CONNECTION'),        // null = default DB connection
    'timeout'    => env('MUTEX_TIMEOUT', 10),       // seconds; 0 = try once; null = wait forever
    'prefix'     => env('MUTEX_PREFIX', ''),        // prepended to every lock name

    'sqlite' => [
        'table'             => env('MUTEX_SQLITE_TABLE', 'mutex_locks'),
        'expire'            => env('MUTEX_SQLITE_EXPIRE', 300),            // null = never expire
        'auto_create_table' => env('MUTEX_SQLITE_AUTO_CREATE_TABLE', true),
    ],
];

use Malkusch\Lock\Exception\LockReleaseException;

try {
    Mutex::synchronized('job', fn () => $job->run());
} catch (LockReleaseException $e) {
    $result    = $e->getCodeResult();     // what the callback returned
    $exception = $e->getCodeException();  // what the callback threw, if anything
}

use Siberfx\MutexLock\Facades\Mutex;

use Siberfx\MutexLock\Facades\Mutex;

Mutex::shouldReceive('synchronized')
    ->once()
    ->with('wallet:1', Mockery::type('callable'))
    ->andReturnUsing(fn ($name, $callback) => $callback());

use Malkusch\Lock\Exception\LockAcquireTimeoutException;

Mutex::shouldReceive('synchronized')
    ->andThrow(LockAcquireTimeoutException::create(0));

$this->post('/imports')->assertSessionHasErrors();
bash
php artisan vendor:publish --tag=mutex-lock-config
bash
php artisan vendor:publish --tag=mutex-lock-migrations
php artisan migrate