Integrations / Frameworks / Symfony / Customizing
Mar. 01, 2019

Customizing

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 Algolia, 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:

The following features are only supported with the default Symfony serializer, not with JMS serializer.

Using annotations

Probably the easiest way to choose which attribute to index is to use annotation. If you used the bundle before version 3, it’s very similar. This feature relies on the built-in ObjectNormalizer and its group feature.

Example based on a simplified version of this Post entity:

Annotations requires enable_serializer_groups to be true in the configuration. Read more

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php

namespace App\Entity;

use Symfony\Component\Serializer\Annotation\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 dedicated 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 your normalize method.

Example based on a simplified version of this Post entity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?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, $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.

1
2
3
4
5
6
7
8
9
10
11
12
13
public function normalize(NormalizerInterface $serializer, $format = null, array $context = []): array
{
    if (\Algolia\SearchBundle\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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
// src/Serializer/Normalizer/UserNormalizer.php (SF4)
// or src/AppBundle/Serializer/Normalizer/UserNormalizer.php (SF3)

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($object, $format = null, array $context = [])
    {
        return [
            'id'       => $object->getId(),
            'username' => $object->getUsername(),
        ];
    }

    public function supportsNormalization($data, $format = null)
    {
        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:

1
2
3
4
5
services:
    user_normalizer:
        class: App\Serializer\Normalizer\UserNormalizer
        tag: serializer.normalizer
        public: false # false by default in Symfony4

In XML:

1
2
3
4
5
<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.

Ordering Normalizers

Because Symfony will use the first normalizer that supports your entity or format, you will want to pay close attention to the order.

The ObjectNormalizer is registered with a priority of -1000 and should always be last. All normalizers registered by default in Symfony are between -900 and -1000 and the CustomNormalizer is registered at -800.

All your normalizers should be above -800. Default priority is 0.

If this doesn’t suit you, the priority can be changed in your service definition.

In YAML:

1
2
3
4
5
services:
    serializer.normalizer.datetime:
        class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer
        name: serializer.normalizer
        priority: -100

In XML:

1
2
3
4
5
6
<services>
    <service id="serializer.normalizer.datetime" class="Symfony\Component\Serializer\Normalizer\DateTimeNormalizer">
        <!-- Run before serializer.normalizer.object -->
        <tag name="serializer.normalizer" priority="-100" />
    </service>
</services>

Did you find this page helpful?