【问题标题】:Laravel Factories - Create or Make dynamicLaravel 工厂 - 创建或制作动态
【发布时间】:2020-06-17 19:37:57
【问题描述】:

我有一个简单的问题。

我如何编写一个工厂,让我根据原始调用 make()create() 定义使用 make()create() 的关系?

这是我的用例:

我有一个简单的工厂

/** @var $factory Illuminate\Database\Eloquent\Factory */
$factory->define(App\User::class, function (Faker $faker) {
    return [
        'role_id' => factory(Role::class),
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => 'secret',
    ];
});

我的问题在于那个 role_id。当使用factory(Role::class) 时,它会一直创建一个角色!在数据库中写入...

在我的测试中,当我写 factory(User::class)->create(); 时,它会创建一个用户和角色,没关系!

但是如果我写factory(User::class)->make();它仍然会创建一个角色...我不会在使用make()时在数据库中写一个角色,在这种情况下,我只想要一个简单的role_id => 0

我怎样才能做到这一点?

谢谢!

编辑(解决方法)

嗯,这比预期的要难,当您定义嵌套关系时,无法知道何时使用make()create()...我发现的唯一方法是使用debug_stacktrace() 方法,并寻找从工厂调用的 make 方法。

为了简化和重用,我为工厂创建了一个辅助方法associateTo($class),这个方法将定义工厂中的关系。使用create() 时,它将从相关模型返回一个现有ID,使用make() 时返回1。

通过这种方式,工厂可以:

/** @var $factory Illuminate\Database\Eloquent\Factory */
$factory->define(App\User::class, function (Faker $faker) {
    return [
        'role_id' => associateTo(Role::class), // Defining relationship
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => 'secret',
        'remember_token' => Str::random(10),
    ];
});

辅助函数是:

/**
 * Helper that returns an ID of a specified Model (by class name).
 * If there are any Model, it will grab one randomly, if not, it will create a new one using it's Factory
 * When using make() it wont write in Database, instead, it will return a 1
 *
 * @param string $modelClass
 * @param array $attributes
 * @param bool $forceNew
 * @return int
 */
function associateTo(string $modelClass, array $attributes = [])
{
    $isMaking = collect(debug_backtrace())
        // A function called 'make()'
        ->filter(fn ($item) => isset($item['function']) && $item['function'] === 'make') 
        // file where I have global functions
        ->filter(fn ($item) => isset($item['file']) && Str::endsWith($item['file'], 'functions.php')) 
        ->count();

    if ($isMaking) return 1;

    /** @var Model $model */
    $model = resolve($modelClass);

    return optional($model::inRandomOrder()->first())->id
        ?? create($modelClass, $attributes)->id; // call to Factory
}

有了这个,我可以轻松编写单元测试(使用工厂)而无需使用数据库连接,因此单元测试变得超快!

我希望这对其他人有帮助!

【问题讨论】:

    标签: database laravel testing factory relation


    【解决方案1】:

    您可以覆盖属性role_id

    factory(User::class)->make(['role_id' => 0]);
    

    另一种解决方案 - 以_id 结尾的任何属性的辅助方法:

    function make($class)
    {
        $attributes = \Schema::getColumnListing((new $class)->getTable());
        $exclude_attribures = [];
    
        foreach ($attributes as $attribute)
        {
            if(ends_with($attribute, '_id'))
            {
                $exclude_attribures[] = [$attribute => 0];
            }
        }
    
        return factory($class)->make($exclude_attribures);
    }
    

    调用示例:

    make(User:class);
    

    【讨论】:

    • 感谢您的回答。我知道,但我希望在调用make() 时自动完成,因为如果我每次只想使用factory(...)->make() 时都必须覆盖每个模型的所有关系属性,那将是压倒性的......也许我可以创建一个测试名为 make(MyClass::class) 的全局函数,它用 0 覆盖所有“_id 结尾”属性,但我仍然不知道如何实现这一点,尤其是嵌套工厂关系。
    • 我添加了辅助方法
    • 谢谢,这很接近,但我不能使用它,因为我避免使用数据库,所以方法 getColumnListing() 不起作用,但是,我可以使用来自 Illuminate\Database 的 rawOf(class) \Eloquent\Factory :) 但是,在定义嵌套关系时它仍然失败:(
    【解决方案2】:

    你可以利用状态

    
    /** @var $factory Illuminate\Database\Eloquent\Factory */
    $factory->define(App\User::class, function (Faker $faker) {
        return [
            'role_id' => factory(Role::class),
            'name' => $faker->name,
            'email' => $faker->unique()->safeEmail,
            'password' => 'secret',
        ];
    });
    
    $factory->state(App\User::class, 'withoutRelationship', [
        'role_id' => null,
        'another_id' => null,
    ]);
    
    

    然后

    factory(User::class)->state('withoutRelationship')->make();
    

    【讨论】:

      猜你喜欢
      • 2010-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-19
      相关资源
      最近更新 更多