Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Advisory Locker Laravel Package

fervo/advisory-locker

A lightweight Laravel package for storing and managing advisory records locally. Advisory Locker offers a simple, cache-friendly way to keep dependency and security advisories organized so your app can query them quickly without repeated external lookups.

View on GitHub
Deep Wiki
Context7

Getting Started

Composer install is the first hurdle, not the setup. The 2021-vintage release almost certainly pins doctrine/dbal ^2.x. Try composer require fervo/advisory-locker on a throwaway branch first; if it forces a DBAL downgrade, stop and read the source instead — you are about to vendor it.

Assuming it resolves:

  1. Stand up a DBAL connection. This package talks to Doctrine, not Eloquent's DB facade. Either configure a doctrine/dbal connection (credentials duplicated from config/database.php) or wrap Laravel's existing PDO:

    $connection = DriverManager::getConnection(['pdo' => DB::connection()->getPdo()]);
    

    The wrapped-PDO path is the only one you should use in a single-app Laravel stack — two connection pools means two lock identities, and locks held on the wrong one are invisible to your workers.

  2. Bind the locker in a service provider.

    $this->app->singleton(AdvisoryLocker::class, fn ($app) =>
        new PostgreSqlAdvisoryLocker($app->make('db.dbal'))
    );
    

    One binding, one instance. Do not construct it per job.

  3. Read the source before using it. It is small — read AdvisoryLocker interface and the per-driver implementations to confirm whether it emits pg_advisory_lock (session-scoped, blocks forever) or pg_advisory_xact_lock (transaction-scoped, released at commit). The two have completely different failure modes.

  4. First use case: a single scheduled job. Wrap one nightly command that is safe to run twice and easy to observe:

    $locker->acquire('nightly:cache-warm');
    try { /* work */ } finally { $locker->release('nightly:cache-warm'); }
    

    Do not start with billing, invoices, or migrations.

Implementation Patterns

Insulate every call site behind a first-party wrapper. This is the single most important pattern — it lets you swap the package for 30 lines of DB::selectOne on a Tuesday afternoon after it inevitably breaks:

final class LockManager {
    public function withLock(string $name, int $timeout, Closure $fn) {
        $key = $this->keyFor($name);
        if (! $this->locker->acquire($key, $timeout)) {
            throw new LockUnavailable($name);
        }
        try { return $fn(); }
        finally { $this->locker->release($key); }
    }
}

Call sites never import the vendor namespace. This is the difference between a two-hour swap and a refactor.

Key naming: hash from business identity, never from a counter. For Postgres you need a 64-bit integer; the package likely hashes a string. Use a stable, namespaced input — "invoice:{$id}", "tenant:{$tenantId}:export", "migrate:{$migrationName}" — and prefix by environment (staging:, prod:) if both share a database, otherwise a staging job can block a production one.

Scheduled commands. The canonical fit. In Kernel::schedule, either override withoutOverlapping() (which already uses Cache::lock) or add the lock inside the command's handle(). Prefer inside handle() — it protects against manual artisan invocations and queue dispatches of the same command, not just the scheduler.

Queued job middleware. A JobMiddleware that acquires before handle() and releases after is the cleanest integration:

public function handle($job, $next) {
    $this->locks->withLock("job:{$job->uniqueKey()}", 30, fn () => $next($job));
}

Attach it with middleware() only on jobs where overlap is genuinely a bug — not on every job.

Deploy/migration safety. Wrap artisan migrate --force in a lock keyed on the database name so multi-instance deploys don't race. Note: use the session-scoped variant and hold it across the whole migration sequence, releasing in finally after the process exits or the command throws.

Test against the real driver. Advisory locks do nothing on SQLite — a test suite using sqlite://:memory: will pass while production breaks. Point integration tests at a Postgres/MySQL service container in CI.

Shared vs exclusive (Postgres only). If you need reader-writer coordination (many cache warmers, one cache clear), the Postgres driver supports shared locks. Check the interface — if it only exposes exclusive, that's a fork-level change.

Gotchas and Tips

Connection pooling is the silent killer. PgBouncer in transaction mode, RDS Proxy, and Octane's reused worker connections all break session-scoped advisory locks: the lock's owner is a session, and a pooler multiplexes sessions freely. Your lock can be released by a random request, or held by a worker that finished hours ago. Test in the topology you ship. If you run PgBouncer, either use pg_advisory_xact_lock (transaction-scoped, safe under pooling) or you cannot use this technique at all — the package cannot fix this for you.

Blocking acquire with no timeout = deadlock. Confirm the acquire signature supports a timeout or a try variant. If it only exposes acquire() with no bound, wrap it carefully or fork it — a caller blocked forever on a lock leaked by a crashed previous worker is an outage, not a stall.

Lock leaks. Session-scoped locks are released when the connection closes. If your app throws inside the critical section and the exception escapes without a finally, the lock persists until the PDO handle is garbage-collected. Always finally, never catch {} swallow. Add a DB::select("SELECT pid, locktype, objid FROM pg_locks WHERE locktype = 'advisory'") diagnostic to your runbook.

MySQL GET_LOCK limits. Names are capped (64 chars historically), scoped per-connection, and silently auto-released on disconnect — semantics diverge sharply from Postgres. If you support both, don't expose one interface pretending they're equivalent; document the difference or use only the Postgres driver.

Hash collisions are real and silent. If the package maps names to bigint keys, two unrelated names can collide and serialize each other. Keep names short, deterministic, and namespaced. The failure mode is "job X occasionally waits for job Y" — confusing and hard to diagnose.

PHP 8.2/8.3 compatibility is unverified. Last release 2021-10-06 predates broad 8.2 adoption. Run the package's own test suite, then run your own before enabling on anything on the critical path. Expect deprecation notices from DBAL 2.x on PHP 8.2+.

Extension points are thin — that's your opening. The interface is small. If you need shared locks, timeout semantics, or a withLock helper, forking into app/Support/AdvisoryLock and adding tests is genuinely cheaper than fighting the vendor. Treat the repo as a reference implementation and pin your fork.

Do it behind a flag on one job first. Roll out to a cache warmer or a report exporter. Watch for LockUnavailable exceptions and lock-hold duration for a week before touching anything customer-visible. The pattern is sound; the package's provenance (1 star, 0 dependents, no releases since 2021) means you are the maintainer the moment you adopt it — so find its sharp edges on a workload that only costs money if it fails, not customers.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource