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 Migration Pruner Bundle Laravel Package

hackzilla/doctrine-migration-pruner-bundle

A Symfony bundle that keeps your Doctrine migrations directory tidy by pruning old, already-applied migration files. Reduce clutter, speed up deployments, and keep version history manageable with simple configuration and safe defaults.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm what you actually got. The bundle is not Symfony Flex–aware (0 dependents, 2 stars), so expect manual registration:

composer require hackzilla/doctrine-migration-pruner-bundle

Then register it by hand in config/bundles.php:

return [
    // ...
    Hackzilla\DoctrineMigrationPrunerBundle\HackzillaDoctrineMigrationPrunerBundle::class => ['all' => true],
];

Look here first, in this order, before trusting the README:

  1. src/Command/ — this package's whole value is one console command. Note its exact name and options.
  2. src/DependencyInjection/Configuration.php — the real config tree (retention count, migrations directory, whether it touches the doctrine_migration_versions table).
  3. src/Service/ or whatever the command delegates to — this is where the deletion logic and any "keep" cutoff lives.

Verify both surfaces from the CLI rather than guessing:

bin/console list | grep -i -E 'prune|migration'
bin/console config:dump-reference hackzilla_doctrine_migration_pruner

First use case — a dry run on a throwaway branch. Do not run this on your main checkout first. The realistic day-one goal is answering "which files would disappear?":

git checkout -b chore/prune-migrations
bin/console <prune-command> --dry-run   # if the command supports it
git status

If there is no --dry-run flag, treat the command as destructive: run it only after git commit-ing everything, then diff git status and git diff --stat to see exactly what it removed. Pruning is a file-deletion operation, so git is your safety net, not the bundle.

Implementation Patterns

Pattern 1 — Squash-then-prune, never prune alone. The bundle deletes history; it does not replace it. Without a baseline, a fresh doctrine:migrations:migrate on a new environment will fail because the removed versions were never replayed. The sane sequence is:

# 1. Capture current schema as a single baseline migration
bin/console doctrine:migrations:dump-schema

# 2. Prune everything older than that baseline
bin/console <prune-command> --keep=1

# 3. Prove a cold install still works
bin/console doctrine:database:drop --force --if-exists
bin/console doctrine:database:create
bin/console doctrine:migrations:migrate --no-interaction

Step 3 is the acceptance test. If it passes, the prune is safe to merge.

Pattern 2 — Prune in a scheduled CI job that opens a PR. Because this is a low-traffic, unmaintained package, keep it out of the deploy path entirely. A nightly or monthly GitHub Action that runs the command and opens a pull request gives you a reviewable diff and automatic rollback (close the PR).

- run: bin/console <prune-command> --keep=10
- uses: peter-evans/create-pull-request@v6
  with:
    branch: chore/prune-migrations
    title: "Prune old Doctrine migrations"

Reviewers then see Deleted: migrations/Version2021...php ×47 in the diff, which is far better than trusting an opaque command in a deploy script.

Pattern 3 — Environment-scoped retention. Configure retention per environment: keep more history locally and in staging (where you may need to replay from an old state), fewer only in the context of a dedicated prune job. Typically:

# config/packages/hackzilla_doctrine_migration_pruner.yaml
hackzilla_doctrine_migration_pruner:
    keep: 20                       # dev/test
    directory: '%kernel.project_dir%/migrations'
# config/packages/prod/hackzilla_doctrine_migration_pruner.yaml
hackzilla_doctrine_migration_pruner:
    keep: 10

Confirm the key names against config:dump-reference — a 2-star bundle's README and its Configuration.php drift apart more often than not.

Pattern 4 — Gate the prune job on "nothing pending." Before pruning, assert that every migration is applied in the target database:

bin/console doctrine:migrations:status --show-versions
# or programmatically: look for "New" / "Not Migrated" entries

If any migration is un-executed in prod, those files are pending history, not excess — pruning them silently loses the change forever.

Pattern 5 — Own the fallback. Given the maintenance risk, read the command once and be prepared to replace it. The functionality is roughly a dozen lines:

$files = glob($dir . '/Version*.php');
sort($files);
foreach (array_slice($files, 0, max(0, count($files) - $keep)) as $file) {
    unlink($file);
}

If the bundle breaks on a future Symfony/Doctrine major, you can drop it without losing the workflow.

Gotchas and Tips

The doctrine_migration_versions table is the real landmine. Deleting a migration file does not delete its row. You end up with a version table full of entries whose files no longer exist. Depending on how the bundle behaves, doctrine:migrations:status will either show orphaned "unknown" versions or migrate will error on them. Check SELECT version FROM doctrine_migration_versions before and after the first run, and decide explicitly whether you also need to DELETE FROM doctrine_migration_versions WHERE version NOT IN (...).

Pending-in-another-environment is the catastrophic case. Multi-branch deploys, hotfix branches cut from old tags, and a prod database that lags behind main all mean "old" files can still be un-applied. Always check migrations:status against every environment's database before pruning, not just dev.

Sort order ≠ execution order. Migration filenames are timestamped, but branch merges routinely produce out-of-order versions. A naive "keep the last N by filename sort" can keep a migration that depends on one it deletes. Sanity-check the surviving set by replaying from scratch (the cold-install test above).

Deletion is only recoverable while the file is in git history. Once committed, recover with:

git log --diff-filter=D --name-only -- migrations/ | head -50
git show <sha>^:migrations/Version2021....php > migrations/Version2021....php

That works until someone runs git filter-repo or the repo is shallow-cloned in CI. Don't treat git as a permanent archive for migrations that still matter.

Debugging a surprising prune: run with -vvv, then diff git status --porcelain migrations/ against your expectation. Pin the exact version in composer.json ("hackzilla/doctrine-migration-pruner-bundle": "0.*") and commit composer.lock. With zero dependents there is no ecosystem pressure to keep this working against new Doctrine ORM majors.

Extension points, if any exist: the bundle almost certainly registers a single public service for the pruner. Override it in config/services.yaml under the same service id to change retention logic (e.g., "never delete the migration that created a table still in use") without forking. If the service is private and final, you're stuck — another reason to keep the ~12-line fallback handy.

Don't run it during deploy. Pruning mid-deploy races with the migrate step and with any rollback path that expects the old files. Keep it a separate, reviewed, low-frequency operation.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package