DEV Community

Luigui Moreno
Luigui Moreno

Posted on

Laravel model factories with relation sharing foreign keys

Larevel users lets say you have three models User, Store and Product, and both Store and Product have a user_id foreign key to the users table, your Product factory may look like this:

<?php
//ProductFactory.php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Product;
use Faker\Generator as Faker;

$factory->define(Product::class, function (Faker $faker) {
    return [
        'name' => $faker->productName,
        'description' => $faker->text(),
        'price' => $faker->randomNumber(4),
        'sku' => $faker->uuid,
        'user_id' => function() {
            return factory(\App\User::class)->create()->id;
        },
        'store_id' => function() {
            return factory(\App\Store::class)->create()->id;
        }
    ];
});

And your StoreFactory like this:

<?php
//StoreFactory.php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Store;
use Faker\Generator as Faker;

$factory->define(Store::class, function (Faker $faker) {
    return [
        'name' => $faker->city,
        'location' => 'SRID=4326;POINT(-74.069891 4.605246)',
        'address' => $faker->address,
        'user_id' => function(){
            return create(\App\User::class)->id;
        },
        'code' => 'S' . $faker->randomNumber(5)
    ];
});

The problem is that then when generating products, the store they belong to doesn't belongs to the same user, and that makes no sense, these three models are just an example the same logic is valid for your specific database model, the solution is to access the current instance generated fields in the callback of the foreign key is:

<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Product;
use Faker\Generator as Faker;

$factory->define(Product::class, function (Faker $faker) {
    return [
        'name' => $faker->productName,
        'description' => $faker->text(),
        'price' => $faker->randomNumber(4),
        'sku' => $faker->uuid,
        'user_id' => function() {
            return create(\App\User::class)->id;
        },
        'store_id' => function(array $product) {
            return factory(\App\Store::class)->create(['user_id' => $product['user_id']])->id;
        }
    ];
});

The callback argument array $product has the current instance fields, this way you can pass the dynamic generated User to the "sibling" Store model.

Hope it is useful!.

Discussion (3)

Collapse
oilmonedev profile image
Oilmone

Thank you for the tips.

Collapse
franksierra profile image
Frank Sierra

Nice I've never thought you could pass a function as the value of an element in the factories

Collapse
luisgmoreno profile image
Luigui Moreno Author

Yeah, it saves a lot of repetition or helper functions