Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

chrispage1's avatar

Using factories to seed sub-relations

I've been watching lots of tutorials and I want to get better at writing tests for my integrations. I've created factories but I'm a little stuck at this point on how to seed the database with all the relevant data...

On my system I have three tables, assets, asset_types & asset_models. Each of these belong to a client master record.

assets

  • id
  • client_id
  • asset_type_id
  • asset_model_id
  • name
  • year...

asset_types

  • id
  • client_id
  • name

asset_models

  • id
  • client_id
  • name

So my question is, how could I reliably seed this so that Asset model generates a client, and asset_types & asset_models generate the client? Currently I've got the following -

$factory->define(Asset::class, function (Faker $faker) {

    return [
        'client_id' => factory(Client::class),
        'asset_type_id' => factory(AssetType::class),
        'asset_model_id' => factory(AssetModel::class),
        'name' => $faker->word,
    ];
});

The problem is that by using factory(AssetType::class), it can actually generate an asset type with a completely different client id to the parent asset?

I hope this makes sense and someone can help!

Thanks, Chris.

0 likes
6 replies
andylord565's avatar

@chrispage1

do something like this in your seeder

factory(App\Asset::class, 1000)->create()->each(function ($asset) {
            factory(\App\AssetType::class, 1000)->create(['asset_id'=>$asset->id]);
            factory(\App\AssetModel::class, 1000)->create(['asset_id'=>$asset->id]);
        });

edit with client as well:

factory(App\Client::class, 1000)->create()->each(function ($client) {
    factory(App\Asset::class, 1000)->create(['client_id'=>$client->id])->each(function ($asset) {
                factory(\App\AssetType::class, 1000)->create(['asset_id'=>$asset->id]);
                factory(\App\AssetModel::class, 1000)->create(['asset_id'=>$asset->id]);
            });
});

edit you wouldnt have asset type_id and asset_model_id on assets table but have them as a relationship so that a client has a asset that has a types and models.

Hope that helps!

chrispage1's avatar

@ANDYLORD565 - Thanks Andy - for the sake of speed I was hoping I could do something within the factory so that when I spin up an Asset it automatically took care of the Asset Type & Asset Model creation so all I had to type was factory(App\Asset::class)->create() in my tests.

Appreciate your feedback and pointers :)

chrispage1's avatar

I've been overlooking a really obvious solution... What about doing this - any objections?

$factory->define(Asset::class, function (Faker $faker, array $args = []) {

    $client_id = isset($args['client_id'])
        ? $args['client_id']
        : factory(Client::class)->create();

    return [
        'client_id' => $client_id,
        'asset_type_id' => factory(AssetType::class)->create(['client_id' => $client_id]),
        'asset_model_id' => factory(AssetModel::class)->create(['client_id' => $client_id]),
        'name' => $faker->word,
    ];
});
andylord565's avatar

@chrispage1

That would work i would recommend the table change however for relational and future work. Especially if a asset has multiple types and models you can even drop the client_id as well as its on the Assets. Solution found is good though!

Example:

assets: id client_id name year

asset_types: id asset_id name

asset_models: id asset_id name

chrispage1's avatar

Ah - I need those there because its a multi tenancy system. One client can have many asset_types / asset_models which both in turn have many assets.

Originally I didn't have the client_id column on the assets table but then decided today for the sake of transparency and ease of querying, logic etc etc I would just have it on all three tables.

Right - all my tests are now working with SQLite which was this evenings aim. Thank you! :)

tokoiwesley's avatar

Since all the three records (assets, asset_types & asset_models) belong to a client master record, the solution is to start by seeding the clients table first followed by asset_types and asset_models and lastly assets as illustrated below;

// Create clients first
factory(\App\Client::class, 50)->create()->each(function ($client) {

    // Create asset models and asset types
    $assetModels = factory(\App\AssetModel::class)->create(['client_id' => $client->id]);
    $assetTypes = factory(\App\AssetType::class)->create(['client_id' => $client->id]);

     // Create assets
     factory(\App\Asset::class)->create([
         'client_id'      => $client->id,
         'asset_type_id'  => $assetTypes->where('client_id', $client->id)->first()->id,
         'asset_model_id' => $assetModels->where('client_id', $client->id)->first()->id,
    ]);
});

Please or to participate in this conversation.