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

Doctrine Entity Lock Bundle Laravel Package

tourze/doctrine-entity-lock-bundle

A Symfony bundle that provides entity locking for Doctrine ORM, preventing concurrent modifications by locking records during critical operations. Integrates through Doctrine events and bundle configuration for safer transactional workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

tourze/doctrine-entity-lock-bundle is a Symfony bundle that layers entity-level locking helpers on top of Doctrine ORM — optimistic concurrency (version columns) and pessimistic FOR UPDATE-style row locks, plus the plumbing (Lockable contract, lock handler/manager services, Doctrine event subscribers) that the wider Tourze stack assumes. As a Laravel dev you almost certainly are not using this directly — you're either:

  1. Pulling it in transitively via a Tourze package you depend on, or
  2. Using Doctrine ORM inside Laravel (via laravel-doctrine) and want the lock helpers without the bundle.

Minimal steps:

composer require tourze/doctrine-entity-lock-bundle:^1.0
  • Read src/ first, not the README. The README is thin; the useful surface is the *Bundle class (what gets registered), the DependencyInjection/ extension (what services/aliases are defined), and the Doctrine event subscribers (where locking is actually applied). That tells you the real integration points in ~10 minutes.
  • Confirm what your app already uses. If you're on Eloquent, stop here — this is not an Eloquent package and there is no Model::lockForUpdate() equivalent being added. Eloquent's ->lockForUpdate() / ->sharedLock() already covers the pessimistic case.
  • First use case: a stock counter or booking row that must not be double-decremented. Wrap the read-modify-write in a transaction and take a write lock on the row before mutating; catch the optimistic-lock exception and retry.

Laravel wiring (no bundle autodiscovery):

// app/Providers/DoctrineLockServiceProvider.php
public function register(): void
{
    $this->app->singleton(LockManager::class, fn () => new LockManager(/* deps from src/ */));
}

Implementation Patterns

Optimistic locking (preferred default). Add a version field to the entity and let Doctrine bump it on flush; a concurrent writer causes a version mismatch. This is the cheapest pattern and the one you want for user-facing edit forms.

$em->wrapInTransaction(function () use ($em, $id) {
    $entity = $em->find(Invoice::class, $id);
    $entity->setAmount($newAmount);
    $em->flush();           // throws OptimisticLockException on conflict
});

Wrap callers in a bounded retry (3 attempts, jittered backoff) and surface a friendly "someone else changed this" 409 rather than a 500.

Pessimistic locking. Inside a transaction, lock the entity before reading mutable state:

$em->wrapInTransaction(function () use ($em, $sku) {
    $stock = $em->getRepository(Stock::class)->findOneBy(['sku' => $sku]);
    $em->lock($stock, LockMode::PESSIMISTIC_WRITE);
    $stock->decrement($qty);
});

Workflow tips

  • Put locking behind a small application service (StockService::decrement()), not in controllers. Lock scope should match transaction scope exactly.
  • Prefer a retry decorator over sprinkling try/catch around every flush.
  • For queued jobs, the same rule applies: the lock lives and dies inside one handle() transaction. Never carry a locked entity across job boundaries or into an HTTP response.
  • Bind the package's services as singletons in a service provider and inject them; don't new the manager at call sites.
  • Test against MySQL/Postgres, not SQLite — SQLite doesn't honour SELECT ... FOR UPDATE, so pessimistic tests pass vacuously.

Gotchas and Tips

  • This is a Symfony bundle. No Laravel package discovery, no config/bundles.php, no config/packages/*.yaml. You must either (a) register the classes manually in a service provider and skip the bundle class, or (b) use laravel-doctrine and wire the extension by hand. Expect to read the DI extension to know which aliases matter.
  • Locking without a transaction is a silent no-op or an exception depending on driver. Always verify a transaction is active before calling lock — Doctrine will not always warn you.
  • Nested transactions (wrapInTransaction inside DB::transaction) create savepoints, not real commits. Locks acquired inside a savepoint are held until the outermost commit — easy way to hold locks far longer than intended.
  • Lock waits are a production hazard. A forgotten lock on a hot row blocks queue workers and cascades into timeouts. Set innodb_lock_wait_timeout (or lock_timeout on Postgres) deliberately and alert on lock-wait spikes.
  • Lazy loading inside a lock triggers extra SELECTs and can deadlock if the order differs between code paths. Lock the owning aggregate root only; avoid touching associations while holding a write lock.
  • Debugging: enable the Doctrine SQL logger and look for FOR UPDATE / version-incrementing UPDATEs. If you don't see them, your lock isn't being applied — usually a missing flush, wrong entity instance (detached), or the lock call sitting outside the transaction.
  • Extension points: the Doctrine event subscribers and the lock handler are the seams for custom behaviour (e.g. audit-logging lock acquisition, swapping the lock strategy per entity). Subclass rather than patching vendor/.
  • On 1.0.1 specifically: the release carries no changelog beyond a compare link — treat it as an undocumented patch. Pin 1.0.1 exactly, diff 1.0.0...1.0.1 on GitHub before upgrading in anything that touches money or inventory, and re-run your concurrency tests (parallel writers against the same row) rather than relying on semver.
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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle