ergebnis / factory-bot
为 doctrine/orm 实体提供固定工厂。
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
本项目提供了一个composer
包,用于为doctrine/orm
实体提供固定工厂。
安装
运行
composer require --dev ergebnis/factory-bot
用法
ergebnis/factory-bot
的入口点是FixtureFactory
。
您将使用固定工厂来创建实体定义,并创建包含假数据的Doctrine实体。
示例
示例可以在以下位置找到
创建固定工厂
固定工厂需要Doctrine\ORM\EntityManagerInterface
实例(用于从Doctrine实体读取类元数据,并在必要时持久化Doctrine实体),以及一个Faker\Generator
实例用于生成假数据。
<?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 );
为了简化测试中固定工厂的创建,您可以创建一个具有对实体管理器、faker和固定工厂访问的抽象测试用例。
<?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; } }
创建实体定义
现在您有了对固定工厂的访问,您可以创建Doctrine实体的定义。
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Example\Entity; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $fixtureFactory->define(Entity\User::class);
当所有实体字段都具有默认值时,这个简单的定义可能有效,但通常,您需要提供一个实体字段名到字段定义的映射。
<?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; }), ]);
除了字段名到字段定义的映射之外,您还可以指定一个闭包,固定工厂将在创建实体后调用它。闭包接受新创建的实体以及固定工厂用于填充实体的字段名到字段值的映射。
<?php declare(strict_types=1); $closure = static function (object $entity, array $fieldValues): void { // ... };
💡您可以使用闭包来修改新创建的实体。
<?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')) { // ... } } );
字段定义
字段定义可以是
FieldDefinition\Resolvable
的实现- 一个闭包(将被标准化为
FieldDefinition\Closure
) - 任意值(将被标准化为
FieldDefinition\Value
)
您可以使用FieldDefinition
工厂来创建此包中提供的字段定义,或自己实现FieldDefinition\Resolvable
接口。
💡自定义字段定义在处理重复的字段定义时非常有用。
不可为空的字段
当您使用不可为空的字段时,可以使用以下字段定义,所有这些都将解析为具体的引用或值
FieldDefinition::closure()
FieldDefinition::reference()
FieldDefinition::references()
FieldDefinition::sequence()
FieldDefinition::value()
可为空的字段
当您处理可空字段时,可以使用以下字段定义,它们都将解析为null
或具体引用或值(具体取决于策略)。
FieldDefinition::optionalClosure()
FieldDefinition::optionalReference()
FieldDefinition::optionalSequence()
FieldDefinition::optionalValue()
FieldDefinition::closure()
FieldDefinition::closure()
接受一个闭包。
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Faker\Generator; $closure = static function (Generator $faker, FactoryBot\FixtureFactory $fixtureFactory) { // return whatever makes sense };
固定工厂将解析字段定义,将其转换为通过固定工厂组合到其中的Faker\Generator
实例的返回值以及固定工厂本身调用的闭包的返回值。
<?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
💡您可以只指定一个闭包(将被标准化为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()
接受一个闭包。
<?php declare(strict_types=1); use Ergebnis\FactoryBot; use Faker\Generator; $closure = static function (Generator $faker, FactoryBot\FixtureFactory $fixtureFactory) { // return whatever makes sense };
使用Strategy\DefaultStrategy
的固定工厂将字段定义解析为null
或通过固定工厂组合到其中的Faker\Generator
实例调用的闭包的返回值。
<?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
使用Strategy\WithOptionalStrategy
的固定工厂将字段定义解析为通过固定工厂组合到其中的Faker\Generator
实例调用的闭包的返回值。
<?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
使用Strategy\WithoutOptionalStrategy
的固定工厂将字段定义解析为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()
接受实体或可嵌入类的类名,以及可选的字段定义覆盖数组。
每个固定工厂都将字段定义解析为实体或可嵌入类的一个实例,该实例通过固定工厂填充。
<?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
当指定字段定义覆盖时,它们将用来覆盖所引用实体的现有字段定义。
<?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
❗在解析引用时,固定工厂需要知道所引用的实体或可嵌入类。
FieldDefinition::optionalReference()
FieldDefinition::optionalReference()
接受实体或可嵌入类的类名,以及可选的字段定义覆盖数组。
使用Strategy\DefaultStrategy
的固定工厂将字段定义解析为null
或通过固定工厂组合到其中的Faker\Generator
实例填充的实体或可嵌入类的一个实例。
<?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
使用Strategy\WithOptionalStrategy
的固定工厂将字段定义解析为通过固定工厂组合到其中的Faker\Generator
实例填充的实体或可嵌入类的一个实例。
<?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
使用Strategy\WithoutOptionalStrategy
的固定工厂将字段定义解析为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
❗在解析引用时,固定工厂需要知道所引用的实体或可嵌入类。
FieldDefinition::references()
FieldDefinition::references()
接受实体或可嵌入类的类名,所需引用的数量,以及可选的字段定义覆盖数组。
您可以从一个确切数字或最小和最大值创建计数。
<?php declare(strict_types=1); use Ergebnis\FactoryBot; $count = FactoryBot\Count::exact(5); $otherCount = FactoryBot\Count::between( 0, 20 );
💡当您从最小和最大值创建计数时,固定工厂将在创建引用之前解析其实际值。这样,您可以有引用数量的变化 - 任何在最小和最大值之间的数字都可以假设。
使用Strategy\DefaultStrategy
的固定工厂将字段定义解析为一个包含零个或多个通过固定工厂填充的实体或可嵌入类实例的数组。根据$count
的值,该数组可能为空。
<?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
使用 Strategy\WithOptionalStrategy
的固定工厂会将字段定义解析为一个包含至少一个通过固定工厂填充的实体或嵌入式类的实例数组的集合,除非 $count
使用了精确值,请参阅 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
使用 Strategy\WithoutOptionalStrategy
的固定工厂会将字段定义解析为一个空数组,除非 $count
使用了精确值,请参阅 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
❗ 在解析引用时,固定工厂需要了解被引用的实体或嵌入式类。
FieldDefinition::sequence()
FieldDefinition::sequence()
接受一个包含至少一次 %d
占位符的字符串,以及一个可选的初始数字(默认为 1
)。
每个固定工厂都将通过将字符串中所有 %d
占位符替换为当前序列号值来解析字段定义。然后序列号将增加 1
以供下一次运行使用。
<?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()
接受一个包含至少一次 %d
占位符的字符串,以及一个可选的初始数字(默认为 1
)。
使用 Strategy\DefaultStrategy
的固定工厂会将字段定义解析为 null
或通过将字符串中所有 %d
占位符替换为当前序列号值。然后序列号将增加 1
以供下一次运行使用。
<?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'
使用 Strategy\WithOptionalStrategy
的固定工厂会将字段定义替换为字符串中所有 %d
占位符的当前序列号值。然后序列号将增加 1
以供下一次运行使用。
<?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'
使用Strategy\WithoutOptionalStrategy
的固定工厂将字段定义解析为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()
接受一个任意值。
固定工厂会将字段定义解析为该值。
<?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'
💡 也可以只指定一个值
<?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()
接受一个任意值。
使用 Strategy\DefaultStrategy
的固定工厂会将字段定义解析为 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'), ]); /** @var Entity\User $user */ $user = $fixtureFactory->create(Entity\User::class); var_dump($user->location()); // null or 'Berlin'
使用 Strategy\WithOptionalStrategy
的固定工厂会将字段定义解析为该值。
<?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'
使用Strategy\WithoutOptionalStrategy
的固定工厂将字段定义解析为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
加载实体定义
您可以直接创建实体定义,也可以实现 EntityDefinitionProvider
接口,并使用固定工厂从目录中加载实体定义。
首先,创建具体的定义提供者。
<?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, [ // ... ]); } }
💡 虽然您可以使用单个实体定义提供者为所有实体提供定义,但我建议为每个实体使用一个定义提供者。然后您可以快速实现一个 自动审查测试 以强制每个实体都有一个实体定义提供者。
其次,调整您的抽象测试用例,以从目录中加载实体定义提供者的定义。
<?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; } // ... }
注册实体定义
您也可以注册已经实例化的实体定义提供者,而不是使用固定工厂从目录中加载实体定义提供者。
<?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; } // ... }
创建实体
现在您已经创建(或加载)了实体定义,您可以使用假数据创建 Doctrine 实体。
固定工厂允许使用以下策略创建实体
Strategy\DefaultStrategy
Strategy\WithOptionalStrategy
Strategy\WithoutOptionalStrategy
Strategy\DefaultStrategy
Strategy\DefaultStrategy
涉及随机行为,基于随机性,固定工厂可能会也可能不会解析可选字段引用
FieldDefinition::optionalClosure()
可能解析为null
或一个具体值FieldDefinition::optionalReference()
可能解析为null
或一个具体引用FieldDefinition::optionalSequence()
可能解析为null
或一个具体值FieldDefinition::optionalValue()
可能解析为null
或一个具体值FieldDefinition::references()
可能解析为零个或多个引用的数组
固定工厂默认使用 Strategy\DefaultStrategy
。
Strategy\WithOptionalStrategy
Strategy\WithOptionalStrategy
涉及随机行为,但固定工厂将解析可选字段引用
FieldDefinition::optionalClosure()
将解析为一个具体值FieldDefinition::optionalReference()
将解析为一个具体引用FieldDefinition::optionalSequence()
将解析为一个具体值FieldDefinition::optionalValue()
将解析为一个具体值FieldDefinition::references()
将解析为一个包含至少一个引用的数组,除非$count
使用一个确切值,请参阅FixtureFactory::createMany()
要从现有的固定工厂创建使用 Strategy\WithOptionalStrategy
的固定工厂,调用 withOptional()
<?php declare(strict_types=1); use Ergebnis\FactoryBot; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $withOptionalFixtureFactory = $fixtureFactory->withOptional();
Strategy\WithoutOptionalStrategy
Strategy\WithoutOptionalStrategy
涉及随机行为,但固定工厂将不会解析可选字段引用
FieldDefinition::optionalClosure()
将解析为null
FieldDefinition::optionalReference()
将解析为null
FieldDefinition::optionalSequence()
将解析为null
FieldDefinition::optionalValue()
将解析为null
FieldDefinition::references()
将解析为一个空数组,除非$count
使用一个确切值,请参阅FixtureFactory::createMany()
要从现有的固定工厂创建使用 Strategy\WithoutOptionalStrategy
的固定工厂,调用 withoutOptional()
<?php declare(strict_types=1); use Ergebnis\FactoryBot; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $withoutOptionalFixtureFactory = $fixtureFactory->withoutOptional();
FixtureFactory::createOne()
FixtureFactory::createOne()
接受一个实体的类名,可选地,一个实体字段名到字段定义的映射,该映射将覆盖特定实体的字段定义。
固定工厂将返回一个实体。
<?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'
字段定义覆盖可以是
FieldDefinition\Resolvable
的实现- 一个闭包(将规范化为
FieldDefinition\Closure
) - 一个任意值(将规范化为
FieldDefinition\Value
)
另请参阅创建实体定义。
FixtureFactory::createMany()
FixtureFactory::createMany()
接受实体类名、所需实体的数量以及一个可选的映射,将实体字段名称映射到应覆盖该特定实体字段定义的字段定义。
您可以创建一个确切的数字数量。
<?php declare(strict_types=1); use Ergebnis\FactoryBot; $count = FactoryBot\Count::exact(5);
固定工厂将 $count
解析为 5
。
您还可以从最小值和最大值创建数量。
<?php declare(strict_types=1); use Ergebnis\FactoryBot; $count = FactoryBot\Count::between( 0, 20 );
固定工厂将 $count
解析为介于 0
和 20
之间的任何数字。
固定工厂将返回一个实体数组。
<?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', ...
字段定义覆盖可以是
FieldDefinition\Resolvable
的实现- 一个闭包(将规范化为
FieldDefinition\Closure
) - 一个任意值(将规范化为
FieldDefinition\Value
)
另请参阅创建实体定义。
持久化实体
当固定工厂创建实体时,默认情况下不会持久化它们。
要创建一个从可用固定工厂持久化实体的固定工厂,请调用 persisting()
<?php declare(strict_types=1); use Ergebnis\FactoryBot; /** @var FactoryBot\FixtureFactory $fixtureFactory */ $persistingFixtureFactory = $fixtureFactory->persisting();
从这一点开始,固定工厂将自动持久化它创建的每个实体。
❗ 您需要自己刷新实体管理器。
刷新实体
固定工厂不会刷新实体管理器 - 您需要自己刷新它。
变更日志
本项目的维护者将此项目的显著更改记录在变更日志中。
贡献
本项目的维护者建议遵循贡献指南。
行为准则
本项目的维护者要求贡献者遵守行为准则。
一般支持策略
本项目的维护者提供有限的支持。
您可以通过赞助 @localheinz 或请求与本项目相关的服务发票来支持本项目的维护。
PHP 版本支持策略
本项目支持具有活跃和安全支持的 PHP 版本。
本项目在 PHP 版本最初发布后添加对该 PHP 版本的支持,并在该 PHP 版本达到安全支持结束时停止支持。
安全策略
本项目有一个安全策略。
许可协议
本项目使用MIT 许可协议。
致谢
本项目基于 breerly/factory-girl-php@0e6f1b6
(最初由 Grayson Koonce 根据 MIT 许可协议授权),它基于 xi/doctrine
(最初由 Xi 根据 MIT 许可协议授权),它反过来提供了 factory_bot
(最初由 Joe Ferris 和 thoughtbot, Inc. 根据 MIT 许可协议授权)的移植。
社交
关注 Twitter 上的 @localheinz 和 @ergebnis。