Styphon Styphon - 4 months ago 28
PHP Question

Laravel polymorph relation query looking for the wrong column

I'm trying to create my first polymorphic relationship and have followed the documentation on the website and checked with several tutorials online that all follow a similar theme. I have 3 tables, Pages, Menu and Menu Items. Menu items will always be a child of Menu, and will either be a page (in the case of a link) or another menu (in the case of a sub-menu). My tables are:

Pages

+----+------+----------+------+--------+-----------+-----------------+------------+------------+------------+
| id | name | url_slug | file | layout | seo_title | seo_description | created_at | updated_at | deleted_at |
+----+------+----------+------+--------+-----------+-----------------+------------+------------+------------+


Menus

+----+------+------------+------------+
| id | name | created_at | updated_at |
+----+------+------------+------------+


Menu Items

+----+---------+-------------+---------------+-------+------+-------+------------+------------+
| id | menu_id | itemable_id | itemable_type | label | left | right | created_at | updated_at |
+----+---------+-------------+---------------+-------+------+-------+------------+------------+


So for menu items table the
menu_id
is the parent of the menu item, it has a one to many relationship with the menus table.
itemable_id
and
itemable_type
are used in the polymophic relation,
itemable_id
should reference the
id
column in either Menus or Pages depending on what
itemable_type
is, which will be the Menu or Page model.

To create this relationship I've added methods to my models like this:

Page

/**
* Fetch the menu items this page is linked to.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function menuItems()
{
return $this->morphMany('App\Modules\Menu\Models\MenuItem', 'itemable');
}


Menu

/**
* Fetch all child menu items belonging to this menu.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function childItems()
{
return $this->hasMany('App\Modules\Menu\Models\MenuItem');
}

/**
* Fetch all menu items linked to this menu.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphMany
*/
public function menuItems()
{
return $this->morphMany('App\Modules\Menu\Models\MenuItem', 'itemable');
}


Menu Item

/**
* Fetch the menu this item belongs to.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function menu()
{
return $this->belongsTo('App\Modules\Menu\Models\Menu');
}

/**
* Fetch the models this menu item links to.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function itemable()
{
return $this->morphTo();
}


I've created all the tables and I'm now trying to seed them. My seed looks like this:

public function run()
{
$menu = Menu::create([
'name' => 'Main'
]);

foreach ( [
[
'page_id' => 1,
'label' => 'Home',
'left' => 1,
'right' => 2
],
[
'page_id' => 2,
'label' => 'About Us',
'left' => 3,
'right' => 4
]
] as $arr )
{
/* @var $page Page */
$page = Page::findOrFail($arr['page_id']);
$item = new MenuItem($arr);
$page->menuItems()->save($item);
$item->menu()->associate($menu);
}
}


But I keep getting errors when I run it about the
page_id
column not existing. The exact error is

[Illuminate\Database\QueryException]
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'page_id' in 'field list' (SQL: insert into `menu_items` (`page_id`, `label`, `left`, `right`, `itemable_type`, `itemable_id`, `updated_at`, `created_at`) values (1, Home, 1, 2, App\Models\Page, 1, 2016-08-03 12:32:21, 2016-08-03 12:32:21))


Everything about the query is fine, except it's trying to populate
page_id
as well as the itemable columns. Why is it doing this? How can I get around it?

Answer

Try changing page_id to itemable_id

[
  'page_id' => 1, //change this to itemable_id
  'label' => 'Home',
  'left' => 1,
  'right' => 2
],
[
  'page_id' => 2, //change this to itemable_id
  'label' => 'About Us',
  'left' => 3,
  'right' => 4
]

Because in following line ,

$item = new MenuItem($arr);

When you are trying to insert the the $arr in MenuItem it is expecting itemable_id and not page_id
So your updated code would be like this:

public function run()
{
    $menu = Menu::create([
        'name' => 'Main'
    ]);

    foreach ( [
                  [
                      'itemable_id' => 1,
                      'label' => 'Home',
                      'left' => 1,
                      'right' => 2
                  ],
                  [
                      'itemable_id' => 2,
                      'label' => 'About Us',
                      'left' => 3,
                      'right' => 4
                  ]
              ] as $arr )
    {
        /* @var $page Page */
        $page = Page::findOrFail($arr['itemable_id']);
        $item = new MenuItem($arr);
        $page->menuItems()->save($item);
        $item->menu()->associate($menu);
    }
}
Comments