# bin/console foundry:load-fixtures report --append
# bin/console doctrine:fixtures:load --group=rpsi
Laboratory (Root, the bottom)
└── Mission ──> [Linked to a Laboratory]
└── Dossier ──> [Linked to a Mission]
└── Seal ──> [Linked to a Dossier - Seals (many)]
└── SealObject ──> [Linked to a Seal] (the top)
What will be happen on Foundry when we call SealObjectFactory::new() to create a SealObject ?
The PHP Discovery (Bottom-Up)
SealObject child requires a parent Seal.
Seal requires a parent Dossier... up to the root Laboratory.
Foundry climbs the tree to build the memory blueprint.
The SQL Insertion (Top-Down)
Database constraints (foreign_key) rule the world.
Doctrine reverses the order: it inserts the Laboratory first, captures its ID, and cascades down back to your SealObject.
No more "mysterious database" artifacts.
A disposable and repeatable environment, managed directly by our Factories.
Foundry's API prioritizes creating child relations before their parents.
No more "mysterious database" artifacts.
A disposable and repeatable environment, managed directly by our Factories.
Foundry's API prioritizes creating child relations before their parents.
Batch-based incremental data generation.
Overriding private attributes and sharing persisted objects across factories to simulate scenarios.
Using afterInstantiate to handle complex post-construction states.
afterInstantiate for inverse relationships when the owning side lacks a setter.Setting a predictable state on entities designed without public setters (immutability)
Methods used to generate a data matrix with as many rows
@interieur.gouv.fr
Generating real-world molecules instead of generic strings.
The goal : Foundry shouldn't be restricted to database tests.
We leverage its features across all testing levels
Uses a real database connection.
Entities are fully persisted, and IDs are naturally generated by the DB.
Perfect for testing real repositories, services, workflows etc
No database persistence (isPersisting() === false)
Objects are instantiated in-memory, but still get unique IDs seamlessly.
Useful to use our Factory for testing domain logic with an Entity full hydrated without booting a Kernel.
If it's a unit test, Foundry automatically disable the persistance useful to use a magic method : withAutoId()
It calls the Foundry’s force() helper to inject IDs directly into the id private property on the AbstractFactory.
Factory::new()->create() stays exactly the same, clean, and database-free on unit test.
new()+ LazyValue vs. create()
Using LazyValue on the factory will lazily load data without persisting it right away ("until I need it").
"new" then "create" allows Foundry to build the entire graph in-memory first.
One Single Request: Everything is committed to the database in one single batch/flush operation.
new()/random()+ LazyValue vs create()new()/random()+ LazyValue vs. create()flush_after() ensuring internal entity modifications are fully committedTweak the foundry initialization is needed to have a full entity with an ID on unit test.
The silent explosion of INSERT queries during poorly managed cascade creations.
The debugging nightmare when it masks the true origin of a Doctrine error.
The Solution: "in the between" with afterInstantiate
We defer the collection generation until the parent object is fully instantiated.
This ensures the entity exists (and has its ID, whether from the DB or our in-memory auto-ID).
Another solution : a method to fully create the Entity on the inverse and call it on a custom Instantiator
This use of Foundry is unifying our data generation.
Special thanks to Kevin Bond and Nikophil for this tool that changed the way we code.
https://github.com/zenstruck/foundry
https://github.com/nikophil
https://github.com/kbond
https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html