How to set up a Parent Child relationship with FOSElasticaBundle

by Ben Stinton - 20th May 2015

Symfony2 FOSElastica ElasticSearch Back

Parent Child relationships in ElasticSearch have some big disadvantages, which mean they will never be as widely used as with a relational database. For example;
The child must exist on the same shared as the parent, therefore the parent must be created before the child. This make it impractical to have long chains of relationships.
They only support one parent, i.e. OneToMany. ManyToMany is not possible
it’s really hard to filter child results by a parent’s data, for example sorting a table of people by the name of the company they work for.
They can have a negative impact on query speed.
Even with this there are some use cases. For example a parent with many many children, if you need to use some of the parent data in a search of the child’s index then this can save time, especially if the children are quite large documents individually.

These settings apply to Symfony2.5, FOSElastica 3.0.9 and Elasticsearch 1.4.4. The first thing to do is to update your mapping with a _parent field declaration under the child, specifying parent type, the name of the property in the child that points to the parent, and the identifier in the parent.

Here is the relevent section from my mapping:

  2. #fos elastica
  3. fos_elastica:
  4.   indexes:
  5.   index:
  6.   index_name: index_%kernel.environment%
  7.   client: default
  8.   types:
  9.   company:
  10.   mappings:
  11.   id:
  12.   type: "long"
  13.   job:
  14.   _parent:
  15.   type: company
  16.   property: company
  17.   identifier: id
  18. #_routing:
  19. # required: true
  20. # path: company

After this make sure you populate the parent index first, but as soon as you try to run the populate command for the the child index, Kerblamm!! You get the following error:

  1. [Elastica\Exception\ResponseException]
  2. RoutingMissingException[routing is required for [index_v2]/[job]/[1]]

At first I thought I needed to set the _routing field declaration (as commented out above). But this didn’t fix the problem.

As alway the clue is in the docs.

Note that to create a document with a parent, you need to call setParent on the document rather than setting a _parent field. If you do this wrong, you will see a RoutingMissingException as Elasticsearch does not know where to store a document that should have a parent but does not specify it.

I ignored this initially as I hadn’t written any code creating a document, I let the populate command take care of this so assumed it would take care of parent child mappings. Not so. As the docs says you need to to use setParent on the Document. In order to do this I needed to setup a custom ModelToElasticaTransformer. This can be done by extending the ModelToElasticaAutoTransformer provided in the bundle in a class Alpha\RMSBundle\Transformer\JobToElasticaTransformer

In the transform() function I inserted a line:
  1. $document->setParent($object->getCompany()->getId());
  2. #just above
  3. return $document

Then you declare the service:
  1. alpha.transformers.model.job:
  2.   class: Alpha\RMSBundle\Transformer\JobToElasticaTransformer
  3.   calls:
  4. - [ setPropertyAccessor, ['@fos_elastica.property_accessor'] ]

Now you can use populate as normal, as long as you remember to populate the parent class first

Comments: Send us an Email