I have a very simple One-to-Many polymorphic relationship as following.

posts
    id - integer
    title - string

videos
    id - integer
    title - string

comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

Below is how the relationship in the models looks like:

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

    class Post extends Model
    {
        public function comments()
        {
            return $this->morphMany('App\Comment', 'commentable');
        }
    }

    class Video extends Model
    {
        public function comments()
        {
            return $this->morphMany('App\Comment', 'commentable');
        }
    }

I use Laravel Backpack CRUD to manage comments. In the CommentCrudController::setup() function, I add columns as following:

public function setup() {

    // Standard CrudPanel Basic Information goes here...

    $this->crud->addColumn([
        'name' => 'body',
        'label' => 'Comment',
    ]);

    $this->crud->addColumn([
        'label' => "Related Entity",
        'type' => "select", // Need to use type 'select' for property of related model.
        'name' => 'commentable_id ', 
        'entity' => 'commentable', // Function name, defining the polymorphic relationship.
        'attribute' => "title", // The column name of the related database table.
    ]);

} 

With above configuration in the CommentCrudController::setup() function, I get a comments overview table as normal. But when I tried to search for something using Backpack search form, I've got the following error:

{
  message: "Please use whereHasMorph() for MorphTo relationships."
  trace: [
    {file: vendor/backpack/crud/src/PanelTraits/Search.php", line: 91}
  ]
}

I do understand that because it is a One-To-Many polymorphic relation with morphTo() function and in the CommentCrudController::setup() function I used 'type' => "select" to add the title of the "Related Entity" as column, when trying to search for records Laravel Backpack will try to execute the following query inside the trait Search::applySearchLogicForColumn() function:

    if ($column['tableColumn']) {
        switch ($columnType) {
            // Skipped other cases here...

            case 'select':
            case 'select_multiple':
                $query->orWhereHas($column['entity'], function ($q) use ($column, $searchTerm) {
                    $q->where($column['attribute'], 'like', '%'.$searchTerm.'%');
                });
                break;

            // Skipped default case here...
        }

As the error said, $query->orWhereHas() should be changed to $query->whereHasMorph(). But changing this does not solve the problem. And I also want to be able to search in the title column of the related model.
How can I fix this?

1

There are 1 best solutions below

1
On

I have found solution by overriding the following three functions in the CommentCrudController:

search()
applySearchTerm($searchTerm)
applySearchLogicForColumn($query, $searchTerm)

In the search() function, I preserved everthing, since it has things to do with pagination, sorting etc. Except calling $this->crud->applySearchTerm() function. I changed it to call $this->applySearchTerm() which I also overrided in the CommentCrudController.

Because in my use case, this operation works with specific database tables and columns, I could override the applySearchTerm($searchTerm) function as following:

public function applySearchTerm($searchTerm)
{
    return $this->crud->query->where(function ($query) use ($searchTerm) {
        $this->applySearchLogicForColumn($query, $searchTerm);
    });
}

And the solution happens by overriding the applySearchLogicForColumn($query, $searchTerm) function as following:

public function applySearchLogicForColumn($query, $searchTerm) 
{
    $query->orWhere('body', 'LIKE', '%' . $searchTerm . '%')
          ->orWhereHasMorph(
              'commentable',
              [
                   Post::class,
                   Video::class,
              ], 
              function (Builder $query) use ($searchTerm) {
                   $query->where('title', 'LIKE', '%' . $searchTerm . '%')
              }
          );
} 

In case, you have another associated morph model with a different column name than title e.g.

posts
    id - integer
    title - string

videos
    id - integer
    title - string

users
    id - integer
    full_name - string  // table 'users' with 'full_name' column where you also want to apply search term to.

comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

You can override the applySearchLogicForColumn($query, $searchTerm) function as following:

public function applySearchLogicForColumn($query, $searchTerm) 
{
    $query->orWhere('body', 'LIKE', '%' . $searchTerm . '%')
          ->orWhereHasMorph(
              'commentable',
              [
                   Post::class,
                   Video::class,
              ], 
              function (Builder $query) use ($searchTerm) {
                   $query->where('title', 'LIKE', '%' . $searchTerm . '%')
              }
          )
          ->orWhereHasMorph(
              'commentable',
              [
                   User::class,
              ], 
              function (Builder $query) use ($searchTerm) {
                   $query->where('full_name', 'LIKE', '%' . $searchTerm . '%')
              }
          );
}