user3871
user3871

Reputation: 12708

Retrieve polymorphic model instances

Using Laravel 5.1, I'm trying to create a dynamic table where I can associate multiple item types with a module. I went with a Polymorphic table and set the morphable_ids and morphable_types per module as such:

id  module_id morphable_id morphable_type
1       1          1       App/Enemy
2       2          2       App/Enemy
3       3          1       App/Item

I get my modules through associated tasks, aka modules are eager loaded in with tasks. When I drill down into my task from the API call, and check module_items, it shows the literal morphable_id/morphable_type rather than the App\Enemy or App\Item instances I was looking for:

more task obj properties...
"module":{
   "id" : 1
   "module_items":[
      {
        "id": 3,
        "module_id": "3",
        "morphable_id": "1",
        "morphable_type": "App/Item"
      }
   ]
 }

So my question is, how can I actually retrieve the polymorphic model instance (like, I retrieve App\Item of id 1 for module 1)?

Here is some more information:

Models:

Task: loads with modules

class Task extends Model
{

    protected $with = ['module'];


    public function module()
    {
        return $this->belongsTo(Module::class);
    }
}

Module

class Module extends Model
{
    protected $with = ['module_items'];

    public function task()
    {
        return $this->belongsTo(Task::class);
    }

    public function module_items()
    {
        return $this->hasMany(ModuleItem::class);
    }
}

Polymorphic ModuleItem model

class ModuleItem extends Model
{
    public function typeable()
    {
        return $this->morphTo();
    }

    public function module()
    {
        return $this->belongsTo(Module::class);
    }
}

Polymorphic relation on Item

class Item extends Model
{
    public function module_item_types()
    {
        return $this->morphMany('App\ModuleItem', 'typeable');
    }
}

Polymorphic relation on Enemy

class Enemy extends Model
{
    public function module_item_types()
    {
        return $this->morphMany('App\ModuleItem', 'typeable');
    }
}

Upvotes: 0

Views: 2322

Answers (1)

Simantov Yousoufov
Simantov Yousoufov

Reputation: 112

Take a look at Illuminate\Database\Eloquent@morphMany:

/**
 * Define a polymorphic one-to-many relationship.
 *
 * @param  string  $related
 * @param  string  $name
 * @param  string  $type
 * @param  string  $id
 * @param  string  $localKey
 * @return \Illuminate\Database\Eloquent\Relations\MorphMany
 */
public function morphMany($related, $name, $type = null, $id = null, $localKey = null)
{
    $instance = new $related;

    // Here we will gather up the morph type and ID for the relationship so that we
    // can properly query the intermediate table of a relation. Finally, we will
    // get the table and create the relationship instances for the developers.
    list($type, $id) = $this->getMorphs($name, $type, $id);

    $table = $instance->getTable();

    $localKey = $localKey ?: $this->getKeyName();

    return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
}

If you follow the rabbit hole down through Illuminate\Database\Eloquent@getMorphs you'll see that the DB table names are inferred through the $name param if $type is null. Your column names are being inferred as typeable_id and typeable_type instead of morphable_id and morphable_type.

Once that's fixed, you should be able to access the ModuleItem's type like $module_item->typeable (or whatever you rename the relationship to). If you wanted to eager load the type instance you would update ModuleItem:

class ModuleItem extends Model
{
    protected $with = ['typeable'];

    public function typeable()
    {
        return $this->morphTo();
    }

    public function module()
    {
        return $this->belongsTo(Module::class);
    }
}

Upvotes: 1

Related Questions