Let's see how to search in the parent's fields using Laravel Scout and the database driver.
Let's say you have this relationship: users
x cats
. Each user has many cats (a "one-to-many" relationship):
class User extends Model
{
public function cats()
{
return $this->hasMany(Cat::class);
}
}
class Cat extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Both models (users and cats) have a name
field.
Let's say we want to get all cats with bob
in their names, using Laravel's Scout.
The standard solution is to add this to the Cat.php
model:
// Cat.php
use Searchable;
/**
* Get the indexable data array for the model.
*
* @return array
*/
public function toSearchableArray()
{
return [
'name' => $this->name,
];
}
And we search with Cat::search('bob')->get()
.
This solution works well, but what if we want to search in the user
fields?
Let's say we want to get cats owned by people with bob
in their names.
If you add this to the "Cat" model:
// Cat.php
use Searchable;
/**
* Get the indexable data array for the model.
*
* @return array
*/
public function toSearchableArray()
{
// we only need to return keys for the database driver
return [
'name' => '', // cat's name
'users.name' => '', // owner's name
];
}
It won't work. You will get this exception:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'users.name' in 'where clause'
SQL:
select `cats`.*
from `cats`
where (`cats`.`name` like %bob% or `users`.`name` like %bob%)
Clearly, the SQL is missing the users
table. But how to add it?
Doing a Cat::join(...)->search('bob')
will throw an exception, same for Cat::search(...)->join(...)
.
The question is: How to search in the parent attributes? And by "parent" I mean the "belongsTo" model.
The query
method allows for modifing the search query. Use it to inject a join
clause:
Cat::search('bob')->query(function ($builder) {
$builder->join('users', 'cats.user_id', '=', 'users.id');
})->get();
This generates the proper query:
SELECT `cats`.*
FROM `cats`
INNER JOIN `users` on `cats`.`genre_id` = `users`.`id`
WHERE (`cats`.`name` LIKE '%bob%' or `users`.`name` LIKE '%bob%')
ORDER BY `id` desc
To avoid having to write ->join(...)
on every search call, we can make Scout automatically add the JOIN:
// Cat.php
/**
* Overrides the "search" method to inject a `join` to the relationships.
*/
use Searchable {
Searchable::search as parentSearch;
}
/**
* Perform a search against the model's indexed data.
*
* @param string $query
* @param \Closure $callback
* @return \Laravel\Scout\Builder
*/
public static function search($query = '', $callback = null)
{
return static::parentSearch($query, $callback)->query(function ($builder) {
$builder->join('users', 'cats.user_id', '=', 'users.id');
});
}
Now, we just call Cat::search('bob')->get()
to search for cats named "bob" or with owners named "bob".
class Cat extends Model
{
/**
* Overrides the "search" method to inject a `join` to the relationships.
*/
use Searchable {
Searchable::search as parentSearch;
}
/**
* Get the indexable data array for the model.
*
* @return array
*/
public function toSearchableArray()
{
// no need to return values as the database engine only uses the array keys
return [
'name' => '', // cat's name
'users.name' => '', // owner's name
];
}
/**
* Perform a search against the model's indexed data.
*
* @param string $query
* @param \Closure $callback
* @return \Laravel\Scout\Builder
*/
public static function search($query = '', $callback = null)
{
return static::parentSearch($query, $callback)->query(function ($builder) {
$builder->join('users', 'cats.user_id', '=', 'users.id');
});
}
/**
* Relationships
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
And search with:
Cat::search('bob')->get();