christophrumpel/laravel-factories-reloaded

该软件包位于 Laravel 工厂之上,为每个模型提供专门的工厂类。


README

Latest Version on Packagist v1 Total Downloads MIT Licensed

该软件包生成基于类的模型工厂,您可以使用它来替代 Laravel 提供的工厂。

Screenshot of the command

Laravel 8 支持

新版本 v3 现在支持 Laravel 8 和 PHP 8+。由于 Laravel 8 对工厂的实现方式进行了大量更改,因此新版本 v2 仅适用于 Laravel 8。如果您尚未使用 Laravel 8,请使用此软件包的最新 1.* 版本,如果需要 PHP 7 支持,请使用最新 2.* 版本。

优势

  • 使用您已熟悉的 Laravel 工厂功能(createmaketimesstates
  • 自动创建针对特定或所有模型的类工厂
  • 自动导入定义的 默认数据states 从您的 Laravel 工厂
  • 等等...

📺 我录制了一些 视频,以概述功能。

⚠️ 注意:对为什么需要基于类的工厂感兴趣?请在此处阅读 这里

安装

您可以通过 composer 安装该软件包

composer require --dev christophrumpel/laravel-factories-reloaded

要发布配置文件,请运行

php artisan vendor:publish --provider="Christophrumpel\LaravelFactoriesReloaded\LaravelFactoriesReloadedServiceProvider"

它将提供软件包的配置文件,您可以在其中定义 模型的多个路径新生成的工厂的路径旧 Laravel 工厂的命名空间,以及您的旧 Laravel 工厂的位置。

配置

Laravel 工厂命名空间

自 Laravel 8 以来,工厂已被命名空间化,默认的工厂命名空间为 Database\Factories。如果您的 Laravel 工厂命名空间为 Database\Factories\ClassFactories,您可以在配置文件中进行自定义,如下所示

'vanilla_factories_namespace' => 'Database\Factories\ClassFactories',

它将解析您的旧 Laravel 工厂类为 Database\Factories\ClassFactories\UserFactory,而不是 Database\Factories\UserFactory

使用方法

生成工厂

首先,您需要为您的模型之一创建一个新的工厂类。这通过一个新的命令 make:factory-reloaded 完成。

php artisan make:factory-reloaded

您可以选择其中一个找到的模型或为所有模型创建工厂。

命令选项

如果您想通过命令本身定义选项,也可以这样做

php artisan make:factory-reloaded --models_path="app/Models"  --factories_path="tests/ClassFactories" --factories_namespace="Tests\ClassFactories"

目前,您只能通过这种方式定义一个模型的位置。

定义默认模型数据

类似于 Laravel 工厂,您可以定义模型实例的默认数据。在您的新工厂中,有一个名为 getDefaults 的方法用于此目的。同样,也提供了 Faker 辅助工具来创建虚拟数据。

public function getDefaults(Faker $faker): array
{
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
        'remember_token' => Str::random(10),
        'active' => false,
    ];
}

使用新工厂

假设您已创建了一个新的用户工厂。现在您可以使用它并创建一个新的用户实例。类似于 Laravel 工厂,create 方法将持久化到一个新的模型中。

$user = UserFactory::new()->create();

如果您想获取一个尚未持久化的实例,可以选择 make 方法。

$user = UserFactory::new()->make();

要创建多个实例,请在 createmake 方法之前链式调用 times() 方法。

$users = UserFactory::new()
    ->times(4)
    ->create();

状态

您可能在您的旧 Laravel 工厂中定义了状态。

$factory->state(User::class, 'active', function () {
    return [
        'active' => true,
    ];
});

在创建新的类工厂时,系统会询问您是否希望将这些状态导入到新的工厂中。如果同意,可以立即使用它们。状态 active 现在是您的 UserFactory 的一个方法。

$user = UserFactory::new()
    ->active()
    ->create();

关系

通常您需要使用相关模型创建一个新的模型实例。现在通过使用 with 方法,这变得相当简单。

$user = UserFactory::new()
    ->with(Recipe::class, 'recipes', 3)
    ->create();

这里我们得到了一个附有三个相关菜谱的用户实例。这里的第二个参数定义了关系名称。

⚠️ 注意:为了使此功能正常工作,您需要已经创建了一个新的 RecipeFactory。

您还可以在直接使用相关模型工厂时定义相关模型的额外属性。

$user = UserFactory::new()
    ->withFactory(RecipeFactory::new()->withCustomName(), 'recipes', 3)
    ->create();

您可以通过链式调用 with 来创建许多相关模型实例。

$recipe = RecipeFactory::new()
    ->with(Group::class, 'group')
    ->with(Ingredient::class, 'ingredients')
    ->with(Ingredient::class, 'ingredients', 3)
    ->create();

这里我们得到一个具有一个组和四个成分的菜谱。

⚠️ 注意:在版本 1.0.8 之前,只有最后一个 with 关系被构建。

在 Laravel 工厂中,您也可以在默认数据中定义相关模型,如下所示

$factory->define(Ingredient::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'recipe_id' => factory(Recipe::class),
    ];
});

这也可以在我们的新工厂类中实现。

public function getDefaults(Faker $faker): array
{
    return [
        'name' => $faker->name,
        'recipe_id' => factory(Recipe::class),
    ];
}

或者通过新工厂类的实例来做得更好。

public function getDefaults(Faker $faker): array
{
    return [
        'name' => $faker->name,
        'recipe_id' => RecipeFactory::new(),
    ];
}

⚠️ 注意:我不推荐使用这些选项,因为在测试中您看不到额外的模型被持久化。请使用提供的 "with" 方法为您自己创建一个创建关系的专用方法。

回调

在 Laravel 中,您可以为 afterCreatingafterMaking 定义工厂 回调。您也可以用工厂类做类似的事情。由于 makecreate 方法都在您的工厂类中,您可以在那里添加代码。

public function create(array $extra = []): Group
{
    return $this->build($extra);
}

public function make(array $extra = []): Group
{
    return $this->build($extra, 'make');
}

这取决于您想要实现什么,但就我个人而言,我会在工厂中添加一个方法,并在测试中调用它。这样,就可以更清楚地了解发生了什么。

不可变性

您可能已经注意到,当此包为您导入一个 状态 时,它会在返回之前克隆工厂。

public function active(): UserFactory
{
    return tap(clone $this)->overwriteDefaults([
        'active' => true,
    ]);
}

这适用于所有您将用于设置测试模型的方法。如果您不克隆工厂,您将始终修改工厂本身。这可能导致在使用同一个工厂时出现问题。

为了使整个工厂默认为不可变,请将 $immutable 属性设置为 true。这样,每次状态更改都会自动返回一个克隆实例。

class UserFactory
{
    protected string $modelClass = User::class;
    protected bool $immutable = true;

    // ...

    public function active(): UserFactory
    {
        return $this->overwriteDefaults([
            'active' => true,
        ]);
    }
}

在某些情况下,您可能希望使用一个标准工厂作为不可变。这可以通过 immutable 方法来完成。

$factory = UserFactory::new()
    ->immutable();

$activeUser = $factory
    ->active()
    ->create();

$inactiveUser = $factory->create();

注意withwithFactory 方法始终是不可变的。

还有什么

这些新工厂类最好的地方在于您 拥有 它们。您可以创建尽可能多的方法或属性来帮助您创建所需的特定实例。以下是一个更复杂的工厂调用的示例

UserFactory::new()
    ->active()
    ->onSubscriptionPlan(SubscriptionPlan::paid)
    ->withRecipesAndIngredients()
    ->times(10)
    ->create();

使用这样的工厂调用将帮助您的测试保持干净,并为每个人提供一个很好的概述。

为什么使用基于类的工厂?

  • 它们在创建模型实例方面提供了更大的灵活性。
  • 它们使测试变得更加干净,因为您可以将复杂的准备隐藏在类内部。
  • 它们提供 IDE 自动完成功能,这是 Laravel 工厂所不具备的。

测试

composer test

更新日志

请参阅 更新日志 获取有关最近更改的更多信息。

贡献

请参阅 贡献指南 获取详细信息。

安全性

如果您发现任何安全相关的问题,请通过电子邮件 christoph@christoph-rumpel.com 而不是使用问题跟踪器。

鸣谢

一些实现受到了Brent关于如何处理Spatie工厂的文章的启发,该文章可参考Brent的文章

同时,对Adrian表示衷心的感谢,他在重构此包方面给予了我很大帮助。

许可协议

MIT许可协议(MIT)。有关更多信息,请参阅许可文件