saelker/migrations-bundle
A Symfony bundle that integrates Doctrine Migrations, providing console commands and configuration to generate, run, and track database schema changes so environments stay in sync from the CLI.
Reality check first: this is a Symfony bundle, not a Laravel package. There is no service provider, no
config/migrations.php, and no Artisan integration. If you are working in a Laravel codebase, you cannotcomposer requirethis and expect it to do anything — Composer will install it, but nothing will boot it. Treat the rest of this section as "how I'd evaluate it before porting the idea into Laravel."
Minimal evaluation steps:
composer require saelker/migrations-bundle
vendor/saelker/migrations-bundle/src/ — the bundle class and any Command/ directory (this is where the real command names and options live)vendor/saelker/migrations-bundle/src/Resources/config/ — service definitions and the config treecomposer.json — PHP and symfony/* version constraints; this is your first compatibility gateconfig/bundles.php):
Saelker\MigrationsBundle\MigrationsBundle::class => ['all' => true],
php bin/console list | grep -i migrat
php bin/console help <command-name>
UserBundle, BillingBundle, ReportingBundle) where each bundle owns tables in the same database. You want one orchestrated command that runs each bundle's migration set in dependency order instead of wiring doctrine:migrations per bundle by hand.Laravel equivalent to reach for instead: a service provider per package calling $this->loadMigrationsFrom(__DIR__.'/../database/migrations'), plus php artisan migrate --path=... --realpath. That covers 90% of the multi-package migration problem without a third-party bundle.
Per-bundle migration ownership. The core idea worth stealing: each bundle ships its own migrations directory and the orchestrator locates them. In Symfony, that means a config map from bundle name to path, then a single run command. In Laravel, mirror it with per-package service providers and a tagged collection:
// packages/billing/src/BillingServiceProvider.php
public function boot(): void
{
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
}
Deployment pipeline ordering. Migrations must run after cache:clear/cache:warmup in Symfony (the container must know the config), and before traffic shifts. In Laravel the order is config:cache → migrate --force → queue:restart. If this bundle wraps Doctrine migrations, keep its run step in the same deploy stage as doctrine:migrations:migrate.
Non-interactive CI runs. Always pass --no-interaction (and --env=prod) in CI. If a dry-run flag exists, run it as a gate on PRs against a disposable database; otherwise diff the generated SQL via doctrine:migrations:diff in a scratch schema.
Environment-isolated execution. Point test/CI at a separate database name and run the orchestrator in the test bootstrap so local and CI schemas never drift. Avoid running these migrations against a shared dev database.
Lockfile discipline. Pin an exact version ("saelker/migrations-bundle": "1.x-dev#<sha>" or the tagged release) and commit composer.lock. With one release and near-zero adoption, an unpinned constraint is a supply-chain and breakage risk.
Fork-and-vendor pattern. If the bundle's model fits but the code is stale, fork it, bump the symfony/* and PHP constraints, and use a VCS repository entry in composer.json. That is a lower-risk path than depending on the upstream tag.
Bridging to Laravel. If you genuinely need multi-tenant or multi-connection migration orchestration in Laravel, do it with native tooling: define connections in config/database.php, then php artisan migrate --database=billing --path=.... Or, if you want Doctrine's model, use doctrine/migrations standalone (it does not require Symfony) and drive it from an Artisan command.
composer.json. Audit the source before it touches production.Configuration class. That is the source of truth.getContainerExtension()/bundle load order or expects an explicit ordered list in config.php bin/console cache:clear. In prod without a clear, changed migration paths silently do nothing — the classic "migration ran but no tables created" symptom.php bin/console <cmd> -vvv for the resolved config and paths, then doctrine:schema:validate if Doctrine is underneath, then inspect doctrine_migration_versions (or the bundle's own tracking table) to see what was recorded.composer update to refuse. A provide/replace hack is not a fix — fork and update the constraints.--bundle= scoping. These are the seams to use instead of patching vendor code.post-install-cmd. Migrations in Composer scripts fire on every composer install, including in read-only or multi-replica deploy steps, and will race.loadMigrationsFrom() per package, --path/--database flags, or standalone doctrine/migrations. Borrow the concept (per-bundle ownership, ordered execution, one command), not the dependency.How can I help you explore Laravel packages today?