Двусторонние отношения в Laravel Factory с Faker

У меня есть два модала, для которых я пытаюсь написать фабрики. Строительные позиции и счета-фактуры. Проблема в том, что они динамически основаны друг на друге.

Table: line_items
id | amount | invoice
1  | 15     | 1 
2  | 10     | 1 
3  | 15     | 2
4  | 5      | null

Table: invoices
id | total
1  | 25   
2  | 15   

Элементы строки знают, какой счет-фактура им назначен, а счета-фактуры сохраняют общую стоимость назначенных им элементов строки.

LineItems будет выглядеть так:

$factory->define(\App\Models\LineItems::class, function (Faker $faker) {
  return [
    'amount' => $faker->numberBetween(1, 15),
    'invoice' => null
  ];
});

А затем, чтобы построить фабрику для счетов-фактур, мне нужно суммировать суммы из статей расходов, ничего страшного, я могу создавать статьи статей внутри фабрики и суммировать:

$factory->define(\App\Models\Invoice::class, function (Faker $faker) {
  $total = 0;

  $line_items = $factory(\App\Models\LineItems::class, 3);

  foreach($line_items as $l) {
    $total += $l->amount;
  }

  return [
    'total' => $total
  ];
});

Но теперь проблема заключается в том, как мне сохранить номер счета-фактуры id обратно в только что созданные позиции? Очевидно, что у меня нет id до тех пор, пока не будет сохранена фабрика, то есть после обратного вызова закрытия. закончен. Я не могу вернуть LineItems из закрытия, потому что фабрике нужно использовать возврат, чтобы он мог фактически сохранить счет-фактуру.

Я знаю, что это похоже на проблему X/Y. Но я просмотрел некоторые из подобных вопросов здесь, на SO, и я не поклонник обходных путей, потому что они очень хрупкие, полагаются на неочевидные порядки работы или на то, что определенные данные уже существуют, прежде чем вы сможете запустить завод. Я бы предпочел найти решение с использованием фабрик и, если возможно, просто сохранить все проблемы на местном уровне.

Кроме того, если есть решение, это означает, что фабрики самодостаточны, что лучше для всех заинтересованных сторон. Но если это невозможно, это был бы приемлемый ответ, поскольку он позволяет мне использовать другие (хотя и более хрупкие) способы сохранения тестовых данных.


person amflare    schedule 16.08.2018    source источник
comment
Какую версию Laravel вы используете?   -  person Rwd    schedule 16.08.2018
comment
@РоссВилсон Laravel 5.5   -  person amflare    schedule 16.08.2018
comment
Я хотел сказать, что если вы используете ›= 5.6.12, вы могли бы использовать метод afterCreating().   -  person Rwd    schedule 16.08.2018
comment
Блин, это было бы полезно.   -  person amflare    schedule 16.08.2018
comment
Интересно, почему total не вычисляется, а сохраняется как целое число... подвержено ошибкам. Удачи!   -  person Kyslik    schedule 16.08.2018


Ответы (1)


Способ 1 — с использованием исходного кода

Во-первых, фабрика LineItems устанавливает случайный счет-фактуру.

$factory->define(\App\Models\LineItems::class, function (Faker $faker) {
    return [
        'amount' => $faker->numberBetween(1, 15),
        'invoice' => function () {
             return factory(\App\Models\Invoice::class)->create()->id;
        },
    ];
});

Затем создайте счет-фактуру без зависимости

$factory->define(\App\Models\Invoice::class, function (Faker $faker) {
    return [
        'total' => 0
    ];
});

Затем вы создаете семя, чтобы заполнить его так, как вы хотите.

class InvoiceSeederClass extends Seeder
{
    public function run()
    {
        $invoice = factory(App\Models\Invoice::class)->create();

        $lineItems = factory(App\Models\LineItems::class, 3)->create([
            'invoice' => $invoice->id,
        ]);

        $total = 0;
        foreach($lineItems as $item) {
            $total += $item->amount;
        }

        $invoice->amount = $total;
        $invoice->save();
    }
}

$seeder = new InvoiceSeederClass();
$seeder->run();

Теперь логика фабрик изолирована, и вы можете создавать свою собственную бизнес-логику.


Способ 2 — с фиктивной фабрикой

Создать фабрику статей расходов

$factory->define(\App\Models\LineItems::class, function (Faker $faker) {
    return [
        'amount' => $faker->numberBetween(1, 15),
        'invoice' => function () {
             return factory(\App\Models\Invoice::class)->create()->id;
        },
    ];
});

Создать фабрику счетов

$factory->define(\App\Models\Invoice::class, function (Faker $faker) {
    return [
        'total' => 0
    ];
});

Создать фиктивную фабрику

$factory->define(\App\Models\DummyFactory::class, function () use($faker) {
    $invoice = factory(App\Models\Invoice::class)->create();

    $lineItems = factory(App\Models\LineItems::class, 3)->create([
        'invoice' => $invoice->id,
    ]);

    $total = 0;
    foreach($lineItems as $item) {
        $total += $item->amount;
    }

    $invoice->amount = $total;
    $invoice->save();

    return []; // you can return any array you want, ex. $invoice
});

Применение. Важно: используйте MAKE вместо CREATE:

$invoiceWithItems = factory(App\Models\DummyFactory::class)->make();

Теперь ваша логика внутри фабрики

person Felippe Duarte    schedule 16.08.2018
comment
Если я что-то упустил, это представляет ту же проблему, но в обратном порядке. Как я могу получить общую сумму, сохраненную обратно в счет-фактуру? Вы предлагаете просто запросить его с помощью красноречия и нормально обновить в сеялке? - person amflare; 16.08.2018
comment
Я забыл об этом. Но все, что вам нужно, это добавить свою собственную логику. - person Felippe Duarte; 16.08.2018
comment
Отредактировано. Вы можете использовать dummyfactory для генерации данных внутри factory вместо сеялки. - person Felippe Duarte; 16.08.2018