Model factory for a polymorphic relationship

Published 2 months ago by Roni

I'm a bit stuck trying something out. Using a Java style inheritance in laravel to get a collection of specialized segments. I'm having a hard time figuring out how to get the model factory working for phpunit, though the code itself works.

ModelFactory


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

    return [
        'quote_id' => create(Quote::class)->id,

        //THIS IS THE BASE ITEM, 
        //NO SEGMENTS EVER EXIST WITHOUT A SPECIFIC IMPLEMENTATION

        'segmentable_id'=> null,
        'segmentable_type'=> '',

        'version' => 1,
        'version_accepted_by_client' => null,
        'version_accepted_by_company' => null,
        'active_version' => 1,
        'company_token' => null,
        'company_token_expiration' => null,
        'client_token' => null,
        'client_token_expiration' => null,

    ];
});


//SPECFIC SEGMENT EXAMPLE

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

    //THIS IS THE SECTION WHERE I NEED TO DEFINE A SEGMENT AND
    //AND OVERRIDE IT'S "SEGMENTABLE" ATTRIBUTES TO THIS
    //CLASSES ID AND CLASSNAME

    // create a segment class here and find a way to override

    return [

        'product_id' => function() { return create(Product::class)->id; },
        'height' => $faker->numberBetween(6,72),
        'width' => $faker->numberBetween(6,72),
        'grouted' => $faker->boolean,
        'sill_on_top' => $faker->boolean,
        'trim_stones' => $faker->numberBetween(0,6),
        'hearth_stones' => $faker->numberBetween(0,6),
        'light_boxes' => $faker->numberBetween(0,6),
        'power_boxes' => $faker->numberBetween(0,6),

    ];
});


The purpose of this is to delegate the calculation of each versioned segment to it's unique object, while allowing a collection of Segments to reside in a project and quote. There is no specific guarantee that any project will have any specific segment. Yet this creates a completely flexible approach.

In code this is not an issue, as I can enforce the relationship in in the boot creating method of the object, or in the controller. I'm a bit new to TDD and factories, is there a "right way" to do this in the model factory?

Thanks

bobbybouwmann

Well you can't do that in the factory class itself. A many-to-many relationships uses a pivot table, so a different table from the tables you with with. Since this is active record your models only have influence on their own table.

So in your example you can do something like this in your test to generate enough data

$stones  = factory(Stone::class, 10)->create();

$segments = factory(Segment::class, 10)->create()->each(function ($segment) use ($stones) {
    $segment->stones()->sync($stones);
});
Roni
Roni
2 months ago (65,660 XP)

Thanks Bobby, I should basically send all my questions directly to you!

I've solved it a bit differently, I think I could benefit from someone with more experience on this issue. Right now, have to keep the segment morph fields nullable, which seems wrong.

and what I've done is add the construction of the segment to the static boot method on the created hook to try and avoid that. but I've found that, it can get bloated, as I also have versions of the quote, which I need to pass through the constructor. I think I may need to rethink this a bit. Are there any paradigms you might suggest to solve this type of problem? Is there a better way to use some form of inheritance? Or just stick with models that don't extend a base and just follow a common external API?

bobbybouwmann

Mmh I don't have enough information to give you the best advice! However I can tell you that you need to keep everything as simple as possible. When you start extending all kind of classes your classes get more and more functionality and also more and more responsibilities.

Can you share your current solution?

Roni
Roni
2 months ago (65,660 XP)

Sure, I’ll put something together on github tonight and post a link. And thanks for all the advice.

bobbybouwmann

No problem ;)

Please sign in or create an account to participate in this conversation.