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

Migrations Bundle Laravel Package

zenstruck/migrations-bundle

A Symfony bundle for managing database migrations. It provides commands and configuration to create, run, and track schema changes, helping keep your application's database structure in sync across environments.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is deprecated and archived, so treat this as an inheritance/maintenance guide, not a greenfield recommendation. If you are evaluating it for a new project, stop here and use doctrine/doctrine-migrations-bundle directly (see Gotchas for the migration path).

If you have inherited a project that depends on it:

  1. Confirm what is actually installed before reading docs:

    composer show zenstruck/migrations-bundle
    composer why zenstruck/migrations-bundle
    

    The Packagist name is zenstruck/migrations-bundle (repo is kbond/ZenstruckMigrationsBundle). Because it is archived and has ~zero dependents, expect the installed version to be old and pinned against an old Symfony/Doctrine pair.

  2. Register the bundle. It is a wrapper around DoctrineMigrationsBundle, so the bundle registration order matters — the Doctrine migrations bundle must be present, and this bundle typically needs to be registered after it in AppKernel/bundles.php so its compiler pass can decorate/replace the migration services.

  3. Look here first, in this order:

    • vendor/zenstruck/migrations-bundle/README.md — the actual usage contract (short, and the only reliable source given the age).
    • DependencyInjection/ and any CompilerPass/ — this is where the "container-aware" magic lives. The pass is what tags migrations and injects the container.
    • Command/ — the bundle likely overrides the Doctrine migrations:generate/migrations:diff commands to emit container-aware classes. Check whether you should be using its command instead of Doctrine's.
    • Any base class under Migration/ (e.g. a ContainerAwareMigration). This is what your migration classes should extend.
  4. First use case: a data migration that needs a service, which is the entire reason this bundle exists. Plain Doctrine migrations are dumb classes instantiated by the migrations library with no access to your DI container. This bundle makes a migration resolvable as a service so you can do things like:

    // conceptual shape — verify the exact base class name in your installed version
    class Version20240101000000 extends ContainerAwareMigration
    {
        public function up(Schema $schema): void
        {
            $slugger = $this->container->get('app.slugger');
            // backfill slugs for existing rows
        }
    }
    

Implementation Patterns

Service-backed migrations. The core pattern is: the migration class is registered in the container (via the bundle's compiler pass) and can pull collaborators. Day-to-day this means you use it for backfills, denormalizations, and one-off data fixes — not for schema changes, which should stay in plain doctrine:migrations migrations.

Prefer constructor/setter injection over $this->container->get(). If the base class exposes the container, resist using it as a service locator. Where the bundle allows it, fetch the specific dependency once and store it, so the migration stays testable and the dependency is explicit:

public function setSlugger(SluggerInterface $slugger): void { $this->slugger = $slugger; }

Workflow for a data migration:

  1. Generate with whatever command the bundle ships (check Command/) so the generated class extends the container-aware base.
  2. Inject the one or two services you need (repository, slugger, HTTP client for a one-off sync).
  3. Batch the work — iterate with ->iterate() / pagination rather than loading the whole table, because these migrations run in a single CLI process with a live container and no request timeout.
  4. Run doctrine:migrations:migrate as usual; the DI wiring is invisible at runtime.

Keep schema and data separate. Use doctrine:migrations:diff for schema, and this bundle only for migrations that genuinely need services. Mixing both in one class makes rollback reasoning hard.

Integration tips:

  • Migrations run in the cli environment; ensure any service you inject is public or explicitly wired for CLI, and that it does not depend on request-scoped state.
  • If you use this for a one-time import, still write down() (or explicitly throw) so migrations:execute --down behaves predictably.
  • Treat the injected services as frozen for the life of the migration. If app.slugger changes behavior six months later, an old migration that re-runs on a fresh database (CI, new dev machines) will produce different data.

Gotchas and Tips

  • It is abandoned. No security patches, no compatibility with modern Symfony/Doctrine. composer require may resolve to ancient constraints; composer why-not will tell you what is blocking an upgrade. Plan removal rather than extension.
  • Container awareness violates migration determinism. The whole point of a migration is that it replays identically forever. Injecting live services means a fresh doctrine:migrations:migrate on a new database depends on current application code. This is the main reason the pattern fell out of favor.
  • Service visibility. If $this->container->get('...') throws "service not found" or a private-service error, it is almost always visibility, not wiring. Debug with debug:container <id> and debug:container --tag=<the bundle's migration tag> to confirm your migration was actually tagged by the compiler pass. If the tag is missing, the bundle is not registered late enough, or the migration class does not extend the expected base.
  • Command shadowing. If the bundle overrides migrations:generate, you may be calling Doctrine's command and getting a non-container-aware stub. Check debug:container --tag=console.command for duplicates and use the fully qualified command name if needed.
  • Compile-time instantiation. Because migrations become services, they are subject to container compilation rules. Constructor arguments must be resolvable at compile time; avoid runtime-conditional dependencies.
  • Extension points are limited to what the archived source offers: subclass the bundle's base migration, or write your own compiler pass that applies the same tag to your classes. Anything more ambitious is a reason to leave the bundle.
  • Migration path off the bundle: move to doctrine/doctrine-migrations-bundle and drop the base class. For migrations that needed services, either (a) inline the logic, (b) use raw SQL/entity manager calls available through the migration's connection, or (c) for Doctrine Migrations 3.x, use the migration factory / a small custom factory to inject what you need. Do this bundle-removal as its own change, verify with a doctrine:migrations:migrate from an empty database in CI, and then delete the dependency.
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