Advanced
Aggregators - multiple entities types in the one index
An Aggregator is a clean way to implement site-wide search amongst multiple entities. In other words, it allows you to have multiple entities types in the one index.
Defining Aggregators
To create a new aggregator, add a new class to your entities directory, App\Entity
in this example, with the following content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Algolia\SearchBundle\Entity\Aggregator;
/**
* @ORM\Entity
*/
class News extends Aggregator
{
/**
* Returns the entities class names that should be aggregated.
*
* @return string[]
*/
public static function getEntities()
{
return [
Post::class,
Comment::class,
];
}
}
Remember, the method getEntities
should return the entities classes names that should be aggregated.
Finally, add the new aggregator class name into the algolia configuration file algolia_search.yml
:
1
2
3
- indices:
- name: news
class: App\Entity\News
Searching
An aggregator is a standard entity class, and, as usual, you may begin searching entities on the aggregator using the search
on
your Index Manager service:
1
2
3
4
5
6
7
$indexManager->index($post, $objectManager);
$indexManager->index($comment, $objectManager);
$results = $indexManager->search('query', News::class, $objectManager);
// $results[0] contains a \App\Entity\Post.
// $results[1] contain a \App\Entity\Comment.
Be careful, the $result
array may contain different types of entities instances.
If you want to get the raw results from Algolia, use the rawSearch
method. However, this time, each
result may contain a different structure:
1
$results = $indexManager->rawSearch('', News::class);
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\\Entity\\Article::1",
...
},
{
"id": 1,
"content": "Comment content",
"objectID": "App\\Entity\\Comment::1",
...
},
...
]
}
To ensure that each result has a similar structure, you may need to implement
the method normalize
on each entity or override it on the aggregator class.
Indexing conditionally
Conditional indexing on Aggregators works just like a normal entity, using the index_if
key. You
may want to use $this->entity
to have access to the current entity being aggregated. Here is an example using the method approach:
1
2
3
4
- indices:
- name: news
class: App\Entity\News
index_if: isPublished
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @ORM\Entity
*/
class News extends Aggregator
{
// ...
public function isPublished()
{
// Index only published articles.
if ($this->entity instanceof Article) {
return $this->entity->isPublished();
}
// If is not an article, index anyway.
return true;
}
}
For more information on conditional indexing, check out the documentation
.
Using Algolia Client
In some cases, you may want to access the Algolia client directly to perform advanced operations (like manage API keys, manage indices and such).
By default, the AlgoliaSearch\Client
is not public in the container, but you can easily expose it.
In the service file of your project, config/services.yaml
in a typical Symfony 4 app,
you can alias it and make it public with the following code:
1
2
3
4
services:
algolia.client:
alias: algolia_client
public: true
Or in XML
1
2
3
<services>
<service id="algolia.client" alias="algolia_client" public="true" />
</services>
Example
Here is an example of how to use the client after your registered it publicly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class TestController extends Controller
{
protected $indexManager;
public function __construct(IndexManagerInterface $indexingManager)
{
$this->indexManager = $indexingManager;
}
public function testAction()
{
$algoliaClient = $this->get('algolia.client');
var_dump($algoliaClient->listIndexes());
$index = $algoliaClient->initIndex(
$this->indexManager->getFullIndexName(Post::class)
);
var_dump($index->listApiKeys());
die;
}
}
Other engines
Everything related to Algolia is contained in the AlgoliaEngine
class, hence it’s
easy to use the bundle with another search engine. It also allows you to write
your own engine if you want to do things differently.
Using another Engine
Considering that you have this AnotherEngine
class implementing the EngineInterface
,
and you want to use it, you can override the service search.engine
definition
in your config/services.yaml
this way:
1
2
3
services:
search.engine:
class: Algolia\SearchBundle\Engine\AnotherEngine
Or in XML
1
2
3
<services>
<service id="search.engine" class="Algolia\SearchBundle\Engine\AnotherEngine" />
</services>
About the NullEngine
The package ships with a \Algolia\SearchBundle\Engine\NullEngine
engine class. This engine implements the EngineInterface
interface and returns an empty array, zero or null depending on the methods.
This is a great way to make sure everything works, without having to call Algolia.
You can use it for your tests but also in a dev environment.
About the AlgoliaSyncEngine
In Algolia, all indexing operations are asynchronous. The API will return a taskID and you can check if this task is completed or not, via another API endpoint.
For test purposes, we use the AlgoliaSyncEngine. It will always wait for task to be completed
before returning. This engine is only auto-loaded during the tests. If you use it in your
project, you can copy it into your app and modify the search.engine
service definition.