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

Pagelock Module Laravel Package

zikula/pagelock-module

A Zikula module that locks pages while they are being edited, preventing concurrent modifications and edit conflicts. It integrates with Zikula's permission system to manage who can lock and unlock content, helping teams collaborate safely on shared pages.

View on GitHub
Deep Wiki
Context7

Getting Started

Before anything else: this is not a Laravel package. zikula/pagelock-module is a Zikula CMS module (Symfony-based, type: zikula-module in its composer.json), archived since December 2021, with zero dependents. You cannot composer require it into a Laravel app — the namespace is Zikula\PageLockModule\... and it depends on Zikula core services (Zikula\Bundle\CoreBundle, the module AbstractModule base class, Zikula's EntityManager conventions). Treat this repository as a reference implementation of a concept, not a dependency.

Where to look first

  1. Entity/ — the lock record itself (likely a Lock entity with a resource identifier, owning user, timestamp, and possibly a reason/comment field). This tells you the data model you need to replicate.
  2. Api/ or Controller/ — the acquire/release/check operations and how they are exposed. Look for the "is this locked and by whom" query; that's the whole value proposition.
  3. The module's version file and any Resources/config — Zikula modules declare lock lifetime/timeout settings there, which is the config surface you'd mirror.

First use case to steal

"Someone else is already editing this record." That's it. Every admin panel eventually needs it: two editors open posts/42/edit, both hit Save, last write wins silently, first editor loses their work. The PageLock module solves this with a pessimistic lock acquired when the edit form opens, released on save/cancel, and expired after a timeout.

Minimal Laravel port (an afternoon of work)

// migration
Schema::create('locks', function (Blueprint $t) {
    $t->id();
    $t->string('lockable_type');
    $t->unsignedBigInteger('lockable_id');
    $t->foreignId('user_id')->constrained();
    $t->timestamp('expires_at');
    $t->timestamps();
    $t->unique(['lockable_type', 'lockable_id']);
});

A LockService with acquire(Model $m, User $u): bool, release(Model $m, User $u): void, owner(Model $m): ?User. Wire acquire into the edit action (or a route middleware), release into update/destroy and a cancel route. That's the core 90% of what the Zikula module does.

Implementation Patterns

Acquire on form open, not on form submit. The lock must be taken when the editor loads the page, otherwise you're just doing optimistic concurrency with extra steps. In Laravel:

public function edit(Post $post, LockService $locks)
{
    if (! $locks->acquire($post, auth()->user())) {
        return redirect()->route('posts.index')
            ->with('warning', $locks->owner($post)->name . ' is editing this post.');
    }
    return view('posts.edit', compact('post'));
}

Allow re-entry by the same user. Multiple tabs, refreshes, and back-button navigation must not self-block. acquire should return true (and refresh expires_at) if the existing lock already belongs to the current user. This is the single most common bug in homegrown implementations.

Heartbeat / TTL refresh. Zikula-style locks expire. Mirror that: a expires_at of now()->addMinutes(15), refreshed on every request that touches the edit page, or via a lightweight POST /locks/{type}/{id}/heartbeat polled by JS every few minutes. Without a heartbeat, a user who thinks for 16 minutes loses their lock mid-edit and gets a 409 on save.

Check before write, as a backstop. Even with a lock, verify inside the update:

DB::transaction(function () use ($post, $locks) {
    abort_unless($locks->ownedBy($post, auth()->user()), 409);
    $post->update($validated);
    $locks->release($post, auth()->user());
});

Reap stale locks on a schedule. Schedule::command('locks:prune')->everyFiveMinutes(); deleting expires_at < now(). Do not rely on the release path alone — users close tabs.

UI pattern. A GET /locks/{type}/{id} endpoint returning { locked: bool, owner: string, since: iso8601 } is enough to render a "🔒 Alice is editing — read only" banner on index pages, which is often more valuable than blocking the edit form outright.

Integration tip: a Lockable trait on models gives you $post->lock(), $post->isLockedBy($user), and a morphMany relation, keeping controllers thin and letting you add locking to a model with one line.

Borrow the "lockdown" idea. The module's most interesting extension point is an admin-level global lockdown (disable edits site-wide during maintenance/migration). That's a config flag plus a middleware — cheap, and genuinely useful during deploys.

Gotchas and Tips

It's archived and unmaintained. No security patches since 2021, no Laravel support, no Symfony 6/7 compatibility. Never add it to a Laravel composer.json — the autoloader will not resolve Zikula\PageLockModule, and the transitive Zikula dependencies will conflict with everything.

Unique index is not optional. acquire implemented as "select, then insert if absent" has a race window where two users both acquire. Use insertOrIgnore on the unique (lockable_type, lockable_id) pair and check the affected-row count. For genuinely hot resources, escalate to Postgres advisory locks or MySQL GET_LOCK; a table row is fine for the 99% case.

Locks are not integrity guarantees. They reduce collisions; they don't prevent lost updates from API clients, imports, or queued jobs that bypass your controller. Add an optimistic check on updated_at in the update path as the real backstop, and let the lock be a UX nicety.

TTL vs. heartbeat mismatch is the classic failure: TTL shorter than a realistic edit session means locks vanish silently. Log a warning whenever a lock is found expired during ownedBy() checks so you can tune the value.

Cache-backed stores need care. Redis with SET NX EX is a natural fit and much faster than a table, but you lose the ability to list "who has what locked" for an admin view, and cache eviction can drop locks early. If you go this route, keep a DB record for auditability or accept the tradeoff explicitly.

Debugging aids worth building on day one: a locks:list artisan command showing active locks with owner/expiry, structured logs on every acquire/release/reject, and a dev-only POST /locks/{type}/{id}/force-release (admin-gated) so you aren't manually truncating a table when someone's session wedges.

Extension points to design in from the start: put acquire/release/owner behind a LockStore interface so DB, Redis, and a no-op (for tests) are swappable; and make the lock key a (type, id) morph pair rather than hardcoding a single entity type. The Zikula module's structural lesson is exactly this — a generic lock table with a polymorphic reference survives being pointed at pages, documents, and later whatever else you add.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core