Skip to content

Useful Implementation Patterns

mpyw edited this page Sep 29, 2023 · 5 revisions

Exclusive Mutator Pattern

When you are afraid to organize all into withLocking() callback argument, whereas using lockOrFail() instead makes it difficult to control the lock lifecycle...
In that case, you can keep locking only as long as the class instance is alive, and release it when it is destructed.

<?php

declare(strict_types=1);

namespace App\Services\Batch;

use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Facades\DB;
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\LockFailedException as DatabaseLockFailedException;
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\SessionLock;
use App\Services\Bank\Exceptions\LockFailedException;

class BatchExecutor
{
    private readonly SessionLock $lock;

    public function __construct(
        private readonly ConnetcionInterface $db,
    ) {
        try {
            // Keep locking as long as the instance is alive.
            $this->lock = $this->db->forSession()->lockOrFail(self::class);
        } catch (DatabaseLockFailedException $e) {
            // If necessary, wrap exceptions thrown by the library with your own domain-level exceptions.
            throw new LockFailedException($e->getMessage(), previous: $e);
        }
    }

    public function __destruct()
    {
        // The lock is released automatically on SessionLock::__destruct(), but written here for clarity.
        $this->lock->release();
    }

    public function runFoo(): void
    {
       /* ... */
    }

    public function runBar(): void
    {
       /* ... */
    }

    public function runBaz(): void
    {
       /* ... */
    }
}
function runBatchExclusively(): void
{
    $batch = new BatchExecutor(DB::connection());

    // Start Critical Section
    $batch->runFoo();
    $batch->runBar();
    $batch->runBaz();
    // End Critical Section
}
Clone this wiki locally