phplaravelpolymorphic-associations

Laravel: complex Many to Many Polymorphic Relationship for conditions and actions


The Method model is a container of conditions (triggers) and actions.


We can have different types of Condition, for instance:

And we can have different types of Action, for instance:


enter image description here

We are creating a table for each type of condition and a table for each type of action. That way we don't end up with a table full of columns to encompass each possible scenario.

The problem is: how to create this Polymorphic Relationship?


Starting with Conditions

My database looks like:

🟢 conditions
id condition_id condition_type method_id
1001 5005 App\Model\ConditionTypeOrderAmount 1
1002 6006 App\Model\ConditionTypeOrderWeight 1
condition_type_order_amount
id min_amount max_amount
5005 0 500
condition_type_order_weight
id min_weight max_weight
6006 10 100
🟣 actions
id action_id action_type condition_id
8008 2002 App\Models\ActionTypeCallSupervisor 1001
8009 2002 App\Models\ActionTypeCallSupervisor 1002
action_type_call_supervisor
id supervisor_id call_anytime
2002 5 1

I could partially achieve what I need using belongsToMany and wherePivot in Method, like:

class Method extends Model
{
    public function orderAmountConditions()
    {
        return $this
            ->belongsToMany(
                ConditionTypeOrderAmount::class,
                'conditions',
                'method_id',
                'condition_id',
            )->wherePivot(
                'condition_type',
                ConditionTypeOrderAmount::class
            );
    }

    public function orderWeightConditions()
    {
        return $this
            ->belongsToMany(
                ConditionTypeOrderWeight::class,
                'conditions',
                'method_id',
                'condition_id',
            )->wherePivot(
                'condition_type',
                ConditionTypeOrderWeight::class
            );
    }
}

But in that scenario conditions table was used just as a pivot table and I had a single function for every type of condition. Not ideal.

So I tried to use Polymorphic Relationships, but than thing got confusing…

What I tried:

Both ConditionTypeOrderAmount and ConditionTypeOrderWeight have the same conditions() relationship:

class ConditionTypeOrderAmount extends Model
{
    public function conditions()
    {
        return $this->morphMany(
            Condition::class,
            'conditions'
        );
    }
}

And Condition has:

class Condition extends MorphPivot
{
    public function conditionable()
    {
        return $this->morphMany(
            Condition::class,
            'conditions',
            'condition_id',
        );
    }
}

But no results are found. I already read Laravel docs several times and watched a lot of YouTube videos, but I can't figure Polymorphic relationships.

Expected response would be something like:

{
  "method": {
    "id": 1,
    "conditions": [
      {
        "id": 1001,
        "type": "App\Models\ConditionalTypeOrderAmount",
        "min_amount": 0,
        "max_amount": 500,
        "actions": [
          {
            "id": 8008,
            "type": "App\Models\ActionTypeCallSupervisor",
            "supervisor_id": 5,
            "call_anytime": 1,
          }
        ]
      },
      {
        "id": 1002,
        "type": "App\Models\ConditionalTypeOrderWeight",
        "min_weight": 10,
        "max_weight": 100,
        "actions": [
          {
            "id": 8009,
            "type": "App\Models\ActionTypeCallSupervisor",
            "supervisor_id": 5,
            "call_anytime": 1,
          }
        ]
      }
    ]
  }
}

Solution

  • In Condition model (extends Model):

        public function conditionable()
        {
            return $this->morphTo();
        }
    
        public function method()
        {
            return $this->belongsTo(
                Method::class
            );
        }
    
        public function actions()
        {
            return $this->hasMany(
                Action::class
            );
        }
    

    In ConditionType.... models (extends Model):

        public function conditions()
        {
            return $this->morphMany(
                Condition::class,
                'conditionable',
            );
        }
    

    In Method model (extends Model):

        public function conditions()
        {
            return $this->hasMany(
                Condition::class
            );
        }
    

    In Action model (extends Model):

        public function actionable()
        {
            return $this->morphTo();
        }
    
        public function condition()
        {
            return $this->belongsTo(
                Condition::class
            );
        }
    

    In ActionType.... models (extends Model):

        public function actions()
        {
            return $this->morphMany(
                Action::class,
                'actionable',
            );
        }