Skip to content
Advertisement

Laravel. morphToMany in cascade mode

I am looking a way of doing the following:

Let’s say we have a Theater application and we have three Models: Event, Session and Seat on one hand and Rate on the other hand.

We have a morphToMany relation so a Rate can be associated with any of the other three models.

Now, we want to have a rates() relation in each model that work in kind a cascade mode. If we ask for rates() on a Seat instance it will returns its Rates if the have, otherwise, it will check if the Session does have Rates and will return them. If not, it will return the Rates associated with Event.

So we want to define like a hierachy or cascade mode to retrieve Rates.

EDIT

More information: Moreover, the tricky part is, for example, an Event may have two Rates: General and Youth for 50$ and 30$ each. But a given session maybe has General at 40$. So for this given Session, the rates() method should return two rates: General at 40$ and Youth at 30$ since it was not “overriden” in Session instance.

If Session->rates() is null it’s easy to return $this->event->rates() but not so easy when I want to apply this “cascades” explained before

EDIT 2

After my first approaches I see some strange behaviour. To make it simple: I have class

<?php

namespace App;

use BackpackCRUDCrudTrait;
use BackpackCRUDModelTraitsSpatieTranslatableHasTranslations;
use IlluminateDatabaseEloquentModel;

class Event extends Model
{

    public function rates()
    {               
        return $this->morphToMany('AppRate', 'assignated_rate', 'assignated_rates', 'assignated_rate_id', 'rate_id')
                ->withPivot(['price', 'max_on_sale', 'max_per_inscription_set']);
    }

}

and

<?php

namespace App;

use BackpackCRUDCrudTrait;
use BackpackCRUDModelTraitsSpatieTranslatableHasTranslations;
use IlluminateDatabaseEloquentModel;
use IlluminateDatabaseEloquentRelationsHasMany;

class Session extends Model
{

    public function event()
    {
        return $this->belongsTo('AppEvent');
    }

    public function rates()
    {            
        return $this->event->rates();
    }        
}

So I am trying the easiest situation. Session has no Rates and must be taken from its Event.

I have a Session with ID 2 and an Event with ID 1. Session 2 belongs to Event 1.

When I do Session::find(2)->rates() rates of Event1 is returned but when I do Session::find(2)->load('rates') nothing is returned.

If I explore the Query executed I got:

SELECT `rates`.*, `assignated_rates`.`assignated_rate_id` as `pivot_assignated_rate_id`, [.....] FROM `rates` inner join `assignated_rates` on `rates`.`id` = `assignated_rates`.`rate_id` WHERE `assignated_rates`.`assignated_rate_id` in ('2') and `assignated_rates`.`assignated_rate_type` = 'App\Event'

Note the WHERE condition: WHERE assignated_rates.assignated_rate_id in (‘2’) where 2 is the ID of the Session, not the Event related to Session.

May it be a bug? Or am I missing something? I guess the condition should be bounded to ID=1 since it is Event’s ID

How would you do this??

Thanks!

Advertisement

Answer

Inheritance may suit you just fine, if all seats are guaranteed to be part of sessions, and all sessions are guaranteed to be part of events. Personally though, I start to get suspicious when I see class hierarchys more than two levels deep that are composed of potential top-level entities (framework classes notwithstanding). Often, the problem can be solved more cleanly with a compositional relationship.

It’s just a hunch though, so if you’re good, don’t let me waste your time. But if there are any use-cases where decoupling those concepts would be more concise, I see a couple ways to do that.

One is the Strategy Pattern, in which you have a sort of master Rate class that evaluates the circumstances of the request and determines which source is appropriate to pull the rate from. The other is the Decorator pattern, in which you have the result of one rate calculation feed into another for alterations. (This might be handy for applying discounts.) I think the one I would go with is the Chain of Responsibility. This takes the form of a middleware-like ‘handler stack’, and each step therein can decide if it wants to apply a rate from a particular source.

In any case, solving this issue directly within the Model structure is not the right place to look. In my mind, they should be treated as little-more than static configurations, making relationship options available, but not actually making any decisions about the output besides transformations.

User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement