Integrations / Frameworks / Laravel / Multiple Models in One Index
Jul. 23, 2019

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'],
    ];
}

Did you find this page helpful?