Skip to content
Advertisement

Select specific column from morph relation according to type in Laravel

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.

Advertisement

Answer

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);
}
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement