I am trying to write a query that selects columns from a model then selects some columns from a morph relationship table. But I have no idea to select columns, and relation tables have different columns. So some column has no slug, some have.
public function index()
{
$menus = Menu::whereActive(true)
->with([
'menuable' => function ($q) {
// This gives error if there is no relation Pages model
$q->whereActive(true)->select('pages.id', 'pages.slug');
// Below not working
// if($q->type === Page::class){
// $q->whereActive(true)->select('pages.id', 'pages.slug');
// } else if($q->type === Category::class){
// $q->whereActive(true)->select('categories.id',
'categories.slug');
// }
}
])
->get(['id', 'menuable_id', 'menuable_type', 'name']);
$response = [
'menus' => $menus,
];
return $this->sendResponse($response);
}
Models
class Menu extends Model
{
public function menuable()
{
return $this->morphTo();
}
}
class Page extends Model
{
public function menu()
{
return $this->morphOne(Menu::class, 'menuable');
}
}
class Category extends Model
{
public function menu()
{
return $this->morphOne(Menu::class, 'menuable');
}
}
How can I select specific columns from morph relation with checking morph type? I am using Laravel version 8.
The polymorphic relation is something the Eloquent aware of, and DBMS hasnot implemented this feature in it. so there cannot be a sql query which join a table to another tables based on the morph column.
so you have to use distinct queries for every polymorphic join relation on your models:
//you can retrieve distinct menu based on their relation
Menu::whereActive(true)->hasPage()->with('pages');
//and having the ralations in the menu model:
public function posts
Menu::whereActive(true)->hasCategory();
//scope in menu class can be like:
public function scopePage($query){
return $query->where('menuable_type',Page::class);
}
public function scopeCategory($query){
return $query->where('menuable_type',Category::class);
}
//with these you can eager load the models
Menu::whereActive(true)->hasPage()->with('page');
Menu::whereActive(true)->hasCategory()->with('category');
public function page(){
return $this->belongsTo(Page::class);
}
public functioncategory(){
return $this->belongsTo(Category::class);
}
if you want a common interface to use one of these dynamically. you can use:
$menu->menuable->id
$menu->menuable->slug
I am not sure which columns you want to response, but as i can guess from your question, I suppose you want id and slug from both models.
public function index(){
$pagedMenu = Menu::whereActive(true)->hasPage()->with('page');
$categoriedMenu = Menu::whereActive(true)->hasCategory()->with('category');
$menues = $pagedMenu->merge($categoriedMenu);
$response = [
'menus' => $menus,
];
return $this->sendResponse($response);
}
You can perfom separate filters based on the morph-class. This can be achieved with the whereHasMorph (https://laravel.com/docs/8.x/eloquent-relationships#querying-morph-to-relationships).
If you need to automatically resolve the relationship you can use with. This will preload morphables automatically into your resulting collection.
The following example uses an orWhere with 2 separate queries per morphable.
$menus = Menu::whereActive(true)
->whereHasMorph('menuable', [Page::class], function (Builder $query) {
// perform any query based on page-related entries
})
->orWhereHasMorph('menuable', [Category::class], function (Builder $query) {
// perform any query based on category-related entries
})
->with('menuable')
;
An alternative way is to pass both classes to the second argument. In that case you can check the type inside your closure.
$menus = Menu::whereActive(true)
->whereHasMorph('menuable', [Page::class, Category::class], function (Builder $query, $type) {
// perform any query independently of the morph-target
// $q->where...
if ($type === (new Page)->getMorphClass()) {
// perform any query based on category-related entries
}
if ($type === (new Category)->getMorphClass()) {
// perform any query based on category-related entries
}
})
->with('menuable')
If required you can also preload nested relationships. (https://laravel.com/docs/8.x/eloquent-relationships#nested-eager-loading-morphto-relationships)
$menus = Menu::whereActive(true)
->with(['menuable' => function (MorphTo $morphTo) {
$morphTo->morphWith([
Page::class => ['calendar'],
Category::class => ['tags'],
]);
}])