Skip to content

Configure Searchable Data

Tomas Norkūnas edited this page Apr 4, 2024 · 8 revisions

Indexing Data

You have to define the entities/documents that should be indexed to Meilisearch. You can add the entities/documents to your configuration file, under the meilisearch.indices key.

Each entry under the indices key must contain the following attributes:

  • name is the canonical name of the index in Meilisearch
  • class is the full class reference of the entity to index

For example, to index all posts:

meilisearch:
    indices:
      -   name: posts
          class: App\Entity\Post

Using Serializer Groups

Before sending your data to Meilisearch, each entity is converted to an array using the Symfony built-in serializer. This option lets you define what fields you want to index using the php attributes #[Groups(['searchable'])].

Example:

meilisearch:
    indices:
        -   name: posts
            class: App\Entity\Post
            enable_serializer_groups: true

Custom Serializer Groups

If you have already applied some groups and want to use them instead of searchable, you can additional provide serializer_groups option. Example:

meilisearch:
    indices:
        -   name: posts
            class: App\Entity\Post
            enable_serializer_groups: true
            serializer_groups: ['group_a', 'group_b']

Batching

By default, calls to Meilisearch to index or remove data are batched per 500 items. You can easily modify the batch size in your configuration.

meilisearch:
    batchSize: 250

The import command also follows this parameter to retrieve data via Doctrine. If you run out of memory while importing your data, use a smaller batchSize value.

Normalizers

By default, all entities are converted to an array with the built-in Symfony Normalizers (GetSetMethodNormalizer, DateTimeNormalizer, ObjectNormalizer...) which should be enough for simple use cases, but we encourage you to write your own Normalizer to have more control over what you send to Meilisearch, or to avoid circular references.

Symfony will use the first Normalizer in the array to support your entity or format. You can change the order in your service declaration.

Note that the normalizer is called with searchableArray format.

You have many choices on how to customize your records:

Using PHP Attributes

Probably the easiest way to choose which fields to the index is to use php attributes. This feature relies on the built-in ObjectNormalizer and its group feature.

Example:

Attributes require enable_serializer_groups to be set to true in the configuration.

<?php

namespace App\Entity;

use Symfony\Component\Serializer\Attribute\Groups;

class Post
{
    // ... Attributes and other methods ...

    #[Groups(['searchable'])]
    public function getTitle(): ?string
    {
        return $this->title;
    }

    #[Groups(['searchable'])]
    public function getSlug(): ?string
    {
        return $this->slug;
    }

    #[Groups(['searchable'])]
    public function getCommentCount(): ?int
    {
        return count($this->comments);
    }
}

Using normalize()

Another quick and easy way is to implement a reliable method that will return the entity as an array. This feature relies on the CustomNormalizer that ships with the serializer component.

Implement the Symfony\Component\Serializer\Normalizer\NormalizableInterface interface and write you normalize method.

Example:

<?php

namespace App\Entity;

use Symfony\Component\Serializer\Normalizer\NormalizableInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class Post implements NormalizableInterface
{
    public function normalize(NormalizerInterface $serializer, ?string $format = null, array $context = []): array
    {
        return [
            'title' => $this->getTitle(),
            'content' => $this->getContent(),
            'comment_count' => $this->getComments()->count(),
            'tags' => array_unique(array_map(function ($tag) {
              return $tag->getName();
            }, $this->getTags()->toArray())),

            // Reuse the $serializer
            'author' => $serializer->normalize($this->getAuthor(), $format, $context),
            'published_at' => $serializer->normalize($this->getPublishedAt(), $format, $context),
        ];
    }
}

Handle Multiple Formats

In case you are already using this method for something else, like encoding entities into JSON for instance, you may want to use a different format for both use cases. You can rely on the format to return different arrays.

public function normalize(NormalizerInterface $serializer, ?string $format = null, array $context = []): array
{
    if (\Meilisearch\Bundle\Searchable::NORMALIZATION_FORMAT === $format) {
        return [
            'title' => $this->getTitle(),
            'content' => $this->getContent(),
            'author' => $this->getAuthor()->getFullName(),
        ];
    }

    // Or if it's not for search
    return ['title' => $this->getTitle()];
}

Using a Custom Normalizer

You can create a custom normalizer for any entity. The following snippet shows a simple CommentNormalizer. Normalizer must implement Symfony\Component\Serializer\Normalizer\NormalizerInterface interface.

<?php
namespace App\Serializer\Normalizer;

use App\Entity\User;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class UserNormalizer implements NormalizerInterface
{
    /**
     * Normalize a user into a set of arrays/scalars.
     */
    public function normalize(mixed $object, ?string $format = null, array $context = []): array
    {
        return [
            'id'       => $object->getId(),
            'username' => $object->getUsername(),
        ];
    }

    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
    {
        return $data instanceof User;

        // Or if you want to use it only for indexing
        // return $data instanceof User && Searchable::NORMALIZATION_FORMAT === $format;
    }
}

Then we need to tag our normalizer to add it to the default serializer. In your service declaration, add the following.

In YAML:

services:
    user_normalizer:
        class: App\Serializer\Normalizer\UserNormalizer
        tag: serializer.normalizer
        public: false

In XML:

<services>
    <service id="user_normalizer" class="App\Serializer\Normalizer\UserNormalizer" public="false">
        <tag name="serializer.normalizer" />
    </service>
</services>

The beauty is that, by following the above example, the Author of the Post will be converted with this normalizer.