JorickL's avatar

Organize polymorphic belongsTo save

OK, here's the deal. I have a situation were there are 3 models, which can have one relation to another model. Let's say we have 3 producttypes and every producttype has a maintenance order.

What is the best way to attach a new maintenance order to one of the producttype models? At first the most 'easy' solution that I could've think of is to create one MaintenanceOrderController which hold the 'create' method. But... How is that controller going to find out to which related model it belongs, if I can only give an ID?

So my table of maintenance_orders looks like this:

id producttype_id producttype_type
1  1                              App\Wasmachines
2 1                             App\Computers
etc...

Any good advice how to fix this, without having the need to create 3 methods in every prodducttype controller to be able to save those maintenanceOrders?

0 likes
7 replies
ts's avatar

You don't necessarily need a controller. I did something very similar last week with a Polymorphic belongsTo relationship for Notifications.

I ended up using a trait, it works perfectly for this kind of thing because the classes (should) all have the same logic for creating the relation.

JorickL's avatar

Allright! So, I've to figure out how that thing will work then.

D9705996's avatar

@jorickL - A couple of questions that will help answer your question

  1. Does a product have only one maintenance order or can it have multiple maintenance orders?
  2. Do you need a polymorphic relationship? Could you have a product and type tables instead?
JorickL's avatar

@D9705996 Hi! Yes, it can have (and certainly will) have multiple orders.

  1. Is more complicated. It is about rolling stock. You have locomotive, carriages and multiple units as models that will require maintenance. Every model of that particular type, hasMany units. Units share the same table, as the information there is the same across all the units. So there is already a polymorphic relation between unit types and individual units.

As the maintenance orders are based on a type and not on a singular unit, but all the maintenance shares the same base, but on a lower level is split up into several maintenance tasks and so on.

Because of the unit types share the same maintenance order class, but are completely different as types itself, the only way of letting these talk 'together' in my imagination was to use polymorphism.

Is it clear, or should I describe in more depth? Thanks for your help and interest anyway!

D9705996's avatar
D9705996
Best Answer
Level 51

@JorickL - wow that does sound complicated! What I would do is create a table/model that represents all of the things in you application that you can apply a maintenance order, possibly a maintainable with a migration like

$table->primary(id);
$this->morphs('maintainable'); 
$this->timestamps();

You can then in you maintainable model you can do

public function maintainable() {
    return $this->morphsTo();
}

In your polymorphic classes, such as locomotive.

public function maintainable() {
    return $this->morphOne(Maintainable::class, 'maintainable');
}

I am not sure if this is right way to use Polymorphic relations but in my usecase (which is similar to yours) it keeps the laravel magic isolated to under the maintainable model/table so the rest of my application just deals with the base table. e.g.

in maintenance orders

public function maintainable() {
    return $this->belongsTo(Maintainable::class);
}

in maintainable

public function maintanceOrders() {
    return $this->hasmany(MaintenanceOrders);
}

so you can then do something like

Locomotive::first()->maintainable->maintenanceOrders);

MaintenanceOrder::first()->maintainable->maintainable; //should print you the underlying model of the thing the maintenance order applies to. 

I found that you have to add a few steps when creating new sub-classes such as locomotive to hook up the maintainable parent but I put these in a Model Observer so it automatically handles this on creating/deletion.

Once you find yourself repeating code in the above scenario for each sub type you can extract a base class that the subtypes inherit from and put shared logic/relationships in there :D

Unfortunately Im not permitted to share my solution as its from my @dayjob and there are IP restrictions otherwise I would (Which is why I have done the above from memory so there might be some mistakes but you should get the general idea).

Hopefully make sense but if not let me know. Also keen too hear if anyone has any thoughts on how I can improve my workflow here as I generally find it a bit icky hence only reaching for it when I have to.

1 like
JorickL's avatar

Holy, thanks for your time! And the great answer! I'm going to read it all again once more and I think I've found the right solution. Especially about the hint for extracting to a base class. As long as I'm able to work with one 'base model' like Locomotive, I'm able to rerun back to the maintenanceorders, one way or another.

I'll let you know what the results are! Thanks - a lot! Fresh eyes on this piece of magic is much appreciated!

1 like
JorickL's avatar

@D9705996 Alright! I've figured it out. Because I already have a polymorphic relation between each Locomotive::class, Carriage::class etc. to my MaintenanceOrder::class and did a bit of searching on Google, I found out about Explicit Model Binding.

In my RouteServiceProvider::boot method I declared every possible 'unit type', and in my routes/web.php I did it as well. Therefore I'm able to fetch the complete model in the MaintenanceOrder::create($unit) method.

It looks like this:

RouteServiceProvider.php

public function boot() {
    parent::boot();
    Route::model('locomotive', \App\Locomotive::class);
    [...]
}
routes\web.php

[...]

Route::get('maintenance/order/locomotive/{locomotive}', 'MaintenanceOrderController@create');

[...]

Please or to participate in this conversation.