marein/symfony-lock-doctrine-migrations-bundle
A Symfony bundle that integrates the Lock component with Doctrine Migrations, using locks to prevent concurrent migration runs. Keeps deployments safe across multiple servers or containers by ensuring only one process executes migrations at a time.
Marein\SymfonyLockDoctrineMigrationsBundle\MareinSymfonyLockDoctrineMigrationsBundle), not a Laravel package. There is no service provider, no config/*.php publish tag, and no Artisan integration. If your app is Laravel, you cannot composer require this and get value — skip to the pattern in Implementation Patterns below and reimplement the idea with Cache::lock().doctrine/doctrine-migrations-bundle: composer require marein/symfony-lock-doctrine-migrations-bundle plus symfony/lock. Flex auto-registers it; otherwise add the bundle to config/bundles.php.bin/console config:dump-reference marein_lock_doctrine_migrations. The docs are sparse, this command is the source of truth for option names on your installed version. Then look at how the migration executor is decorated — that tells you whether anything in your app already hooks the migrate command.bin/console doctrine:migrations:migrate --no-interaction simultaneously. The bundle makes the second caller wait (or bail) instead of racing on the doctrine_migration_versions table. Prove it in staging with two concurrent replicas before it ever matters in production.Default wiring. Empty config means the executor is decorated with a lock from Symfony's default lock factory. Fine for a single host, effectively useless across containers.
Explicit shared store — the only production-safe setup. flock/semaphore are host-local, so point at Redis, a PDO store, or Postgres advisory locks:
framework:
lock:
migrations: '%env(REDIS_DSN)%'
marein_lock_doctrine_migrations:
lock: 'lock.migrations'
Deploy-pipeline pattern. Keep doctrine_migrations: config for paths/namespace/table untouched — this bundle only wraps execution. Invoke bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration from your release script and let the lock serialize it.
Choose blocking vs fail-fast deliberately. Blocking = second deploy waits for the first to finish (usually right). Fail-fast = second deploy aborts; catch the lock-acquiring exception in your release script and exit non-zero so the orchestrator retries.
Laravel-side equivalent, which is what you'll actually ship. Key the lock on the connection so parallel migrations against different databases don't block each other:
$lock = Cache::lock('doctrine-migrations:'.config('database.default'), 300);
if (! $lock->get()) {
$this->error('Another deployment is migrating.');
return 1;
}
try {
Artisan::call('migrate', ['--force' => true]);
} finally {
$lock->release();
}
Note Laravel's own migrate command already guards against concurrent runs in newer versions — verify before adding the wrapper.
Extension point: because the bundle decorates rather than replaces the executor, any custom MigrationExecutor or Doctrine event subscriber you already have keeps working. Hook lifecycle via onMigrationsMigrated; don't try to wrap the bundle's internals.
Bundle class, DependencyInjection/Configuration.php, and framework.lock are all Symfony-specific. This will never autoload usefully in Laravel.flock and semaphore work only per-machine; in Docker/Kubernetes you get zero protection. If your "safe migrations" setup uses the default store across replicas, you have not solved anything.redis-cli keys '*migration*'), or use the Symfony profiler's Lock panel in dev. A stale key is almost always a SIGKILLed process, not a bundle bug — add a scheduled check that no lock stays held after a failed deploy.config:dump-reference on your installed version instead of trusting snippets; option names are stable but documentation is thin.compose.yaml for local testing. No breaking changes, no deprecations, safe from 1.0.3. But the widened constraints mean Composer may now resolve symfony/lock / doctrine/migrations combinations the maintainer's CI never exercised — treat composer.lock as the contract, and gate any bump of those two packages on a staging two-replica concurrency test.How can I help you explore Laravel packages today?