### Run Foundry Test Suite with Docker Compose Source: https://github.com/zenstruck/foundry/blob/2.x/README.md Instructions for setting up and running the Foundry test suite using Docker Compose. It covers starting containers, installing dependencies with Composer, and executing different PHPUnit test suites for schema reset and migration reset database strategies. Requires Docker and PHP with specific extensions. ```bash # start the container $ docker compose up --detach # install dependencies $ composer update # run main testsuite (with "schema" reset database strategy) $ ./phpunit # run "migrate" testsuite (with "migrate" reset database strategy) $ ./phpunit --testsuite reset-database ``` -------------------------------- ### PHP: Using Factory States for Explicit Test Data Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example illustrates how to apply predefined states to a factory instance using the `new()` method in Zenstruck Foundry. It shows single state application (`unpublished()`, `withViewCount(3)`) and combining multiple states (`unpublished()->withViewCount(10)`) before calling `create()` to build and persist the entity. This improves test readability by making data setup explicit. ```php // never use the constructor (i.e. "new PostFactory()"), but use the // "new()" method. After defining the states, call "create()" to create // and persist the model. $post = PostFactory::new()->unpublished()->create(); $post = PostFactory::new()->withViewCount(3)->create(); // combine multiple states $post = PostFactory::new() ->unpublished() ->withViewCount(10) ->create() ; ``` -------------------------------- ### Example Generated Zenstruck Foundry PostFactory Class Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This PHP code illustrates the structure of a `PostFactory` class generated by Zenstruck Foundry. It extends `PersistentProxyObjectFactory`, defines the associated entity class, and includes a `defaults()` method for specifying default values for entity properties during fixture generation. It also shows the `initialize()` method for post-creation setup. ```php // src/Factory/PostFactory.php namespace App\Factory; use App\Entity\Post; use App\Repository\PostRepository; use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory; use Zenstruck\Foundry\Persistence\Proxy; use Zenstruck\Foundry\Persistence\ProxyRepositoryDecorator; /** * @extends PersistentProxyObjectFactory */ final class PostFactory extends PersistentProxyObjectFactory { /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services * * @todo inject services if required */ public function __construct() { } public static function class(): string { return Post::class; } /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories * * @todo add your default values here */ protected function defaults(): array|callable { return [ 'createdAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()), 'title' => self::faker()->text(255), ]; } /** * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization */ protected function initialize(): static { return $this ``` -------------------------------- ### Deprecated Instantiator Class and Methods Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Details the deprecation of the `Zenstruck\Foundry\Instantiator` class and its associated methods, providing the new recommended alternatives for object instantiation and property handling. Also includes a configuration deprecation. ```APIDOC Class Deprecation: Old: Zenstruck\Foundry\Instantiator New: Zenstruck\Foundry\Object\Instantiator Constructor Usage: Old: new Instantiator() New: Instantiator::withConstructor() or Instantiator::withoutConstructor() Property Forcing: Old: Instantiator::alwaysForceProperties() New: Instantiator::alwaysForce(variadic parameters) Extra Attributes: Old: Instantiator::allowExtraAttributes() New: Instantiator::allowExtra(variadic parameters) Configuration: Old: zenstruck_foundry.without_constructor New: zenstruck_foundry.use_constructor ``` -------------------------------- ### Miscellaneous API Type-Hinting and Return Type Changes Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Documents changes in type-hinting for `RepositoryProxy` and `RepositoryAssertions`, along with a significant change in the return type of methods within `Zenstruck\Foundry\RepositoryProxy`. ```APIDOC Type-Hinting Migrations: Zenstruck\Foundry\RepositoryProxy -> Zenstruck\Foundry\Persistence\RepositoryDecorator Zenstruck\Foundry\RepositoryAssertions -> Zenstruck\Foundry\Persistence\RepositoryAssertions Return Type Change: Methods in Zenstruck\Foundry\RepositoryProxy no longer return Proxy objects; they now return the actual underlying object. ``` -------------------------------- ### Zenstruck Foundry Bundle Configuration Migration Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Shows a diff of the `zenstruck_foundry` bundle configuration, highlighting deprecated settings (marked with '-') and their new, recommended replacements (marked with '+') for ORM and Mongo persistence. ```YAML zenstruck_foundry: - auto_refresh_proxies: null instantiator: - without_constructor: false + use_constructor: true + orm: + auto_persist: true + reset: + connections: [default] + entity_managers: [default] + mode: schema + mongo: + auto_persist: true + reset: + document_managers: [default] - database_resetter: - enabled: true - orm: - connections: [] - object_managers: [] - reset_mode: schema - odm: - object_managers: [] ``` -------------------------------- ### Zenstruck Foundry: Using Factories Defined as Services Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Provides examples of how to use a factory that has been configured as a service. It demonstrates creating entities with specific attributes and how the injected services (like a password hasher) automatically process the data during entity creation. ```php UserFactory::createOne(['password' => 'mypass'])->getPassword(); // "mypass" encoded UserFactory::createOne()->getPassword(); // "1234" encoded (because "1234" is set as the default password) ``` -------------------------------- ### Example afterInstantiate Callback Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Illustrates a PHP callback function used with `afterInstantiate` to perform actions on a `Post` object after it has been instantiated by the factory. This allows for custom logic post-object creation. ```php // ->afterInstantiate(function(Post $post): void {}) ``` -------------------------------- ### Install Rector and PHPStan Doctrine for Foundry Migration Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md This command installs the necessary `rector/rector` and `phpstan/phpstan-doctrine` packages as development dependencies. These tools are crucial for running the automated Rector rules that assist in the Foundry 1.x to 2.0 migration by applying code transformations. ```shell composer require --dev rector/rector phpstan/phpstan-doctrine ``` -------------------------------- ### Update Factory initialize Method Signature Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Details the required update to the `initialize()` method signature in Zenstruck Foundry factories, specifically changing its return type to `static`. ```PHP // before protected function initialize() { // ... } ``` ```PHP // after protected function initialize(): static { // ... } ``` -------------------------------- ### Deprecated and Removed Standalone Foundry Functions Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Lists deprecated and removed standalone functions in Zenstruck Foundry, providing their new equivalents in the `Persistence` namespace or noting their complete removal without replacement. ```APIDOC Function Migrations: Zenstruck\Foundry\create() -> Zenstruck\Foundry\Persistence\persist() Zenstruck\Foundry\instantiate() -> Zenstruck\Foundry\object() Zenstruck\Foundry\repository() -> Zenstruck\Foundry\Persistence\repository() Zenstruck\Foundry\Factory::delayFlush() -> Zenstruck\Foundry\Persistence\flush_after() Zenstruck\Foundry\Test\TestState methods -> Zenstruck\Foundry\Test\UnitTestConfig::configure() Removed Functions: Zenstruck\Foundry\instantiate_many() (no replacement) Zenstruck\Foundry\create_many() (no replacement) ``` -------------------------------- ### Zenstruck Foundry PHPUnit Data Providers with Extension Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This PHP example demonstrates using Zenstruck Foundry factories directly within PHPUnit data providers when the Foundry PHPUnit Extension is enabled. It allows createOne() and similar methods to be called, providing pre-persisted objects to test methods, simplifying test setup. ```php use App\Factory\PostFactory; use PHPUnit\Framework\Attributes\DataProvider; #[DataProvider('createMultipleObjectsInDataProvider')] public function test_post_via_data_provider(Post $post): void { // at this point, `$post` exists, and is already stored in database } public static function postDataProvider(): iterable { yield [PostFactory::createOne()]; yield [PostWithServiceFactory::createOne()]; yield [PostFactory::createOne(['body' => faker()->sentence()]; } ``` -------------------------------- ### Install Zenstruck Foundry with Composer Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This command installs the Zenstruck Foundry bundle as a development dependency using Composer. It's the essential first step to integrate Foundry into a Symfony project. Ensure Composer is installed and accessible in your terminal. ```terminal $ composer require --dev zenstruck/foundry ``` -------------------------------- ### Group Stories for Fixture Loading with #[AsFixture] Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example demonstrates how to assign stories to specific groups using the `groups` option within the `#[AsFixture]` attribute. This allows for loading multiple related stories simultaneously by specifying a group name with the `bin/console foundry:load-stories` command, streamlining fixture management. ```PHP use Zenstruck\Foundry\Attribute\AsFixture; #[AsFixture(name: 'category', groups: ['all-stories'])] final class CategoryStory extends Story {} #[AsFixture(name: 'post', groups: ['all-stories'])] final class PostStory extends Story {} ``` -------------------------------- ### Configure Rector for Foundry 1.x to 2.0 Migration Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md This PHP snippet defines the `rector.php` configuration file. It sets the paths where Rector should apply its rules (e.g., `src/Factory`, `tests`) and includes the `FoundrySetList::UP_TO_FOUNDRY_2` rule set to automate migration tasks specifically for Foundry 2.0. ```php withPaths([ // add all paths where your factories are defined and where Foundry is used 'src/Factory', 'tests' ]) ->withSets([FoundrySetList::UP_TO_FOUNDRY_2]) ; ``` -------------------------------- ### Define Story Build Method for Data Generation Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Shows an example of implementing the `build()` method within a Zenstruck Foundry Story. This method is used to define and create a specific set of entities (e.g., categories, tags, posts) that represent a desired application state. ```php // src/Story/PostStory.php namespace App\Story; use App\Factory\CategoryFactory; use App\Factory\PostFactory; use App\Factory\TagFactory; use Zenstruck\Foundry\Story; final class PostStory extends Story { public function build(): void { // create 10 Category's CategoryFactory::createMany(10); // create 20 Tag's TagFactory::createMany(20); // create 50 Post's PostFactory::createMany(50, function() { return [ // each Post will have a random Category (created above) 'category' => CategoryFactory::random(), // each Post will between 0 and 6 Tag's (created above) 'tags' => TagFactory::randomRange(0, 6), ]; }); } } ``` -------------------------------- ### Demonstrate Zenstruck Foundry Factory Immutability Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example highlights the immutable nature of Zenstruck Foundry factories. When methods like `with()`, `instantiateWith()`, `beforeInstantiate()`, `afterInstantiate()`, or `afterPersist()` are called, they return a *new* factory instance rather than modifying the original one, ensuring predictable behavior. ```php use App\Factory\PostFactory; $factory = PostFactory::new(); $factory1 = $factory->with([]); // returns a new PostFactory object $factory2 = $factory->instantiateWith(function () {}); // returns a new PostFactory object $factory3 = $factory->beforeInstantiate(function () {}); // returns a new PostFactory object $factory4 = $factory->afterInstantiate(function () {}); // returns a new PostFactory object $factory5 = $factory->afterPersist(function () {}); // returns a new PostFactory object ``` -------------------------------- ### Define and Use a Custom Array Factory Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Illustrates how to create a custom `ArrayFactory` to generate associative arrays with default values. It includes the factory definition and an example of creating an array, overriding some default properties. ```php use Zenstruck\Foundry\ArrayFactory; final class SomeArrayFactory extends ArrayFactory { protected function defaults(): array|callable { return [ 'prop1' => 'default value 1', 'prop2' => 'default value 2', ]; } } // somewhere in a test // will create ['prop1' => 'foo', 'prop2' => 'default value 2'] $array = SomeArrayFactory::createOne(['prop1' => 'foo']); ``` -------------------------------- ### Extend Zenstruck Foundry Database Reset Mechanism with PHP Decorator Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This PHP class demonstrates how to extend Zenstruck Foundry's database reset mechanism using Symfony's service decoration. It allows executing custom logic before the first test (`resetBeforeFirstTest`) and before each test (`resetBeforeEachTest`), useful for tasks like installing PostgreSQL extensions or restarting sequences, ensuring test environment setup flexibility. ```php use Symfony\Component\DependencyInjection\Attribute\AsDecorator; use Symfony\Component\DependencyInjection\Attribute\When; use Symfony\Component\HttpKernel\KernelInterface; use Zenstruck\Foundry\ORM\ResetDatabase\OrmResetter; // The decorator should be declared in test environment only. #[When('test')] // You can also decorate `MongoResetter::class`. #[AsDecorator(OrmResetter::class)] final readonly class DecorateDatabaseResetter implements OrmResetter { public function __construct( private OrmResetter $decorated ) {} public function resetBeforeFirstTest(KernelInterface $kernel): void { // do something once per test suite (for instance: install a PostgreSQL extension) $this->decorated->resetBeforeFirstTest($kernel); } public function resetBeforeEachTest(KernelInterface $kernel): void { // do something once per test case (for instance: restart PostgreSQL sequences) $this->decorated->resetBeforeEachTest($kernel); } } ``` -------------------------------- ### Implement In-Memory Repository with Foundry Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Provides an example of an in-memory repository implementation using Zenstruck Foundry's `InMemoryRepository` interface and `InMemoryRepositoryTrait`. This pattern is useful for Domain-Driven Design (DDD) or hexagonal architectures where repositories are abstracted. ```php use App\Domain\Address\DomainAddressRepositoryInterface; use Zenstruck\Foundry\InMemory\InMemoryRepository; use Zenstruck\Foundry\InMemory\InMemoryRepositoryTrait; /** * @implements InMemoryRepository
*/ final class InMemoryAddressRepository implements InMemoryRepository, DomainAddressRepositoryInterface { /** @use InMemoryRepositoryTrait
*/ use InMemoryRepositoryTrait; // The class returned by this method is the class managed by this repository } ``` -------------------------------- ### Zenstruck Foundry Object Proxy Basic Operations in PHP Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This PHP example illustrates the basic usage of Zenstruck Foundry's Object Proxy. It shows how to create an entity using a factory, access the real object, call methods, set properties, save, refresh, and delete entities, and access the repository proxy, providing an Active Record-like interface. ```php use App\Factory\PostFactory; $post = PostFactory::createOne(['title' => 'My Title']); // instance of Zenstruck\Foundry\Proxy // get the wrapped object $realPost = $post->_real(); // instance of Post // call any Post method $post->getTitle(); // "My Title" // set property and save to the database $post->setTitle('New Title'); $post->_save(); // refresh from the database $post->_refresh(); // delete from the database $post->_delete(); $post->_repository(); // repository proxy wrapping PostRepository (see Repository Proxy section below) ``` -------------------------------- ### Configure Zenstruck Foundry PHPUnit Extension Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This XML snippet shows how to modify `phpunit.xml.dist` to install the Zenstruck Foundry PHPUnit extension. This extension provides features like `#[WithStory]` attribute support and `Factory::create()` usage in data providers. ```XML ``` -------------------------------- ### Using Zenstruck Foundry Repository Proxy for Entity Management Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example showcases the comprehensive capabilities of Zenstruck Foundry's Repository Proxy. It demonstrates how to obtain a proxy for an entity repository and utilize its various methods for common database operations such as counting, finding, truncating, and retrieving random entities. All returned objects from these methods are automatically proxied. ```php use App\Entity\Post; use App\Factory\PostFactory; use function Zenstruck\Foundry\Persistence\repository; // instance of RepositoryProxy that wraps PostRepository $repository = PostFactory::repository(); // alternative to above for proxying repository you haven't created factories for $repository = repository(Post::class); // helpful methods - all returned object(s) are proxied $repository->inner(); // the real "wrapped" repository $repository->count(); // number of rows in the database table count($repository); // equivalent to above (RepositoryProxy implements \Countable) $repository->first(); // get the first object (assumes an auto-incremented "id" column) $repository->first('createdAt'); // assuming "createdAt" is a datetime column, this will return latest object $repository->last(); // get the last object (assumes an auto-incremented "id" column) $repository->last('createdAt'); // assuming "createdAt" is a datetime column, this will return oldest object $repository->truncate(); // delete all rows in the database table $repository->random(); // get a random object $repository->random(['author' => 'kevin']); // get a random object filtered by the passed criteria $repository->randomSet(5); // get 5 random objects $repository->randomSet(5, ['author' => 'kevin']); // get 5 random objects filtered by the passed criteria $repository->randomRange(0, 5); // get 0-5 random objects $repository->randomRange(0, 5, ['author' => 'kevin']); // get 0-5 random objects filtered by the passed criteria // instance of ObjectRepository - all returned object(s) are proxied $repository->find(1); // Proxy|Post|null $repository->find(['title' => 'My Title']); // Proxy|Post|null $repository->findOneBy(['title' => 'My Title']); // Proxy|Post|null $repository->findAll(); // Proxy[]|Post[] iterator_to_array($repository); // equivalent to above (RepositoryProxy implements \IteratorAggregate) $repository->findBy(['title' => 'My Title']); // Proxy[]|Post[] // can call methods on the underlying repository - returned object(s) are proxied $repository->findOneByTitle('My Title'); // Proxy|Post|null ``` -------------------------------- ### Provide Custom Instantiation Logic with Callable Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example shows how to provide a completely custom callable function to `instantiateWith()`. This callable receives all attributes and the class name, giving full control over the object creation and hydration process, overriding Foundry's default behavior. ```php ->instantiateWith(function(array $attributes, string $class): object { return new Post(); // ... your own logic }) ``` -------------------------------- ### Execute Rector for Foundry Migration Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md These shell commands demonstrate how to run Rector. The first command performs a 'dry run' to preview changes without modifying files, which is useful for review. The second command applies the changes directly, automating many of the code modifications required for the Foundry 1.x to 2.0 migration. ```shell # you can run Rector in "dry run" mode, in order to see which files will be modified vendor/bin/rector process --dry-run # actually modify files vendor/bin/rector process ``` -------------------------------- ### Zenstruck Foundry Proxy Method Deprecations and Replacements Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Documents the deprecated methods of the `Zenstruck\Foundry\Proxy` class in Zenstruck Foundry 2.0, outlining their new `_` prefixed equivalents and methods that have been removed without direct replacement, reflecting the shift to Symfony's lazy proxy mechanism. ```APIDOC Zenstruck\Foundry\Proxy (Deprecated Methods): - object() -> _real() - save() -> _save() - remove() -> _delete() - refresh() -> _refresh() - forceSet() -> _set() - forceGet() -> _get() - repository() -> _repository() - enableAutoRefresh() -> _enableAutoRefresh() - disableAutoRefresh() -> _disableAutoRefresh() - withoutAutoRefresh() -> _withoutAutoRefresh() - assertPersisted() -> _assertPersisted() - assertNotPersisted() -> _assertNotPersisted() - isPersisted() (Removed, no replacement) - forceSetAll() (Removed, no replacement) Type Hinting Change: - Zenstruck\Foundry\Proxy -> Zenstruck\Foundry\Persistence\Proxy (interface) - Zenstruck\Foundry\FactoryCollection (from PersistentProxyObjectFactory) -> Zenstruck\Foundry\FactoryCollection> ``` -------------------------------- ### Zenstruck Foundry: Defining Custom Factory States and Methods Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example illustrates how to extend a Zenstruck Foundry factory with custom methods, often referred to as 'states'. These methods allow for encapsulating specific data configurations or behaviors, such as setting a `published_at` date or a `viewCount`, making factory usage more expressive and reusable. The `with()` method is used to apply attributes to the factory's current state. ```php final class PostFactory extends PersistentProxyObjectFactory { // ... public function published(): self { // call setPublishedAt() and pass a random DateTime return $this->with(['published_at' => self::faker()->dateTime()]); } public function unpublished(): self { return $this->with(['published_at' => null]); } public function withViewCount(?int $count = null): self { ``` -------------------------------- ### Using In-Memory Repositories in PHPUnit Tests Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example demonstrates how to use the `#[AsInMemoryTest]` attribute to enable in-memory repositories for PHPUnit tests. It shows how to retrieve an in-memory repository, create entities, and assert their presence without database interaction, along with configuring the `InMemoryKernel`. ```PHP use Zenstruck\Foundry\InMemory\AsInMemoryTest; #[AsInMemoryTest] final class SomeTest extends KernelTestCase { private InMemoryAddressRepository $addressRepository; protected function setUp(): void { $this->addressRepository = self::getContainer()->get(InMemoryAddressRepository::class); } #[Test] public function object_should_be_accessible_from_in_memory_repository(): void { $address = AddressFactory::createOne(); self::assertSame([$address], $this->addressRepository->_all()); // The following assertion is also true, `YourFactory::repository()` returns a special "in-memory" repository // no request to the database will be made. self::assertSame(1, AddressFactory::repository()->count(1)); // You can even use `YourFactory::repository()->assert()` AddressFactory::repository()->assert()->count(1); } protected static function getKernelClass(): string { // This is one of the ways to use the "in-memory" repositories in a "kernel test": // the "InMemoryKernel" would use "in-memory" repositories instead of the main ones. return InMemoryKernel::class; } } ``` -------------------------------- ### Override Factory initialize() for Default State Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Illustrates how to override the `initialize()` method in a Zenstruck Foundry factory to set default states, custom instantiation logic, or default event listeners for all objects created by that factory. This centralizes common setup logic. ```php final class PostFactory extends PersistentProxyObjectFactory { // ... protected function initialize(): static { return $this ->published() // published by default ->instantiateWith(function (array $attributes) { return new Post(); // custom instantiation for this factory }) ->afterPersist(function () {}) // default event for this factory ; } } ``` -------------------------------- ### Update Factory getDefaults/defaults Method Signature Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Illustrates the required change in the `getDefaults()` method signature for Zenstruck Foundry factories, transitioning from a fixed `array` return type to a more flexible `array|callable` return type, along with a method rename to `defaults()`. ```PHP // before protected function getDefaults(): array { // ... } ``` ```PHP // after protected function defaults(): array|callable { // ... } ``` -------------------------------- ### Create Multiple Many-to-Many Relationships with Random Set of Existing Entities Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Demonstrates creating multiple parent entities (Posts), each linked to a random *set* of existing child entities (Tags). A callback ensures each parent gets a different random selection. ```PHP TagFactory::createMany(10); PostFactory::new() ->many(5) ->create(function() { return ['tags' => TagFactory::randomSet(2)]; }) ; ``` -------------------------------- ### Paratest Doctrine DBAL Configuration for Parallel Testing Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst YAML configuration for Doctrine DBAL within a Symfony application's `config/packages/doctrine.yaml` file. This setup is crucial for enabling parallel testing with `paratestphp/paratest` by appending a unique `TEST_TOKEN` to the database name for each parallel process. ```yaml # config/packages/doctrine.yaml when@test: doctrine: dbal: dbname_suffix: '_test%env(default::TEST_TOKEN)%' ``` -------------------------------- ### Update Factory getClass/class Method Signature Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Shows the necessary modification to the `getClass()` method signature in Zenstruck Foundry factories, including changes to visibility (`public`), static keyword, and the method name (`class()`). ```PHP // before protected static function getClass(): string { // ... } ``` ```PHP // after public static function class(): string { // ... } ``` -------------------------------- ### YAML: Configure Zenstruck Foundry Database Reset Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Provides a YAML configuration example for customizing which database connections and object managers Zenstruck Foundry should reset. This allows specifying multiple ORM connections and MongoDB document managers, and setting the reset mode (e.g., 'schema'). ```yaml # config/packages/zenstruck_foundry.yaml when@dev: # see Bundle Configuration section about sharing this in the test environment zenstruck_foundry: orm: reset: connections: - orm_connection_1 - orm_connection_2 entity_managers: - orm_object_manager_1 - orm_object_manager_2 mode: schema # default value, enables resetting the schema with doctrine:schema commands mongo: reset: document_managers: - odm_object_manager_1 - odm_object_manager_2 ``` -------------------------------- ### Create Object Manager for Rector's Doctrine Mapping Resolution Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md This PHP snippet creates a `tests/object-manager.php` file, which exposes your Doctrine configuration. This file is essential for Rector to understand your Doctrine mapping, especially when it's defined outside of code (e.g., XML, YAML), allowing Rector to correctly determine whether to use `PersistentProxyObjectFactory` or `ObjectFactory` during the migration. ```php bootEnv(__DIR__ . '/../.env'); $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); $kernel->boot(); return $kernel->getContainer()->get('doctrine')->getManager(); ``` -------------------------------- ### Generate Foundry Factory using Symfony MakerBundle Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This terminal command utilizes Symfony MakerBundle to generate a new factory class for a specified entity, in this case, the `Post` entity. It automates the creation of the factory file, prompting the user for the entity name. The generated factory provides a structured starting point for defining default fixture data. ```terminal $ php bin/console make:factory > Entity class to create a factory for: > Post created: src/Factory/PostFactory.php Next: Open your new factory and set default values/states. ``` -------------------------------- ### PHP: Distributing Values Across Factory-Created Objects with `distribute()` Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example demonstrates the `distribute()` method in Zenstruck Foundry, which allows assigning values from a collection to a series of created objects in a round-robin or sequential manner. It shows distributing related entities (categories) and scalar values (datetimes) across posts created using a sequence, enabling precise control over generated data relationships. ```php // let's say we have 2 categories... $categories = CategoryFactory::createSequence( [ ['name' => 'category 1'], ['name' => 'category 2'] ] ); // ...that we want to "distribute" over 2 posts $posts = PostFactory::new() ->sequence( [ ['name' => 'post 1'], ['name' => 'post 2'] ] ) // "post 1" will have "category 1" and "post 2" will have "category 2" ->distribute('category', $categories) // you can even chain "distribute()" methods: // first post is published today, second post is published tomorrow ->distribute('publishedAt', [new \DateTimeImmutable('today'), new \DateTimeImmutable('tomorrow')]) ->create(); ``` -------------------------------- ### Activate Symfony Deprecation Helper for Foundry 1.x to 2.0 Migration Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md This snippet shows how to configure the `SYMFONY_DEPRECATIONS_HELPER` environment variable to detect deprecations during the migration from Foundry 1.x to 2.0. It should be set in `phpunit.xml` or `.env.local` to help identify code that needs updating. ```shell SYMFONY_DEPRECATIONS_HELPER="max[self]=0&max[direct]=0&quiet[]=indirect&quiet[]=other" ``` -------------------------------- ### Override Default Configuration with .env.local Source: https://github.com/zenstruck/foundry/blob/2.x/README.md Examples of how to customize Foundry's test environment by creating a '.env.local' file. It shows how to change the database URL (PostgreSQL, SQLite), disable Mongo, enable Dama Doctrine Test Bundle, and set the PHPUnit version for testing. ```dotenv # .env.local # change the database to postgreSQL... DATABASE_URL="postgresql://zenstruck:zenstruck@127.0.0.1:5433/zenstruck_foundry?serverVersion=15" # ...or to SQLite DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" MONGO_URL="" # disables Mongo USE_DAMA_DOCTRINE_TEST_BUNDLE="1" # enables dama/doctrine-test-bundle PHPUNIT_VERSION="11" # possible values: 9, 10, 11, 11.4 # test reset database with migrations, # only relevant for "reset-database" testsuite MIGRATION_CONFIGURATION_FILE="tests/Fixture/MigrationTests/configs/migration-configuration.php" ``` -------------------------------- ### Manage and Access Story-Created Objects (State) Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Illustrates how stories can remember objects they create using `addState()`. It also shows two ways to retrieve these stored objects later: using `get()` or a magic method, enabling other factories to reference specific story-generated entities. ```php // src/Story/CategoryStory.php namespace App\Story; use App\Factory\CategoryFactory; use Zenstruck\Foundry\Story; final class CategoryStory extends Story { public function build(): void { $this->addState('php', CategoryFactory::createOne(['name' => 'php'])); // factories are created when added as state $this->addState('symfony', CategoryFactory::new(['name' => 'symfony'])); } } PostFactory::createOne(['category' => CategoryStory::get('php')]); // or use the magic method (functionally equivalent to above) PostFactory::createOne(['category' => CategoryStory::php()]); ``` -------------------------------- ### Fetch Objects from Story Pools Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This snippet demonstrates various methods to retrieve objects from defined story pools. It shows how to get a random object, a set of random objects, a range of random objects, or all objects from a specific pool, providing flexibility in accessing generated data. ```PHP ProvinceStory::getRandom('be'); // random Province|Proxy from "be" pool ProvinceStory::getRandomSet('be', 3); // 3 random Province|Proxy's from "be" pool ProvinceStory::getRandomRange('be', 1, 4); // between 1 and 4 random Province|Proxy's from "be" pool ProvinceStory::getPool('be'); // all Province|Proxy's from "be" pool ``` -------------------------------- ### PHP: Overriding Factory Attributes and Relationships with `with()` and `many()` Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This comprehensive example demonstrates various ways to define and override attributes when creating entities with Zenstruck Foundry. It shows initial attribute setting with `new()`, adding attributes and relationships with `with()` (including callable attributes and nested factory creation), and creating multiple instances with `many()`, while also illustrating how to access the created objects and their properties. ```php use App\Entity\Category; use App\Entity\Post; use App\Factory\CategoryFactory; use App\Factory\PostFactory; use function Zenstruck\Foundry\faker; // The first argument to "new()" allows you to overwrite the default // values that are defined in the `PostFactory::defaults()` $posts = PostFactory::new(['title' => 'Post A']) ->with([ 'body' => 'Post Body...', // CategoryFactory will be used to create a new Category for each Post 'category' => CategoryFactory::new(['name' => 'php']) ]) ->with([ // Proxies are automatically converted to their wrapped object // will override previous category 'category' => CategoryFactory::createOne(['name' => 'Symfony']) ]) ->with(function() { return ['createdAt' => faker()->dateTime()]; }) // see faker section below // create "2" Post's ->many(2)->create(['title' => 'Different Title']) ; $posts[0]->getTitle(); // "Different Title" $posts[0]->getBody(); // "Post Body..." $posts[0]->getCategory(); // Category with name "Symfony" $posts[0]->getPublishedAt(); // \DateTime('last week') $posts[0]->getCreatedAt(); // random \DateTime $posts[1]->getTitle(); // "Different Title" $posts[1]->getBody(); // "Post Body..." $posts[1]->getCategory(); // Category with name "Symfony" (same object than above) $posts[1]->getPublishedAt(); // \DateTime('last week') $posts[1]->getCreatedAt(); // random \DateTime (different than above) ``` -------------------------------- ### Add Objects to Story Pools Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This example illustrates how to add single or multiple generated objects to named 'pools' within a Foundry Story. Pools allow for organized storage and retrieval of objects, which can then be fetched in tests or other stories. It shows using `addToPool` for collections and single objects, and `addState` for adding a single object to a pool while also making it available as state. ```PHP // src/Story/ProvinceStory.php namespace App\Story; use App\Factory\ProvinceFactory; use Zenstruck\Foundry\Story; final class ProvinceStory extends Story { public function build(): void { // add collection to a "pool" $this->addToPool('be', ProvinceFactory::createMany(5, ['country' => 'BE'])); // equivalent to above $this->addToPool('be', ProvinceFactory::new(['country' => 'BE'])->many(5)); // add single object to a pool $this->addToPool('be', ProvinceFactory::createOne(['country' => 'BE'])); // add single object to single pool and make available as "state" $this->addState('be-1', ProvinceFactory::createOne(['country' => 'BE']), 'be'); } } ``` -------------------------------- ### Zenstruck Foundry Factory Creation and Lifecycle Callbacks Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst Demonstrates basic factory usage for creating entities and various lifecycle callbacks available during instantiation and persistence, allowing custom logic at different stages of the object creation process. ```php $factory->many(5)->create(['field' => 'value']); $factory->instantiateWith(function () {}); $factory->beforeInstantiate(function () {}); $factory->afterInstantiate(function () {}); $factory->afterPersist(function () {}); ``` -------------------------------- ### Zenstruck Foundry: Creating and Querying Entities with PostFactory Source: https://github.com/zenstruck/foundry/blob/2.x/docs/index.rst This snippet demonstrates various methods available on a Zenstruck Foundry factory, specifically `PostFactory`, for creating, persisting, and querying entity objects. It covers single and multiple object creation, conditional creation (`findOrCreate`, `randomOrCreate`), and various retrieval methods like `first`, `last`, `find`, `all`, `findBy`, `random`, and `randomSet`/`randomRange`. It also shows how to interact with the returned Proxy objects and access the real entity. ```php use App\Factory\PostFactory; // create/persist Post with random data from `defaults()` PostFactory::createOne(); // or provide values for some properties (others will be random) PostFactory::createOne(['title' => 'My Title']); // createOne() returns the persisted Post object wrapped in a Proxy object $post = PostFactory::createOne(); // the "Proxy" magically calls the underlying Post methods and is type-hinted to "Post" $title = $post->getTitle(); // getTitle() can be autocompleted by your IDE! // if you need the actual Post object, use ->_real() $realPost = $post->_real(); // create/persist 5 Posts with random data from defaults() PostFactory::createMany(5); // returns Post[]|Proxy[] PostFactory::createMany(5, ['title' => 'My Title']); // Create 5 posts with incremental title PostFactory::createMany( 5, static function(int $i) { return ['title' => "Title $i"]; // "Title 1", "Title 2", ... "Title 5" } ); // find a persisted object for the given attributes, if not found, create with the attributes PostFactory::findOrCreate(['title' => 'My Title']); // returns Post|Proxy PostFactory::first(); // get the first object (assumes an auto-incremented "id" column) PostFactory::first('createdAt'); // assuming "createdAt" is a datetime column, this will return latest object PostFactory::last(); // get the last object (assumes an auto-incremented "id" column) PostFactory::last('createdAt'); // assuming "createdAt" is a datetime column, this will return oldest object PostFactory::truncate(); // empty the database table PostFactory::count(); // the number of persisted Posts PostFactory::count(['category' => $category]); // the number of persisted Posts with the given category PostFactory::all(); // Post[]|Proxy[] all the persisted Posts PostFactory::findBy(['author' => 'kevin']); // Post[]|Proxy[] matching the filter $post = PostFactory::find(5); // Post|Proxy with the id of 5 $post = PostFactory::find(['title' => 'My First Post']); // Post|Proxy matching the filter // get a random object that has been persisted $post = PostFactory::random(); // returns Post|Proxy $post = PostFactory::random(['author' => 'kevin']); // filter by the passed attributes // or automatically persist a new random object if none exists $post = PostFactory::randomOrCreate(); $post = PostFactory::randomOrCreate(['author' => 'kevin']); // filter by or create with the passed attributes // get a random set of objects that have been persisted $posts = PostFactory::randomSet(4); // array containing 4 "Post|Proxy" objects $posts = PostFactory::randomSet(4, ['author' => 'kevin']); // filter by the passed attributes // random range of persisted objects $posts = PostFactory::randomRange(0, 5); // array containing 0-5 "Post|Proxy" objects $posts = PostFactory::randomRange(0, 5, ['author' => 'kevin']); // filter by the passed attributes // or automatically persist a new random range of objects if none exists $posts = PostFactory::randomRangeOrCreate(0, 5); // array containing 0-5 "Post|Proxy" objects $posts = PostFactory::randomRangeOrCreate(0, 5, ['author' => 'kevin']); // filter by or create with the passed attributes ``` -------------------------------- ### Understanding RefreshObjectFailed Error Source: https://github.com/zenstruck/foundry/blob/2.x/UPGRADE-2.0.md Explains the 'RefreshObjectFailed' error encountered when an auto-refreshed proxy object is accessed after unsaved modifications. It advises either saving changes with `_save()` or disabling auto-refresh. ```Plain Text RefreshObjectFailed: Cannot auto refresh "[Entity FQCN]" as there are unsaved changes. Be sure to call ->_save() or disable auto refreshing. ``` -------------------------------- ### Run Documentation Linter Locally with Docker Source: https://github.com/zenstruck/foundry/blob/2.x/README.md Command to execute the documentation linter locally using a Docker image. It mounts the local 'docs' directory into the container to verify document syntax against required standards, ensuring consistency and quality. ```shell docker run --rm -it -e DOCS_DIR='/docs' -v ${PWD}/docs:/docs oskarstark/doctor-rst:latest ```