Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

Default listener settings of FOSElasticaBundle is set to automatically update elasticsearch index in real time when an object is added, updated or removed. It is set as listener: ~ in the config file. However, this only applies to parent object so if you have one to many relationship then the child object will never get indexed. This example solves this problem.

In this example, there are 'Post' and 'Comment' entities (1-to-n) and we will do insert, update and delete operations on both object individually and also together with doctrine then manually update elasticsearch index with an event subscriber.

Note: Rather than using this version, I would suggest you to use shorter event listener version that I have in this blog.


Config


fos_elastica:
clients:
default: { host: 127.0.0.1, port: 9200 }
indexes:
post_index:
client: default
index_name: post_dev
types:
post:
mappings:
id:
type: integer
index: not_analyzed
title:
type: string
analyzer: english
year:
type: integer
index: not_analyzed
price:
type: double
index: not_analyzed
is_published:
type: boolean
index: not_analyzed
comment:
type: object
properties:
id:
type: integer
index: not_analyzed
message:
type: string
persistence:
driver: orm
model: Application\SearchBundle\Entity\Post
finder: ~
provider: ~
listener: ~

PostService


namespace Application\SearchBundle\Service;

........
........

class PostDatabaseService
{
........
........

public function createPost()
{
$post = new Post();
$post->setTitle('inanzzz');
$post->setYear(1999);
$post->setPrice(6.99);
$post->setIsPublished(true);

$this->entityManager->persist($post);
$this->entityManager->flush();

return sprintf('create-post ID: %s', $post->getId());
}

public function updatePost($id)
{
/** @var Post $post */
$post = $this->findPost($id);
$post->setTitle('inanzzz inanzzz');

$this->entityManager->flush();

return sprintf('update-post ID: %s', $id);
}

public function deletePost($id)
{
/** @var Post $post */
$post = $this->findPost($id);

$this->entityManager->remove($post);
$this->entityManager->flush();

return sprintf('delete-post ID: %s', $id);
}

public function createChild($id)
{
/** @var Post $post */
$post = $this->findPost($id);

$comment = new Comment();
$comment->setMessage('inanzzz');
$comment->setPost($post);

$this->entityManager->persist($comment);
$this->entityManager->flush();

return sprintf('create-child ID: %s', $comment->getId());
}

public function updateChild($id)
{
/** @var Comment $comment */
$comment = $this->findComment($id);
$comment->setMessage('inanzzz inanzzz');

$this->entityManager->flush();

return sprintf('update-child ID: %s', $id);
}

public function deleteChild($id)
{
/** @var Comment $comment */
$comment = $this->findComment($id);

$this->entityManager->remove($comment);
$this->entityManager->flush();

return sprintf('delete-child ID: %s', $id);
}

public function createPostChild()
{
$post = new Post();
$post->setTitle('inanzzz');
$post->setYear(1999);
$post->setPrice(6.99);
$post->setIsPublished(true);

$comment = new Comment();
$comment->setMessage('inanzzz');
$comment->setPost($post);

$this->entityManager->persist($post);
$this->entityManager->persist($comment);
$this->entityManager->flush();

return sprintf('create-post-child ID-ID: %s-%s', $post->getId(), $comment->getId());
}

public function updatePostChild($pid, $cid)
{
/** @var Post $post */
$post = $this->findPost($pid);
$post->setTitle('inanzzz inanzzz');

/** @var Comment $comment */
$comment = $this->findComment($cid);
$comment->setMessage('inanzzz inanzzz');

$this->entityManager->flush();

return sprintf('update-post-child ID-ID: %s-%s', $pid, $cid);
}

private function findPost($id)
{
$post = $this->postRepository->findOneById($id);
if (!$post instanceof Post) {
throw new EntityNotFoundException(sprintf('Post [%s] cannot be found.', $id));
}

return $post;
}

private function findComment($id)
{
$comment = $this->commentRepository->findOneById($id);
if (!$comment instanceof Comment) {
throw new EntityNotFoundException(sprintf('Comment [%s] cannot be found.', $id));
}

return $comment;
}
}

PostIndexSubscriber


You should also study vendor/friendsofsymfony/elastica-bundle/Doctrine/Listener.php class to see how things actually work.


namespace Application\SearchBundle\Subscriber;

use Application\SearchBundle\Entity\Comment;
use Application\SearchBundle\Entity\Post;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Provider\IndexableInterface;

class PostIndexSubscriber implements EventSubscriber
{
/** @var ObjectPersisterInterface */
private $postPersister;
/** @var IndexableInterface */
private $indexable;
private $events;
private $config;
private $scheduledForInsert;
private $scheduledForUpdate;
private $scheduledForDelete;

public function __construct(
ObjectPersisterInterface $postPersister,
IndexableInterface $indexable,
array $events,
array $config
) {
$this->postPersister = $postPersister;
$this->indexable = $indexable;
$this->events = $events;
$this->config = $config;
}

public function getSubscribedEvents()
{
return $this->events;
}

public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getObject();

if ($entity instanceof Post) {
if ($this->postPersister->handlesObject($entity)) {
if ($this->isObjectIndexable($entity)) {
$this->scheduledForInsert = $entity;
}
}
}

$this->handleChild($entity);
}

public function postUpdate(LifecycleEventArgs $args)
{
$entity = $args->getObject();

if ($entity instanceof Post) {
if ($this->postPersister->handlesObject($entity)) {
if ($this->isObjectIndexable($entity)) {
$this->scheduledForUpdate = $entity;
}
}
}

$this->handleChild($entity);
}

public function preRemove(LifecycleEventArgs $args)
{
$entity = $args->getObject();

if ($entity instanceof Post) {
if ($this->postPersister->handlesObject($entity)) {
$this->scheduledForDelete = $entity->getId();
}
}

$this->handleChild($entity);
}

public function postFlush()
{
if ($this->scheduledForInsert) {
$this->postPersister->insertOne($this->scheduledForInsert);
$this->scheduledForInsert = null;
}

if ($this->scheduledForUpdate) {
$this->postPersister->replaceOne($this->scheduledForUpdate);
$this->scheduledForUpdate = null;
}

if ($this->scheduledForDelete) {
$this->postPersister->deleteById($this->scheduledForDelete);
$this->scheduledForDelete = null;
}
}

private function handleChild($entity)
{
if ($entity instanceof Comment) {
$post = $entity->getPost();
$post->removeComment($entity);
$post->addComment($entity);
if ($this->postPersister->handlesObject($post)) {
if ($this->isObjectIndexable($post)) {
$this->scheduledForUpdate = $post;
}
}
}
}

private function isObjectIndexable($object)
{
return $this->indexable->isObjectIndexable(
$this->config['index'],
$this->config['type'],
$object
);
}
}

Services.yml


services:
application_search.subscriber.post_index:
class: Application\SearchBundle\Subscriber\PostIndexSubscriber
arguments:
- @fos_elastica.object_persister.post_index.post
- @fos_elastica.indexable
- [ postPersist, postUpdate, preRemove, postFlush ]
- { index: post_index, type: post }
tags:
- { name: doctrine.event_subscriber }