jupeter/doctrine-dump-fixtures-bundle
Symfony bundle for exporting Doctrine database contents as reusable fixture classes. Generate fixtures from existing data via a console command, simplifying test data setup and development seeding.
Read this first: it is a Symfony 2 bundle, not a Laravel package. There is no Illuminate\Support\ServiceProvider, no extra.laravel auto-discovery block, no config/*.php merge path, and no Artisan command base class. composer require into a Laravel app is not a supported path — you either have a legacy Symfony 2.x host app, or you treat this repository as a reference implementation of a command you write yourself. Day-to-day leverage for a Laravel developer is almost entirely in the second mode.
What the package actually does, stripped of Symfony plumbing: reads Doctrine metadata for the entity/table list, selects rows from the connection, and serializes them into fixture files that doctrine/data-fixtures can later load. That is table introspection plus serialization — a few hundred lines of real logic.
Where to look first (in the repo):
Command/ — the console command class. This is your spec: it defines the input signature (entity/table selection, output directory, format), the flow, and the exit codes.DependencyInjection/Configuration.php — bundle config keys: output path, connection name, possibly excluded tables.Resources/config/services.xml — shows which parts are services and therefore decoratable/replaceable.Minimal setup, if and only if you have a Symfony 2/3 host app:
composer require --dev jupeter/doctrine-dump-fixtures-bundle (expect to pin period-correct Doctrine DBAL/ORM; on a modern PHP this may not resolve at all).app/AppKernel.php, gated to non-prod:
if (in_array($this->getEnvironment(), ['dev', 'test'])) {
$bundles[] = new Jupeter\DoctrineDumpFixturesBundle\DoctrineDumpFixturesBundle();
}
doctrine/doctrine-fixtures-bundle is installed — this bundle only produces fixtures, it does not load them.app/console list and inspect the doctrine namespace for the dump command and its options.First use case — a reference table, not users. Pick something small, stable, and dependency-free: countries, languages, currencies, permissions. Dump that one table, load the resulting fixture with doctrine:fixtures:load in a scratch database, and confirm the round-trip. This isolates serialization problems from foreign-key ordering problems, which are the two distinct failure modes you will hit. If you are doing the Laravel port, the emitted fixture file from this exercise is your required output format — reproduce it byte-for-byte before writing anything else.
One fixture per table, never one giant dump. A monolithic dump of the whole schema is un-reviewable in a diff, un-regenerable partially, and fails atomically on a single bad column. Per-table files let you regenerate only what drifted and lets you exclude users/audit_log from regeneration entirely.
Order tables by foreign key, not by name. The single most important behaviour to replicate: build a dependency graph from the metadata and topologically sort before emitting. A dump that is correct but loaded in the wrong order fails on insert. The pragmatic escape hatch in Laravel is Schema::disableForeignKeyConstraints() around the load — use it, but keep the ordering correct anyway so the fixtures remain usable by other tooling.
The Laravel port, concretely. One Artisan command, roughly:
php artisan db:dump-fixtures
--connection=mysql
--tables=countries,currencies,regions
--exclude=users,sessions,password_resets
--format=php # or yaml/json
--output=database/seeders/fixtures
--chunk=1000
Implementation shape: Schema::getTableListing() / DB::select('SHOW TABLES') for the table list, Schema::getColumns($table) for types, DB::table($table)->orderBy($pk)->cursor() for chunked reads, then a serializer that emits return [ ... ]; PHP arrays (diff-friendly, preserves types, no YAML quoting surprises) or DB::table()->insert() calls in a seeder. Keep the serializer behind a small interface so --format is a strategy swap rather than an if/else ladder.
Chunk the reads. ->cursor() or chunkById() against a real table will otherwise hit memory_limit on anything with more than a few hundred thousand rows. Chunking also gives you a natural place to apply the anonymization transform per row rather than loading everything and mapping in memory.
Layer anonymization as a transform, not a post-process. Config a map of column => faker method | closure | null, applied inside the row loop before serialization. Anything not explicitly allowed should be a hard error, not a silent passthrough — an allowlist beats a denylist when the cost of a miss is real PII in Git history.
Wire into the existing seeder flow. Generated fixtures should be consumable by DatabaseSeeder or iseed-produced seeder classes, not a parallel universe. If you already use orangehill/iseed, check whether it covers the use case before building — it reverse-seeds an existing DB into seeders and is Eloquent-native and maintained. spatie/laravel-db-snapshots covers the "refresh my local env from a snapshot" case more directly. Reach for a custom command when you specifically need Doctrine-style fixtures, FK-ordered cross-table dumps, or column-level scrubbing that neither provides.
Regenerate, don't hand-edit. Fixtures are generated artifacts. Add a scheduled or on-demand regeneration and a cheap CI check that the committed fixtures still match the current schema column set — otherwise they go stale silently after migrations and the failure surfaces as NOT NULL constraint violation in an unrelated test.
Gate on environment. Refuse to run when app()->environment('production') or when the connection is a production DSN. This is a one-line guard that prevents the worst outcome in the whole tool.
LONGTEXT, JSON columns, and tables with hundreds of columns produce unusable fixture files and spike memory. Exclude them by default and require an explicit opt-in per column.NULL vs empty string, booleans stored as tinyint, datetime formats and timezones, JSON columns that need json_encode at emit and json_decode at load, and enums. Doctrine's DBAL type system handles some of this in the original; your port has to do it explicitly.users.created_by_id -> users.id). Detect the cycle, defer those rows to a second pass after the first insert, or fall back to disabling FK checks.DB::table(), the emitted file, and the row after fixture load. The divergence is almost always a type cast or a NULL/empty-string conversion, and this comparison finds it in minutes.services.xml — you can decorate them to change output format or add scrubbing without touching the command. Preserve that seam in your port: command does argument parsing and orchestration, serializer is injectable, transform is a separate class.How can I help you explore Laravel packages today?