Laravel Фабрики - Создавайте или Делайте динамически - PullRequest
1 голос
/ 05 марта 2020

У меня простой вопрос.

Как я могу написать Фабрику, которая позволяет мне определять отношения, которые используют 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, вызываемого из Factory.

Для упрощения и повторного использования я создал вспомогательный метод для Factories под названием associateTo($class), этот метод будет определять отношения в Factory. Он вернет существующий идентификатор из связанной модели при использовании create() или 1 при использовании make().

Таким образом, фабрики могут быть:

/** @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
}

С этим я могу легко написать модульный тест (используя фабрики) без использования соединений с базой данных, поэтому модульные тесты становятся сверхбыстрыми!

Надеюсь, это поможет кому-то еще!

Ответы [ 2 ]

0 голосов
/ 05 марта 2020

Вы можете воспользоваться состояниями


/** @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();
0 голосов
/ 05 марта 2020

Вы можете перезаписать атрибут 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);
...