league/factory-muffin

该包的目标是能够快速创建用于测试的对象。

v3.3.0 2020-12-13 18:38 UTC

README

StyleCI Status Build Status Software License Latest Version Total Downloads

该包的目标是能够快速创建用于测试的对象。

它基本上是一个简化用于PHP的 "factory_girl"。

安装

需要PHP 5.4+和Composer

在您的composer.json中,只需将"league/factory-muffin": "^3.3"添加到"require-dev"部分。

{
    "require-dev": {
        "league/factory-muffin": "^3.3"
    }
}

Faker支持由Factory Muffin Faker提供。如果您想启用faker支持,则需要添加"league/factory-muffin-faker": "^2.3"

{
    "require-dev": {
        "league/factory-muffin": "^3.3",
        "league/factory-muffin-faker": "^2.3"
    }
}

升级

现有用户查看升级指南可能会有所帮助。

使用方法

简介

这是Factory Muffin 3.0的使用指南。在本指南中,您将看到“xyz函数可以调用”。您应假设这些函数应该在League\FactoryMuffin\FactoryMuffin的实例上调用;您应自行跟踪此实例,当然,您当然可以拥有多个此类类的实例,以实现最大灵活性。为了简化,许多示例中都包含了$fm变量。当使用loadFactories函数引入文件时,该变量实际上会可用。

模型定义

您可以使用define函数定义模型工厂。您可以像这样调用它:$fm->define('Fully\Qualified\ModelName')->addDefinitions('foo', 'bar'),其中foo是您要在模型上设置的属性名称,而bar描述了您希望如何生成该属性。您还可以一次定义多个属性,如下所示:$fm->define('Fully\Qualified\ModelName')->setDefinitions('foo', 'bar')。请注意,这两个函数都附加到内部属性定义数组,而不是替换它。有关如何工作的更多信息,请参阅生成器部分。

您还可以为模型定义多个不同的模型定义。您可以通过在模型类名前加上您的“组”名称并跟上一个冒号来实现这一点。这将导致您按如下方式定义模型:$fm->define('myGroup:Fully\Qualified\ModelName')->addDefinitions('bar', 'baz')。您不必在这里完全定义模型,因为我们首先会查找不带组前缀的定义,然后应用您的组定义,在需要时覆盖属性定义。请注意,如果您想使用组前缀,必须还创建一个不带组前缀的定义。

我们在测试中为您提供了这样做的一种巧妙方法。PHPUnit提供了一个setupBeforeClass函数。在该函数中,您可以调用$fm->loadFactories(__DIR__ . '/factories');,它将包含factories文件夹中的所有文件。在这些PHP文件中,您可以放置您的定义(调用define函数的所有代码)。

loadFactories函数将递归遍历您指定的路径下所有子文件夹,以查找工厂文件,但会忽略隐藏文件夹(即以.开头的文件夹)。如果指定的工厂目录找不到,它将抛出League\FactoryMuffin\Exceptions\DirectoryNotFoundException异常。

创建/实例化回调

您可以使用setCallback函数可选地指定在模型创建/实例化时执行的回调,例如:$fm->define('MyModel')->setCallback(function ($object, $saved) {})。我们将把您的模型实例作为回调的第一个参数传递,并将一个布尔值作为第二个参数。如果模型正被持久化到数据库(使用了create函数),则布尔值为true;如果模型没有正在被持久化(使用了instance函数),则布尔值为false。我们在底层使用的是isPendingOrSaved函数。请注意,如果您指定了回调并使用了create函数,我们将在执行回调前后尝试将您的模型保存到数据库。

生成器

可调用

可调用生成器可用于您需要更定制化解决方案的情况。从您的闭包或有效的可调用函数返回的任何内容都将被设置为属性。闭包/可调用将以与上述创建/实例化回调相同的参数调用:作为第一个参数的您的模型实例(以提供更多的灵活性来按您的意愿修改它)和一个指示模型是否被持久化到数据库的布尔值。在以下示例中,我们将介绍如何使用闭包或可调用,以及如何使用faker生成属性。

示例1

如您从本例中看到的,使用闭包生成属性的能力非常有用且灵活。我们在这里使用它来根据最初随机生成的5个词长的标题生成一个slug。

$fm->define('MyModel')->setDefinitions([
    'title' => Faker::sentence(5),
    'slug' => function ($object, $saved) {
        $slug = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $object->title);
        $slug = strtolower(trim($slug, '-'));
        $slug = preg_replace("/[\/_|+ -]+/", '-', $slug);

        return $slug;
    },
]);
示例2

这将把foo属性设置为调用MyModel::exampleMethod($object, $saved)返回的内容。

$fm->define('MyModel')->setDefinitions([
    'foo' => 'MyModel::exampleMethod',
]);
示例3

这是一个使用我们的faker包装器设置几个不同属性的简单示例。

use League\FactoryMuffin\Faker\Facade as Faker;

$fm->define('MyModel')->setDefinitions([
    'foo'    => Faker::word(),          // Set the foo attribute to a random word
    'name'   => Faker::firstNameMale(), // Set the name attribute to a random male first name
    'email'  => Faker::email(),         // Set the email attribute to a random email address
    'body'   => Faker::text(),          // Set the body attribute to a random string of text
    'slogan' => Faker::sentence(),      // Set the slogan attribute to a random sentence
]);
示例4

这将把age属性设置为介于20到40之间的随机数。

use League\FactoryMuffin\Faker\Facade as Faker;

$fm->define('MyModel')->setDefinitions([
    'age' => Faker::numberBetween(20, 40),
]);
示例5

这将把name属性设置为随机的女性名字。因为我们首先调用了unique方法,所以该属性应在所有生成的模型之间是唯一的。如果生成很多模型时要小心使用,因为我们可能会用完唯一的项目。此外,请注意,调用Faker::setLocale('whatever')将重置内部唯一列表。

use League\FactoryMuffin\Faker\Facade as Faker;

$fm->define('MyModel')->addDefinition('name', Faker::unique()->firstNameFemale());
示例6

这将把profile_pic属性设置为400x400的随机图片URL。因为我们首先调用了optional方法,所以并非所有生成的模型都会设置图片URL;有时我们会返回null。

use League\FactoryMuffin\Faker\Facade as Faker;

$fm->define('MyModel')->addDefinition('profile_pic', Faker::optional()->imageUrl(400, 400));
更多

请查看faker库本身以查看所有可用方法。在这里的文档中涵盖的太多,它们在文档中涵盖的也太多。请注意,如果您想通过我们的faker类上的公共方法调整底层faker实例,可以这样做。

工厂

工厂生成器对于设置模型之间的关系很有用。工厂生成器将返回您请求生成的模型的模型ID。

示例1

当我们创建一个Foo对象时,我们会发现Bar对象也会被生成并保存,它的ID将被分配给Foo模型的bar_id属性。

$fm->define('Foo')->addDefinition('bar_id', 'factory|Bar');

$fm->define('Bar')->addDefinition('baz', Faker::date('Y-m-d'));

创建和播种

create函数将创建和保存您的模型,并且也会保存使用Factory生成器生成的任何内容。如果您想创建多个实例,请查看seed函数,它在开始时接受一个额外的参数,即在这个过程中要生成的模型数量。seed函数将有效地重复调用create函数。

例如,让我们创建一个用户模型,并将其与多个位置和电子邮件模型关联起来。每个电子邮件模型也会关联多个令牌模型。

$user = $fm->create('User');
$profiles = $fm->seed(5, 'Location', ['user_id' => $user->id]);
$emails = $fm->seed(100, 'Email', ['user_id' => $user->id]);

foreach ($emails as $email) {
    $tokens = $fm->seed(50, 'Token', ['email_id' => $email->id]);
}

您可能会遇到以下异常

  • League\FactoryMuffin\Exceptions\ModelNotFoundException 如果找不到定义的模型类,则会被抛出。
  • League\FactoryMuffin\Exceptions\DefinitionNotFoundException 如果尝试创建模型,但之前没有为其定义模型定义,则会抛出。
  • League\FactoryMuffin\Exceptions\SaveFailedException 如果您的模型上的保存函数返回false,则会抛出。
  • League\FactoryMuffin\Exceptions\SaveMethodNotFoundException 如果您的模型上的保存函数不存在,则会抛出。
  • 在尝试创建或保存模型时,您的模型抛出的任何其他异常。

还有5个其他辅助函数可用

  • 您可以调用 pending 来返回一个等待保存的所有对象的数组。
  • 您可以调用 isPending 并传入一个模型实例来检查是否将保存。
  • 您可以调用 saved 来返回一个包含所有已保存对象的数组。
  • 您可以调用 isSaved 并传入一个模型实例来检查它是否已保存。
  • 您可以调用 isPendingOrSaved 并传入一个模型实例来检查它是否将保存或已保存。

另外,如果您不想使用数据库持久性,仍然可以使用 instance 函数。

删除

您可以使用 deleteSaved 函数删除所有已保存的模型。请注意,您的已保存模型将按保存的相反顺序删除,以确保关系行为正确。

如果有一个或多个模型无法删除,在尝试删除所有已保存模型后,将引发 League\FactoryMuffin\Exceptions\DeletingFailedException。您可以通过 getExceptions 函数访问每个底层异常,该函数将返回一个异常数组。您可能会遇到以下异常

  • League\FactoryMuffin\Exceptions\DeleteFailedException 如果您的模型上的删除函数返回false,则会抛出。
  • League\FactoryMuffin\Exceptions\DeleteMethodNotFoundException 如果您的模型上的删除函数不存在,则会抛出。
  • 在尝试删除模型时,您的模型抛出的任何其他异常。

建议您从PHPUnit的 tearDownAfterClass 函数中调用 deleteSaved 函数,但是,如果您正在使用Laravel的 TestCase 编写测试,则应在调用 parent::tearDown 之前从 tearDown 方法中调用 deleteSaved 函数。此方法将刷新应用程序实例的绑定,Factory Muffin将无法执行其删除操作。此外,这将解绑分配的异常处理程序,您将无法通过绑定解析异常来调试测试,从而模糊了真正的异常。

实际示例

首先,我们需要创建一些定义

# tests/factories/all.php

use League\FactoryMuffin\Faker\Facade as Faker;

$fm->define('Message')->setDefinitions([
    'user_id'      => 'factory|User',
    'subject'      => Faker::sentence(),
    'message'      => Faker::text(),
    'phone_number' => Faker::randomNumber(8),
    'created'      => Faker::date('Ymd h:s'),
    'slug'         => 'Message::makeSlug',
])->setCallback(function ($object, $saved) {
    // we're taking advantage of the callback functionality here
    $object->message .= '!';
});

$fm->define('User')->setDefinitions([
    'username' => Faker::firstNameMale(),
    'email'    => Faker::email(),
    'avatar'   => Faker::imageUrl(400, 600),
    'greeting' => 'RandomGreeting::get',
    'four'     => function() {
        return 2 + 2;
    },
]);

然后您可以在测试中使用这些工厂

# tests/TestUserModel.php

use League\FactoryMuffin\Faker\Facade as Faker;

class TestUserModel extends PHPUnit_Framework_TestCase
{
    protected static $fm;

    public static function setupBeforeClass()
    {
        // create a new factory muffin instance
        static::$fm = new FactoryMuffin();

        // you can customize the save/delete methods
        // new FactoryMuffin(new ModelStore('save', 'delete'));

        // load your model definitions
        static::$fm->loadFactories(__DIR__.'/factories');

        // you can optionally set the faker locale
        Faker::setLocale('en_EN');
    }

    public function testSampleFactory()
    {
        $message = static::$fm->create('Message');
        $this->assertInstanceOf('Message', $message);
        $this->assertInstanceOf('User', $message->user);
    }

    public static function tearDownAfterClass()
    {
        static::$fm->deleteSaved();
    }
}

更多信息

如果您想了解更多信息,以下资源可供您参考

集成

贡献

有关详细信息,请查看我们的 贡献指南

鸣谢

工厂松饼是基于 Zizaco Zizuini 对 "Factory Muff" 的原始工作的扩展,目前由 Graham Campbell 维护。在3.0版本发布之前,Scott Robertson 也是共同维护者。还要感谢我们所有优秀的 贡献者

安全性

如果您在此软件包中发现安全漏洞,请发送电子邮件至 [email protected] 给 Graham Campbell。所有安全漏洞都将得到及时处理。您可以在 此处 查看我们的完整安全政策。

许可证

工厂松饼遵循 MIT 许可证 (MIT)

针对企业

作为 Tidelift 订阅的一部分提供

league/factory-muffin 的维护者以及数千个其他软件包的维护者正在与 Tidelift 合作,为构建应用程序时使用的开源依赖项提供商业支持和维护。节省时间,降低风险,并提高代码质量,同时支付您使用的确切依赖项的维护者。了解更多信息 请点击这里