This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
The file ".specify/memory/constitution.md" MUST also be loaded in memory and is VERY important.
Always respond in French (Français).
Zenstruck Foundry is a Symfony bundle that provides an expressive, auto-completable, on-demand fixtures system for Doctrine ORM and MongoDB ODM. It allows developers to create test data using a fluent factory API that can be used in both tests and data fixtures.
# Start required services (MySQL, PostgreSQL, MongoDB)
docker compose up --detach
# Install dependencies
composer update
# Run main test suite
./phpunit
# Run specific test suites
./phpunit --testsuite main # Unit tests only
./phpunit --testsuite reset-database # Database reset strategy tests
# Run single test
./phpunit path/to/TestFile.php --filter testMethodName
# Exclude slow maker tests
./phpunit --exclude-group=makerCreate .env.local with these settings for 3-5x faster tests:
USE_DAMA_DOCTRINE_TEST_BUNDLE="1" # Wraps tests in transactions
MONGO_URL="" # Skip MongoDB if not testing it
PHPUNIT_VERSION="12" # Use latest PHPUnit
USE_FOUNDRY_PHPUNIT_EXTENSION="1" # Enable Foundry's PHPUnit extensionOverride database in .env.local:
# PostgreSQL
DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15"
# SQLite (no Docker needed)
DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"# Static analysis
bin/tools/phpstan/vendor/phpstan/phpstan/phpstan analyse
# Psalm (for generated factories)
bin/tools/psalm/vendor/vimeo/psalm/psalmImportant: Ne jamais lancer php-cs-fixer manuellement sur ce projet. Le formatage du code est géré automatiquement par la CI.
The core factory system uses three levels of abstraction:
Factory (abstract)
├─ ObjectFactory (abstract)
│ └─ PersistentObjectFactory (abstract)
│ └─ PersistentProxyObjectFactory (abstract, deprecated in 2.7+)
└─ ArrayFactory
Key files:
src/Factory.php- Base factory with immutable builder pattern and attribute normalizationsrc/ObjectFactory.php- Adds object instantiation, hooks (beforeInstantiate, afterInstantiate), and object reusesrc/Persistence/PersistentObjectFactory.php- Adds database persistence, repositories, and afterPersist hooks
Key concept: All factory methods return cloned instances (immutable builder pattern), enabling safe method chaining:
PostFactory::new()
->published() // Returns new clone
->with(['title' => 'Foo']) // Returns new clone
->create()The persistence layer uses an adapter pattern to abstract Doctrine ORM and MongoDB ODM:
PersistentObjectFactory
→ ProxyGenerator (lazy loading via Symfony VarExporter)
→ PersistenceManager (central orchestrator)
→ PersistenceStrategy (adapter interface)
├─ OrmV2PersistenceStrategy / OrmV3PersistenceStrategy
└─ MongoPersistenceStrategy
Key files:
src/Persistence/PersistenceManager.php- Orchestrates persistence operationssrc/Persistence/ProxyGenerator.php- Generates lazy-loading proxies usingeval()and Symfony VarExportersrc/Persistence/PersistenceStrategy.php- Abstract adapter for Doctrine backendssrc/ORM/AbstractORMPersistenceStrategy.php- Doctrine ORM implementationsrc/Mongo/MongoPersistenceStrategy.php- MongoDB ODM implementation
Proxy pattern: Objects are wrapped in lazy-loading proxies that:
- Defer object creation until first property/method access
- Auto-refresh from database when accessed (configurable)
- Provide methods like
_save(),_refresh(),_delete(),_assertPersisted()
Configuration (src/Configuration.php):
- Singleton that boots Foundry with factories, faker, and persistence managers
- Manages global state for factory registry, stories, and instantiator
- Supports in-memory mode for unit tests (no database required)
Test integration:
- PHPUnit:
src/Test/Factories.phptrait boots Foundry,src/PHPUnit/FoundryExtension.phpmanages lifecycle - Behat:
src/Test/Behat/FoundryExtension.phpintegrates with Symfony kernel - In-memory mode:
src/InMemory/provides database-less testing for unit tests
Bundle (src/ZenstruckFoundryBundle.php):
- Symfony bundle that registers services, configures DI container
- Configures faker locale/seed, instantiator defaults, database reset strategies
- Registers maker commands and test extensions
Immutable builder: Factory methods return cloned instances, never mutating the original.
Hook system with priorities: Callbacks at instantiation and persistence points:
beforeInstantiate(callable $callback, int $priority = 0)- Modify parameters before object creationafterInstantiate(callable $callback, int $priority = 0)- Post-process object after creationafterPersist(callable $callback, int $priority = 0)- Final customization after database persistence
Lazy loading: ProxyGenerator creates runtime proxy classes using Symfony's VarExporter and eval(), wrapping real objects for deferred instantiation.
Object reuse: reuse(object ...$objects) composes objects via type-based injection into factory attributes.
FactoryCollection: Batch operations via many(), range(), sequence(), and distribute() methods.
Relationship system: src/Persistence/Relationship/ handles bidirectional relationships (ManyToOne, OneToMany, OneToOne) to maintain consistency.
The ./phpunit script dynamically manages PHPUnit versions (9, 10, 11, 12) based on PHPUNIT_VERSION env var:
PHPUNIT_VERSION="11" ./phpunitDifferent PHPUnit versions require different configuration files and extension loading strategies.
Schema reset (default, faster):
./phpunit --testsuite reset-database
# Uses: doctrine:schema:drop + doctrine:schema:updateMigrate reset (tests real migrations):
# In .env.local:
DATABASE_RESET_MODE="migrate"
MIGRATION_CONFIGURATION_FILE="tests/Fixture/MigrationTests/configs/migration-configuration.php"
./phpunit --testsuite reset-database --bootstrap tests/bootstrap-reset-database.phpDAMA/DoctrineTestBundle: Wraps tests in database transactions for 3-5x speedup (automatically rolls back after each test):
USE_DAMA_DOCTRINE_TEST_BUNDLE="1" ./phpunitPHP 8.4 Lazy Objects: Alternative to proxy generation using native PHP 8.4 lazy objects:
# config/packages/zenstruck_foundry.yaml
zenstruck_foundry:
enable_auto_refresh_with_lazy_objects: trueMaker tests generate factories and verify output against expected files. They're slow, so exclude them for faster iteration:
./phpunit --exclude-group=makerExpected output files are in tests/Fixture/Maker/expected/.
Core factory system:
src/Factory.php,src/ObjectFactory.php,src/Persistence/PersistentObjectFactory.phpsrc/FactoryCollection.php- Batch operations
Persistence layer:
src/Persistence/PersistenceManager.php,src/Persistence/ProxyGenerator.phpsrc/Persistence/PersistenceStrategy.php- Adapter interfacesrc/ORM/- Doctrine ORM adapters (v2 and v3)src/Mongo/- MongoDB ODM adapter
Test integration:
src/Test/Factories.php- PHPUnit traitsrc/PHPUnit/FoundryExtension.php- PHPUnit extensionsrc/Test/Behat/- Behat integrationsrc/InMemory/- In-memory repositories for unit tests
Maker commands:
src/Maker/MakeFactory.php- Factory generatorsrc/Maker/MakeStory.php- Story generatorsrc/Maker/Factory/- Factory generation logic
Bundle:
src/ZenstruckFoundryBundle.php- Symfony bundlesrc/DependencyInjection/- Service configuration
Object instantiation:
src/Object/Instantiator.php- Multiple instantiation strategiessrc/Object/Hydrator.php- Property setting via PropertyAccess
- The codebase supports both Doctrine ORM and MongoDB ODM simultaneously
- Factories can be used in DoctrineFixturesBundle fixtures AND in tests
- The bundle provides repository decorators (
ProxyRepositoryDecorator) with test assertions - Stories are fixture sets that can be loaded in tests via
$this->load(UserStory::class) - The
functions.phpfiles provide global helper functions (e.g.,factory(),repository()) - add phpdoc
@internalfor classes that must not be directly used by final users - for string concatenation, if no static involved, prefer interpolation to sprintf
- utilise le moins possible de commentaires, uniquement la phpdoc qui est utile et qui n'est pas un doublon des déclaration de type
- tous les fichiers de code modifiés doivent l'être en anglais, sauf éventuellement les specs et les plans
- à chaque fois que Claude pose des questions, il doit toujours proposer un onglet supplémentaire pour donner des directives qui n'ont potentiellement rien à voir avec les questions en cours
- minimise les variables temporaires inutiles
- préfère les
array_map()etarray_filter()auforeach, sauf que c'est trop complexe - préfère les nouvelles méthodes
array_find(),array_find_key(),array_any()etarray_all()sur les tableaux de PHP 8.4 et 8.5 auxforeach, sauf que c'est trop complexe - si possible, les callable doivent être static
IMPORTANT: You MUST use grepai as your PRIMARY tool for code exploration and search.
Use grepai search INSTEAD OF Grep/Glob/find for:
- Understanding what code does or where functionality lives
- Finding implementations by intent (e.g., "authentication logic", "error handling")
- Exploring unfamiliar parts of the codebase
- Any search where you describe WHAT the code does rather than exact text
Only use Grep/Glob when you need:
- Exact text matching (variable names, imports, specific strings)
- File path patterns (e.g.,
**/*.go)
If grepai fails (not running, index unavailable, or errors), fall back to standard Grep/Glob tools.
# ALWAYS use English queries for best results (--compact saves ~80% tokens)
grepai search "user authentication flow" --json --compact
grepai search "error handling middleware" --json --compact
grepai search "database connection pool" --json --compact
grepai search "API request validation" --json --compact- Use English for queries (better semantic matching)
- Describe intent, not implementation: "handles user login" not "func Login"
- Be specific: "JWT token validation" better than "token"
- Results include: file path, line numbers, relevance score, code preview
Use grepai trace to understand function relationships:
- Finding all callers of a function before modifying it
- Understanding what functions are called by a given function
- Visualizing the complete call graph around a symbol
IMPORTANT: Always use --json flag for optimal AI agent integration.
# Find all functions that call a symbol
grepai trace callers "HandleRequest" --json
# Find all functions called by a symbol
grepai trace callees "ProcessOrder" --json
# Build complete call graph (callers + callees)
grepai trace graph "ValidateToken" --depth 3 --json- Start with
grepai searchto find relevant code - Use
grepai traceto understand function relationships - Use
Readtool to examine files from results - Only use Grep for exact string searches if needed