ORM Examples#

Doctrine ORM#

It is very common to use an ORM like Doctrine to manage your business objects. This cookbook will show you how to connect Doctrine ORM lifecycle events to automatically update your Search Engine indexes.

Create a ReindexProvider#

At first, a ReindexProvider is required for the newly created index.

Note

See Reindex operations Documentation for more information about creating a reindex provider and how to call it in the different frameworks.

Make also sure you are familiar with basics of SEAL, see Getting Started.

<?php

class BlogReindexProvider implements ReindexProviderInterface
{
    public function __construct(private BlogRepository $entityRepository)
    {}

    public static function getIndex(): string
    {
        return 'blog';
    }

    public function total(): ?int
    {
        return $this->entityRepository->count([]);
    }

    public function provide(ReindexConfig $reindexConfig): \Generator
    {
        $entities = $this->loadEntities(ReindexConfig $reindexConfig);

        foreach ($entities as $entity) {
            // map entity to your index structure
            // keep best practices in mind your index structure should be optimized for search
            // and not represent the same domain / entity model as in your ORM
            yield [
                'id' => (string) $entity->getId(),
                'title' => $entity->getTitle(),
                'content' => [
                    $entity->getIntroduction(),
                    $entity->getDescription(),
                ],
            ];
        }
    }

    /**
     * @return \Generator<Blog>
     */
    private function loadEntities(ReindexConfig $reindexConfig): \Generator
    {
        $queryBuilder = $this->entityRepository->createQueryBuilder('entity');

        $identifiers = $reindexConfig->getIdentifiers();

        if ($identifiers !== []) { // for partial updates by our doctrine listener
            $queryBuilder
                ->andWhere('entity.id IN (:ids)')
                ->setParameter('ids', \array_map('intval', $identifiers));
        }

        return $queryBuilder->getQuery()->toIterable();
    }
}

Create a DoctrineListener#

Now create a Doctrine Listener which listens to the lifecycle events of your entity. We are interested in newly created, modified and deleted entities.

We collect all entities in an array and on postFlush we trigger the reindex operation.

This will call our previously created ReindexProvider to update the index.

<?php

class BlogSearchDoctrineListener implements ResetInterface
{
    /**
     * @var Blog[]
     */
    private array $entities = [];

    public function __construct(
        private EngineInterface $engine,
        private iterable $reindexProviders,
    ) {}

    public function preRemove(LifecycleEventArgs $eventArgs): void
    {
        $this->entities[] = $eventArgs->getObject();
    }

    public function preUpdate(LifecycleEventArgs $eventArgs): void
    {
        $this->entities[] = $eventArgs->getObject();
    }

    public function prePersist(LifecycleEventArgs $eventArgs): void
    {
        $this->entities[] = $eventArgs->getObject();
    }

    public function postFlush(PostFlushEventArgs $args): void
    {
        if ($this->entities === []) {
            return;
        }

        $reindexConfig = new ReindexConfig()
            ->withIndex('blog')
            ->withIdentifiers(\array_map(fn(Blog $entity) => (string) $entity->getId(), $this->entities));

        // The following will update the index synchronously.
        // If you want todo it asynchronously you can use ``Symfony Messenger`` or any queue system
        // to create an async message/command and handler and do the following line in the async handler.
        $engine->reindex($reindexProviders, $reindexConfig);

        $this->reset();
    }

    public function onClear(OnClearEventArgs $args): void
    {
        $this->reset();
    }

    public function reset(): void
    {
        $this->entities = [];
    }
}

Depending on your used framework it is required to register the above DoctrineListener as a service correctly via tags or configuration. See your framework doctrine integration documentation for it.