Multiple Models in One Index
Aggregators - Multiple models in one index
An aggregator is a clean way to implement site-wide search among multiple models. In other words, it allows you to have multiple models in the one index.
Defining aggregators
To create a new aggregator, use the scout:make-aggregator
Artisan command. This command will create a new aggregator class in the app/Search
directory:
$
php artisan scout:make-aggregator News
Don’t worry if this directory does not exist in your application since it will be created the first time you run the command.
After generating your aggregator, you should fill in the $models
property
of the class, which will be used to identify the models that should be aggregated:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace App\Search;
use Algolia\ScoutExtended\Searchable\Aggregator;
class News extends Aggregator
{
/**
* The names of the models that should be aggregated.
*
* @var string[]
*/
protected $models = [
\App\Event::class,
\App\Article::class,
];
}
To register an Aggregator, use the bootSearchable
method on the aggregator you
wish to register. For this, you should use the boot
method of one of your
service providers. In this example, we’ll register the aggregator in
the App\AppServiceProvider
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace App\Providers;
use App\Search\News;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
News::bootSearchable();
}
}
Conditional sync
For searchable
models, you can use the shouldBeSearchable
method to conditionally change which results you want to index.
For aggregators, you can define a shouldBeSearchable
method that calls the shouldBeSearchable
method of the aggregated model.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class News extends Aggregator
{
/**
* The names of the models that should be aggregated.
*
* @var string[]
*/
protected $models = [
\App\Event::class,
\App\Article::class,
];
public function shouldBeSearchable()
{
// Check if the class uses the Searchable trait before calling shouldBeSearchable
if (array_key_exists(Searchable::class, class_uses($this->model))) {
return $this->model->shouldBeSearchable();
}
}
}
You can modify the logic of your aggregator’s shouldBeSearchable
method with any condition that does not depend on its
grouped models.
Searching
An aggregator is a standard searchable
class, and, as usual, you may begin searching models on the aggregator using the search
method:
1
2
3
4
$models = App\Search\News::search('Star Trek')->get();
echo get_class($models[0]); // "App\Article"
echo get_class($models[1]); // "App\Comment"
Be careful, the $models
array may contain different types of models instances.
If you want to get the raw results from Algolia, use the raw
method. However, this time, each
result may contain a different structure:
1
$results = App\Search\News::search('Star Trek')->raw();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"hits":[
{
"id":1,
"title": "Article title",
"slug": "article-title",
"content": "Article content",
"objectID":"App\\Article::1",
...
},
{
"id": 1,
"content": "Comment content",
"objectID": "App\\Comment::1",
...
},
],
...
}
To ensure that each result have a similar structure, you may need to implement the method toSearchableArray
on the
each searchable
classes or directly on the aggregator class.
Eager Loading
Sometimes you may wish to eager load
a relationship of an aggregated model. On those cases, you
need to use the property $relations
if your aggregator class. Here’s an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class News extends Aggregator
{
/**
* The names of the models that should be aggregated.
*
* @var string[]
*/
protected $models = [
\App\Event::class,
\App\Article::class,
];
/**
* Map of model relations to load.
*
* @var string[]
*/
protected $relations = [
\App\Event::class => ['user'],
\App\Article::class => ['photo', 'user'],
];
}