Reputation: 91
In Laravel, let's say I want to have a Mission with different MissionObjectives, each MissionObjective has it's own model. I have Mission, CheckboxObjective, RadioObjetive and SpinnerObjective. I need to connect the Objectives to the Mission, but I don't want to use a different method for each Objective, I want them all in one method. I tried polymorphic relations but they just don't seem to work for this. I would need a Many-to-One, sort of, which doesn't exist.
This is what I want:
Mission class:
public function objectives() {
return *All objectives*;
}
Objective classes:
public function mission() {
return $this->hasOne('App\Models\Mission');
}
Upvotes: 2
Views: 1608
Reputation: 4857
I found 2 ways to do this. Both do the job, neither one is perfect.
Missions which have multiple *Objective
Mission
(1:n)*Objective
- one-to-many
Mission
hasMany *Objective
Objective
(Abstract) belongsTo Mission
*Objective
extends Objective
Missions which have multiple Objective
which in turn each can be of kind *Objective
(intermediate layer)
Mission
(1:n)Objective
- one-to-many
Objective
(1:1)*Objective
- one-to-one (polymorphic)
Mission
hasMany Objective
Objective
morphTo *Objective
*Objective
extends Objective
You'd use 2 only if you need an itermediate layer if you want to inherit common attributes for *Objective
from Objective
- which totally makes sense, since they all at least share mission_id
.
You won't be able to get inherited attributes by *Objective
out of the box, because the model for any *Objective
is bound to a specific table, hence it cant reach the attributes of the inherited Objective
class. There is a module for this though: parental
edit: Nevermind, parental only supports STI(Single Table Inheritance) which means we can have multiple models referring the same table.
Tables
missions
id - int
*_objectives (e.g. main_objectives)
id - int
mission_id - int
Models
class Mission extends Model
{
const OBJECTIVE_TYPES = [
MainObjective::class,
// ...
];
public function __get($name)
{
switch ($name) {
case 'objectives':
$objectives = new Collection();
foreach (static::OBJECTIVE_TYPES as $objectiveType) {
$objectives = $objectives->concat($this->hasMany($objectiveType)->get()->all());
}
return $objectives;
default:
return parent::__get($name);
}
}
}
abstract class Objective extends Model
{
public function mission() {
return $this->belongsTo(Mission::class, 'mission_id', 'id');
}
}
class MainObjective extends Objective
{
}
Example
$mission = Mission::find(1);
foreach ($mission->objectives as $objective) {
echo 'Class: ' . get_class($objective) . PHP_EOL;
}
Output (In case we have 2 MainObjectives and 1 SecondaryObjective)
Class: App\Models\MainObjective
Class: App\Models\MainObjective
Class: App\Models\SecondaryObjective
Tables
missions
id - int
objectives
id - int
mission_id - int
objectiveable_type - string
objectiveable_id - int
*_objectives (e.g.: main_objectives)
id - int
objectiveable_id
refers the id
of the table used by the model in objectiveable_type
.
objectiveable_type
has to be fully qualified. Example: App\Models\MainObjective
Models
class Mission extends Model
{
public function __get($name)
{
if ($name === 'objectives') {
return new Collection(array_map(function ($objective) {
return $objective->objectiveable;
}, $this->hasMany(Objective::class)->get()->all()));
}
return parent::__get($name);
}
}
class Objective extends Model
{
public function objectiveable()
{
return $this->morphTo();
}
protected function _mission() {
return $this->belongsTo(Mission::class, 'mission_id', 'id');
}
public function __get($name)
{
switch ($name) {
case 'mission':
return $this->morphMany(Objective::class, 'objectiveable')->get()->first()->_mission;
default:
return parent::__get($name);
}
}
}
class MainObjective extends Objective
{
}
Example
$mission = Mission::find(1);
foreach ($mission->objectives as $objective) {
echo 'Class: ' . get_class($objective) . PHP_EOL;
}
Output (In case we have 2 MainObjectives and 1 SecondaryObjective)
Class: App\Models\MainObjective
Class: App\Models\MainObjective
Class: App\Models\SecondaryObjective
Pretty much the same as in the first solution. The big difference here are the attributes. you won't be able to access the attributes of Objective
of MainObjective
without extending this further.
@Laravel/Eloquent
PLEASE make this easy - my head hurts.
Upvotes: 1