ergebnis / factory-bot
Provides a fixture factory for doctrine/orm entities.
Installs: 616 415
Dependents: 0
Suggesters: 0
Security: 0
Stars: 78
Watchers: 2
Forks: 4
Open Issues: 0
Requires
- php: ~8.1.0 || ~8.2.0 || ~8.3.0
- doctrine/collections: ^1.6.5 || ^2.0.0
- doctrine/dbal: ^2.12.0 || ^3.0.0 || ^4.0.0
- doctrine/instantiator: ^1.0.0 || ^2.0.0
- doctrine/orm: ^2.14.0 || ^3.0.0
- doctrine/persistence: ^2.1.0 || ^3.0.0
- ergebnis/classy: ^1.6.0
- fakerphp/faker: ^1.20.0
Requires (Dev)
- ext-pdo_sqlite: *
- ergebnis/composer-normalize: ^2.42.0
- ergebnis/license: ^2.4.0
- ergebnis/php-cs-fixer-config: ^6.26.0
- ergebnis/phpstan-rules: ^2.2.0
- ergebnis/phpunit-slow-test-detector: ^2.14.0
- infection/infection: ~0.27.11
- phpstan/extension-installer: ^1.3.1
- phpstan/phpstan: ^1.10.67
- phpstan/phpstan-deprecation-rules: ^1.1.4
- phpstan/phpstan-doctrine: ^1.3.69
- phpstan/phpstan-phpunit: ^1.3.16
- phpstan/phpstan-strict-rules: ^1.5.5
- phpunit/phpunit: ^10.5.20
- psalm/plugin-phpunit: ~0.19.0
- ramsey/uuid: ^4.7.6
- rector/rector: ^1.0.4
- roave/backward-compatibility-check: ^8.6.0
- symfony/cache: ^6.4.7
- vimeo/psalm: ^5.24.0
README
This project provides a composer
package with a fixture factory for doctrine/orm
entities.
Installation
Run
composer require --dev ergebnis/factory-bot
Usage
The entry point of ergebnis/factory-bot
is the FixtureFactory
.
You will use the fixture factory to create entity definitions and to create Doctrine entities populated with fake data.
- Examples
- Creating a fixture factory
- Creating entity definitions
- Loading entity definitions
- Registering entity definitions
- Creating entities
- Persisting entities
- Flushing entities
Examples
You can find examples in
- the directory
example/
- the repository
ergebnis/factory-bot-example
Creating a fixture factory
The fixture factory requires an instance of Doctrine\ORM\EntityManagerInterface
(for reading class metadata from Doctrine entities, and for persisting Doctrine entities when necessary) and an instance of Faker\Generator
for generating fake data.
<?php declare(strict_types=1); use Doctrine\ORM; use Ergebnis\FactoryBot; use Faker\Factory; $entityManager = ORM\EntityManager::create(...); $faker = Factory::create(...); $fixtureFactory = new FactoryBot\FixtureFactory( $entityManager, $faker );
To simplify the creation of a fixture factory in tests, you can create an abstract test case with access to an entity manager, a faker, and a fixture factory.
<?php declare(strict_types=1); namespace App\Test\Functional; use Doctrine\ORM; use Ergebnis\FactoryBot; use Faker\Generator; use PHPUnit\Framework; abstract class AbstractTestCase extends Framework\TestCase { final protected static function entityManager(): ORM\EntityManagerInterface { // create entity manager from configuration or fetch it from container return $entityManager; } final protected static function faker(): Generator { $faker = Factory::create(); $faker->seed(9001); return $faker; } final protected static function fixtureFactory(): FactoryBot\FixtureFactory { $fixtureFactory = new FactoryBot\FixtureFactory( static::entityManager(), static::faker() ); // create or load entity definitions return $fixtureFactory; } }
Creating entity definitions
Now that you have access to a fixture factory, you can create definitions for Doctrine entities.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class);
This simple definition might work when all entity fields have default values, but typically, you will want to provide a map of entity field names to field definitions.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'avatar' => FactoryBot\FieldDefinition::reference(Entity\Avatar::class), 'id' => FactoryBot\FieldDefinition::closure(static function (Generator $faker): string { return $faker->uuid; }), 'location' => FactoryBot\FieldDefinition::optionalClosure(static function (Generator $faker): string { return $faker->city; }), 'login' => FactoryBot\FieldDefinition::closure(static function (Generator $faker): string { return $faker->userName; }), ]);
In addition to the map of field names to field definitions, you can specify a closure that the fixture factory will invoke after creating the entity. The closure accepts the freshly created entity and the map of field names to field values that the fixture factory used to populate the entity.
<?php declare(strict_types=1); $closure = static function (object $entity, array $fieldValues): void { // ... };
π‘ You can use the closure to modify the freshly created entity.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define( Entity\User::class, [ 'avatar' => FactoryBot\FieldDefinition::reference(Entity\Avatar::class), 'id' => FactoryBot\FieldDefinition::closure(static function (Generator $faker): string { return $faker->uuid; }), 'location' => FactoryBot\FieldDefinition::optionalClosure(static function (Generator $faker): string { return $faker->city; }), 'login' => FactoryBot\FieldDefinition::closure(static function (Generator $faker): string { return $faker->userName; }), ], static function (Entity\User $user, array $fieldValues): void { if (is_string($fieldValues['location')) { // ... } } );
Field Definitions
A field definition can be
- an implementation of
FieldDefinition\Resolvable
- a closure (will be normalized to
FieldDefinition\Closure
) - an arbitrary value (will be normalized to
FieldDefinition\Value
)
You can use the FieldDefinition
factory to create field definitions shipped with this package or implement the FieldDefinition\Resolvable
interface yourself.
π‘ Custom field definitions can be useful when you are dealing with identical field definitions over and over again.
Non-nullable fields
When you are working with non-nullable fields, you can use the following field definitions, all of which will resolve to concrete references or values:
FieldDefinition::closure()
FieldDefinition::reference()
FieldDefinition::references()
FieldDefinition::sequence()
FieldDefinition::value()
Nullable fields
When you are working with nullable fields, you can use the following field definitions, all of which will either resolve to null
or to a concrete reference or value (depending on the strategy:
FieldDefinition::optionalClosure()
FieldDefinition::optionalReference()
FieldDefinition::optionalSequence()
FieldDefinition::optionalValue()
FieldDefinition::closure()
FieldDefinition::closure()
accepts a closure.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Faker\Generator; $closure = static function (Generator $faker, FactoryBot\FixtureFactory $fixtureFactory) { // return whatever makes sense };
The fixture factory will resolve the field definition to the return value of invoking the closure with the instance of Faker\Generator
composed into the fixture factory, and the fixture factory itself.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'id' => FactoryBot\FieldDefinition::closure(static function (Generator $faker): string { return $faker->uuid; }), 'organizations' => FactoryBot\FieldDefinition::closure(static function (Generator $faker, FactoryBot\FixtureFactory $fixtureFactory): array { return $fixtureFactory->createMany( Entity\Organization::class, FactoryBot\Count::exact($faker->numberBetween( 1, 5 )) ); }), ]); /** @var Entity\User $user */ $user = $fixtureFactory->createOne(Entity\User::class); var_dump($user->id()); // string var_dump($user->organizations()); // array with 1-5 instances of Entity\Organization
π‘ It is possible to specify a closure only (will be normalized to FieldDefinition\Closure
):
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'id' => static function (Generator $faker): string { return $faker->uuid; }, 'organizations' => static function (Generator $faker, FactoryBot\FixtureFactory $fixtureFactory): array { return $fixtureFactory->createMany( Entity\Organization::class, FactoryBot\Count::exact($faker->numberBetween( 1, 5 )) ); }, ]); /** @var Entity\User $user */ $user = $fixtureFactory->createOne(Entity\User::class); var_dump($user->id()); // string var_dump($user->organizations()); // array with 1-5 instances of Entity\Organization
FieldDefinition::optionalClosure()
FieldDefinition::optionalClosure()
accepts a closure.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Faker\Generator; $closure = static function (Generator $faker, FactoryBot\FixtureFactory $fixtureFactory) { // return whatever makes sense };
A fixture factory using the Strategy\DefaultStrategy
will resolve the field definition to null
or to the return value of invoking the closure with the instance of Faker\Generator
composed into the fixture factory.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalClosure(static function (Generator $faker): string { return $faker->city; }), ]); /** @var Entity\User $user */ $user = $fixtureFactory->createOne(Entity\User::class); var_dump($user->location()); // null or a random city
A fixture factory using the Strategy\WithOptionalStrategy
will resolve the field definition to the return value of invoking the closure with the instance of Faker\Generator
composed into the fixture factory.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalClosure(static function (Generator $faker): string { return $faker->city; }), ]); $withOptionalFixtureFactory = $fixtureFactory->withOptional(); /** @var Entity\User $user */ $user = $withOptionalFixtureFactory->createOne(Entity\User::class); var_dump($user->location()); // a random city
A fixture factory using the Strategy\WithoutOptionalStrategy
will resolve the field definition to null
.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalClosure(static function (Generator $faker): string { return $faker->city; }), ]); $withoutOptionalFixtureFactory = $fixtureFactory->withoutOptional(); /** @var Entity\User $user */ $user = $withoutOptionalFixtureFactory->createOne(Entity\User::class); var_dump($user->location()); // null
FieldDefinition::reference()
FieldDefinition::reference()
accepts the class name of an entity or embeddable, and optionally an array of field definition overrides.
Every fixture factory will resolve the field definition to an instance of the entity or embeddable class populated through the fixture factory.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'avatar' => FactoryBot\FieldDefinition::reference(Entity\Avatar::class), ]); /** @var Entity\User $user */ $user = $fixtureFactory->createOne(Entity\User::class); var_dump($user->avatar()); // an instance of Entity\Avatar
When field definition overrides are specified, they will be used to override exsting field definitions of the referenced entity.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'avatar' => FactoryBot\FieldDefinition::reference( Entity\Avatar::class, [ 'height' => 300, 'width' => 200, ] ), ]); /** @var Entity\User $user */ $user = $fixtureFactory->createOne(Entity\User::class); var_dump($user->avatar()); // an instance of Entity\Avatar with height set to 300 and width set to 200
β When resolving the reference, the fixture factory needs to be aware of the referenced entity or embeddable.
FieldDefinition::optionalReference()
FieldDefinition::optionalReference()
accepts the class name of an entity or embeddable, and optionally an array of field definition overrides.
A fixture factory using the Strategy\DefaultStrategy
will resolve the field definition to null
or an instance of the entity or embeddable class populated through the fixture factory.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\Repository::class, [ 'template' => FactoryBot\FieldDefinition::optionalReference(Entity\Repository::class), ]); /** @var Entity\Repository $repository */ $repository = $fixtureFactory->createOne(Entity\Repository::class); var_dump($repository->template()); // null or an instance of Entity\Repository
A fixture factory using the Strategy\WithOptionalStrategy
will resolve the field definition to an instance of the entity or embeddable class populated through the fixture factory.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\Repository::class, [ 'template' => FactoryBot\FieldDefinition::optionalReference(Entity\Repository::class), ]); $withOptionalFixtureFactory = $fixtureFactory->withOptional(); /** @var Entity\Repository $repository */ $repository = $withOptionalFixtureFactory->createOne(Entity\Repository::class); var_dump($repository->template()); // an instance of Entity\Repository
A fixture factory using the Strategy\WithoutOptionalStrategy
will resolve the field definition to null
.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\Repository::class, [ 'template' => FactoryBot\FieldDefinition::optionalReference(Entity\Repository::class), ]); $withoutOptionalFixtureFactory = $fixtureFactory->withoutOptional(); /** @var Entity\Repository $repository */ $repository = $withoutOptionalFixtureFactory->createOne(Entity\Repository::class); var_dump($repository->template()); // null
β When resolving the reference, the fixture factory needs to be aware of the referenced entity or embeddable.
FieldDefinition::references()
FieldDefinition::references()
accepts the class name of an entity or embeddable, the count of desired references, and optionally an array of field definition overrides.
You can create the count from an exact number, or minimum and maximum values.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; $count = FactoryBot\Count::exact(5); $otherCount = FactoryBot\Count::between( 0, 20 );
π‘ When you create the count from minimum and maximum values, the fixture factory will resolve its actual value before creating references. This way, you can have variation in the number of references - any number between the minimum and maximum can be assumed.
A fixture factory using the Strategy\DefaultStrategy
will resolve the field definition to an array with zero or more instances of the entity or embeddable class populated through the fixture factory. Depending on the value of $count
, the array might be empty.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\Organization::class, [ 'members' => FactoryBot\FieldDefinition::references( Entity\User::class, FactoryBot\Count::exact(5) ), 'repositories' => FactoryBot\FieldDefinition::references( Entity\Repository::class, FactoryBot\Count::between(0, 20), [ 'codeOfConduct' => null, ] ), ]); /** @var Entity\Organization $organization */ $organization = $fixtureFactory->createOne(Entity\Organization::class); var_dump($organization->members()); // array with 5 instances of Entity\User var_dump($organization->repositories()); // array with 0-20 instances of Entity\Repository
A fixture factory using the Strategy\WithOptionalStrategy
will resolve the field definition to an array containing at least one instance of the entity or embeddable class populated through the fixture factory, unless $count
uses an exact value, see FixtureFactory::createMany()
.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\Organization::class, [ 'members' => FactoryBot\FieldDefinition::references( Entity\User::class, FactoryBot\Count::exact(5) ), 'repositories' => FactoryBot\FieldDefinition::references( Entity\Repository::class, FactoryBot\Count::between(0, 20) ), ]); $withOptionalFixtureFactory = $fixtureFactory->withOptional(); /** @var Entity\Organization $organization */ $organization = $withOptionalFixtureFactory->createOne(Entity\Organization::class); var_dump($organization->members()); // array with 5 instances of Entity\User var_dump($organization->repositories()); // array with 1-20 instances of Entity\Repository
A fixture factory using the Strategy\WithoutOptionalStrategy
will resolve the field definition to an empty array, unless $count
uses an exact value, see FixtureFactory::createMany()
.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\Organization::class, [ 'members' => FactoryBot\FieldDefinition::references( Entity\User::class, FactoryBot\Count::exact(5) ), 'repositories' => FactoryBot\FieldDefinition::references( Entity\Repository::class, FactoryBot\Count::between(0, 20) ), ]); $withoutOptionalFixtureFactory = $fixtureFactory->withoutOptional(); /** @var Entity\Organization $organization */ $organization = $withoutOptionalFixtureFactory->createOne(Entity\Organization::class); var_dump($organization->members()); // array with 5 instances of Entity\User var_dump($organization->repositories()); // empty array
β When resolving the references, the fixture factory needs to be aware of the referenced entity or embeddable.
FieldDefinition::sequence()
FieldDefinition::sequence()
accepts a string containing the %d
placeholder at least once and an optional initial number (defaults to 1
).
Every fixture factory will resolve the field definition by replacing all occurrences of the placeholder %d
in the string with the sequential number's current value. The sequential number will then be incremented by 1
for the next run.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'login' => FactoryBot\FieldDefinition::sequence( 'user-%d', 1 ), ]); /** @var Entity\User $userOne */ $userOne = $fixtureFactory->createOne(Entity\User::class); /** @var Entity\User $userTwo */ $userTwo = $fixtureFactory->createOne(Entity\User::class); var_dump($userOne->login()); // 'user-1' var_dump($userTwo->login()); // 'user-2'
FieldDefinition::optionalSequence()
FieldDefinition::optionalSequence()
accepts a string containing the %d
placeholder at least once and an optional initial number (defaults to 1
).
A fixture factory using the Strategy\DefaultStrategy
will resolve the field definition to null
or by replacing all occurrences of the placeholder %d
in the string with the sequential number's current value. The sequential number will then be incremented by 1
for the next run.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalSequence( 'City %d', 1 ), ]); /** @var Entity\User $userOne */ $userOne = $fixtureFactory->createOne(Entity\User::class); /** @var Entity\User $userTwo */ $userTwo = $fixtureFactory->createOne(Entity\User::class); var_dump($userOne->location()); // null or 'City 1' var_dump($userTwo->location()); // null or 'City 1' or 'City 2'
A fixture factory using the Strategy\WithOptionalStrategy
will resolve the field definition by replacing all occurrences of the placeholder %d
in the string with the sequential number's current value. The sequential number will then be incremented by 1
for the next run.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalSequence( 'City %d', 1 ), ]); $withOptionalFixtureFactory = $fixtureFactory->withOptional(); /** @var Entity\User $userOne */ $userOne = $withOptionalFixtureFactory->createOne(Entity\User::class); /** @var Entity\User $userTwo */ $userTwo = $withOptionalFixtureFactory->createOne(Entity\User::class); var_dump($userOne->location()); // 'City 1' var_dump($userTwo->location()); // 'City 2'
A fixture factory using the Strategy\WithoutOptionalStrategy
will resolve the field definition to null
.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalSequence( 'City %d', 1 ), ]); $withOptionalFixtureFactory = $fixtureFactory->withOptional(); /** @var Entity\User $userOne */ $userOne = $withOptionalFixtureFactory->createOne(Entity\User::class); /** @var Entity\User $userTwo */ $userTwo = $withOptionalFixtureFactory->createOne(Entity\User::class); var_dump($userOne->location()); // null var_dump($userTwo->location()); // null
FieldDefinition::value()
FieldDefinition::value()
accepts an arbitrary value.
The fixture factory will resolve the field definition to the value.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'login' => FactoryBot\FieldDefinition::value('localheinz'), ]); /** @var Entity\User $user */ $user = $fixtureFactory->createOne(Entity\User::class); var_dump($user->login()); // 'localheinz'
π‘ It is also possible to specify a value only:
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'login' => 'localheinz', ]); /** @var Entity\User $user */ $user = $fixtureFactory->createOne(Entity\User::class); var_dump($user->login()); // 'localheinz'
FieldDefinition::optionalValue()
FieldDefinition::optionalValue()
accepts an arbitrary value.
A fixture factory using the Strategy\DefaultStrategy
will resolve the field definition to null
or the value.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalValue('Berlin'), ]); /** @var Entity\User $user */ $user = $fixtureFactory->create(Entity\User::class); var_dump($user->location()); // null or 'Berlin'
A fixture factory using the Strategy\WithOptionalStrategy
will resolve the field definition to the value.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalValue('Berlin'), ]); $withOptionalFixtureFactory = $fixtureFactory->withOptional(); /** @var Entity\User $user */ $user = $withOptionalFixtureFactory->create(Entity\User::class); var_dump($user->location()); // 'Berlin'
A fixture factory using the Strategy\WithoutOptionalStrategy
will resolve the field definition to null
.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'location' => FactoryBot\FieldDefinition::optionalValue('Berlin'), ]); $withoutOptionalFixtureFactory = $fixtureFactory->withoutOptional(); /** @var Entity\User $user */ $user = $withoutOptionalFixtureFactory->create(Entity\User::class); var_dump($user->location()); // null
Loading entity definitions
Instead of creating entity definitions inline, you can implement the EntityDefinitionProvider
interface and load entity definitions contained within a directory with the fixture factory.
First, create concrete definition providers.
<?php declare(strict_types=1); namespace Example\Test\Fixture\Entity; use Ergebnis\FactoryBot; use Example\Entity; final class UserDefinitionProvider implements FactoryBot\EntityDefinitionProvider { public function accept(FactoryBot\FixtureFactory $fixtureFactory): void { $fixtureFactory->define(Entity\User::class, [ // ... ]); } }
π‘ While you can use a single entity definition provider to provide definitions for all entities, I recommend using one definition provider per entity. Then you can quickly implement an auto-review test to enforce that an entity definition provider exists for each entity.
Second, adjust your abstract test case to load definitions from entity definition providers contained in a directory.
<?php declare(strict_types=1); namespace App\Test\Functional; use Ergebnis\FactoryBot; use PHPUnit\Framework; abstract class AbstractTestCase extends Framework\TestCase { // ... final protected static function fixtureFactory(): FactoryBot\FixtureFactory { $fixtureFactory = new FactoryBot\FixtureFactory( static::entityManager(), static::faker() ); $fixtureFactory->load(__DIR__ . '/../Fixture'); return $fixtureFactory; } // ... }
Registering entity definitions
Instead of loading entity definition providers contained within a directory with the fixture factory, you can also register entity definition providers that you have already instantiated.
<?php declare(strict_types=1); namespace App\Test\Functional; use Ergebnis\FactoryBot; use Example\Test\Fixture; use PHPUnit\Framework; abstract class AbstractTestCase extends Framework\TestCase { // ... final protected static function fixtureFactory(): FactoryBot\FixtureFactory { $fixtureFactory = new FactoryBot\FixtureFactory( static::entityManager(), static::faker() ); $fixtureFactory->register(new Fixture\UserDefinitionProvider()); return $fixtureFactory; } // ... }
Creating entities
Now that you have created (or loaded) entity definitions, you can create Doctrine entities populated with fake data.
The fixture factory allows to create entities using the following strategies:
Strategy\DefaultStrategy
Strategy\WithOptionalStrategy
Strategy\WithoutOptionalStrategy
Strategy\DefaultStrategy
The Strategy\DefaultStrategy
involves random behavior, and based on randomness, the fixture factory might or might not resolve optional field references:
FieldDefinition::optionalClosure()
might be resolved tonull
or a concrete valueFieldDefinition::optionalReference()
might be resolved tonull
or a concrete referenceFieldDefinition::optionalSequence()
might be resolved tonull
or a concrete valueFieldDefinition::optionalValue()
might be resolved tonull
or a concrete valueFieldDefinition::references()
might be resolved to anarray
of zero or more references
The fixture factory uses the Strategy\DefaultStrategy
by default.
Strategy\WithOptionalStrategy
The Strategy\WithOptionalStrategy
involves random behavior, but the fixture factory will resolve optional field references:
FieldDefinition::optionalClosure()
will be resolved to a concrete valueFieldDefinition::optionalReference()
will be resolved to a concrete referenceFieldDefinition::optionalSequence()
will be resolved to a concrete valueFieldDefinition::optionalValue()
will be resolved to a concrete valueFieldDefinition::references()
will be resolved to anarray
containing at least one reference, unless$count
uses an exact value, seeFixtureFactory::createMany()
To create a fixture factory using the Strategy\WithOptionalStrategy
out of an available fixture factory, invoke withOptional()
:
<?php declare(strict_types=1); use Ergebnis\FactoryBot; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $withOptionalFixtureFactory = $fixtureFactory->withOptional();
Strategy\WithoutOptionalStrategy
The Strategy\WithoutOptionalStrategy
involves random behavior, but the fixture factory will not resolve optional field references:
FieldDefinition::optionalClosure()
will be resolved tonull
FieldDefinition::optionalReference()
will be resolved tonull
FieldDefinition::optionalSequence()
will be resolved tonull
FieldDefinition::optionalValue()
will be resolved tonull
FieldDefinition::references()
will be resolved to an emptyarray
, unless$count
uses an exact value, seeFixtureFactory::createMany()
To create a fixture factory using the Strategy\WithoutOptionalStrategy
out of an available fixture factory, invoke withoutOptional()
:
<?php declare(strict_types=1); use Ergebnis\FactoryBot; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $withoutOptionalFixtureFactory = $fixtureFactory->withoutOptional();
FixtureFactory::createOne()
FixtureFactory::createOne()
accepts the class name of an entity and optionally, a map of entity field names to field definitions that should override the field definitions for that specific entity.
The fixture factory will return a single entity.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'login' => FactoryBot\FieldDefinition::closure(static function (Generator $faker): string { return $faker->userName; }), ]); /** @var Entity\User $userOne */ $userOne = $fixtureFactory->createOne(Entity\User::class); /** @var Entity\User $userTwo */ $userTwo = $fixtureFactory->createOne(Entity\User::class, [ 'login' => FactoryBot\FieldDefinition::value('localheinz'), ]); /** @var Entity\User $userThree */ $userThree = $fixtureFactory->createOne(Entity\User::class, [ 'login' => 'ergebnis-bot', ]); var_dump($userOne->login()); // random user name var_dump($userTwo->login()); // 'localheinz' var_dump($userThree->login()); // 'ergebnis-bot'
A field definition override can be
- an implementation of
FieldDefinition\Resolvable
- a closure (will be normalized to
FieldDefinition\Closure
) - an arbitrary value (will be normalized to
FieldDefinition\Value
)
Also see Creating entity definitions.
FixtureFactory::createMany()
FixtureFactory::createMany()
accepts the class name of an entity, the count of desired entities, and an optional map of entity field names to field definitions that should override the field definitions for that specific entity.
You can create the count from an exact number:
<?php declare(strict_types=1); use Ergebnis\FactoryBot; $count = FactoryBot\Count::exact(5);
The fixture factory will resolve $count
to 5
.
You can also create the count from minimum and maximum values.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; $count = FactoryBot\Count::between( 0, 20 );
The fixture factory will resolve $count
to any number between 0
and 20
.
The fixture factory will return an array of entities.
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; use Faker\Generator; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class, [ 'login' => FieldDefinition\Closure(static function (Generator $faker): string { return $faker->username; }), ]); /** @var array<Entity\User> $users */ $users = $fixtureFactory->createMany( Entity\User::class, FactoryBot\Count::exact(5) ); /** @var array<Entity\User> $otherUsers */ $otherUsers = $fixtureFactory->createMany( Entity\User::class, FactoryBot\Count::exact(5), [ 'login' => FactoryBot\FieldDefinition::sequence('user-%d'), ] ); $normalize = static function (array $users): array { return array_map(static function (Entity\User $user): string { return $user->login(); }, $users); }; var_dump($normalize($users)); // random user names var_dump($normalize($otherUsers)); // 'user-1', 'user-2', ...
A field definition override can be
- an implementation of
FieldDefinition\Resolvable
- a closure (will be normalized to
FieldDefinition\Closure
) - an arbitrary value (will be normalized to
FieldDefinition\Value
)
Also see Creating entity definitions.
Persisting entities
When the fixture factory creates entities, the fixture factory does not persist them by default.
To create a fixture factory that persists entities out of an available fixture factory, invoke persisting()
:
<?php declare(strict_types=1); use Ergebnis\FactoryBot; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $persistingFixtureFactory = $fixtureFactory->persisting();
After this point, the fixture factory will automatically persist every entity it creates.
β You need to flush the entity manager yourself.
Flushing entities
The fixture factory will not flush the entity manager - you need to flush it yourself.
Changelog
The maintainers of this project record notable changes to this project in a changelog.
Contributing
The maintainers of this project suggest following the contribution guide.
Code of Conduct
The maintainers of this project ask contributors to follow the code of conduct.
General Support Policy
The maintainers of this project provide limited support.
You can support the maintenance of this project by sponsoring @localheinz or requesting an invoice for services related to this project.
PHP Version Support Policy
This project supports PHP versions with active and security support.
The maintainers of this project add support for a PHP version following its initial release and drop support for a PHP version when it has reached the end of security support.
Security Policy
This project has a security policy.
License
This project uses the MIT license.
Credits
This project is based on breerly/factory-girl-php@0e6f1b6
(originally licensed under MIT by Grayson Koonce), which is based on xi/doctrine
(originally licensed under MIT by Xi), which in turn provided a port of factory_bot
(originally licensed under MIT by Joe Ferris and thoughtbot, Inc.).
Social
Follow @localheinz and @ergebnis on Twitter.