ergebnis/factory-bot

为 doctrine/orm 实体提供固定工厂。

1.8.0 2024-05-05 13:26 UTC

README

Integrate Merge Release Renew

Code Coverage Type Coverage

Latest Stable Version Total Downloads Monthly Downloads

本项目提供了一个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工厂来创建此包中提供的字段定义,或自己实现FieldDefinition\Resolvable接口。

💡自定义字段定义在处理重复的字段定义时非常有用。

不可为空的字段

当您使用不可为空的字段时,可以使用以下字段定义,所有这些都将解析为具体的引用或值

可为空的字段

当您处理可空字段时,可以使用以下字段定义,它们都将解析为null或具体引用或值(具体取决于策略)。

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 涉及随机行为,基于随机性,固定工厂可能会也可能不会解析可选字段引用

固定工厂默认使用 Strategy\DefaultStrategy

Strategy\WithOptionalStrategy

Strategy\WithOptionalStrategy 涉及随机行为,但固定工厂将解析可选字段引用

要从现有的固定工厂创建使用 Strategy\WithOptionalStrategy 的固定工厂,调用 withOptional()

<?php

declare(strict_types=1);

use Ergebnis\FactoryBot;

/** @var FactoryBot\FixtureFactory $fixtureFactory */
$withOptionalFixtureFactory = $fixtureFactory->withOptional();

Strategy\WithoutOptionalStrategy

Strategy\WithoutOptionalStrategy 涉及随机行为,但固定工厂将不会解析可选字段引用

要从现有的固定工厂创建使用 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 解析为介于 020 之间的任何数字。

固定工厂将返回一个实体数组。

<?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 Ferristhoughtbot, Inc. 根据 MIT 许可协议授权)的移植。

社交

关注 Twitter 上的 @localheinz@ergebnis