My First Assignment: Creating an Elastica Adapter for the Sonata Admin list view

Daphné Popin
Daphné PopinNovember 19, 2013
#integration#php

Just like every marmelab recruit, my first weeks are spent working on various new technologies (Node.js, ElasticSearch, MongoDb, etc) with real use cases.

My first mission: Power the Sonata Admin list view and filters by an ElasticSearch index to speed up navigation.

I've created a bundle, it can be found on GitHub.

Here's the README intro:

The Sonata Admin Bundle provides a web UI to many types of persistence (RDBMS, MongoDB, PHPCR), some of which have limited query capabilities. If you already have an ElasticSearch index for a given model, this bundle allows you to use this index instead of the native repository query system. This may provide a great performance boost, depending on your data structure and indexes.

 

Sonata Elastica Bundle

Sonata Admin with no DB query

First Start: Bad Direction

It was clear that the configuration should be in the entity admin service's definition.

At first, I tried to add a new manager_type=elastica in the sonata.admintag. I wanted to override each class used by the admin bundle and/or elastica. I thought I must get rid of any little dependency on doctrine or propel.

That was a mistake.

First, because there was so many classes to override, that I almost wanted to give up.

Second, because my goal was to display a list view in the admin bundle with no query in database. I wanted to be able to filter, sort and paginate the elements, using the ElasticSearch index. But I did not want to override the detail view, the batch actions, ... So, depending on the action, I needed to keep the 'normal' behaviour.

Second Start: Better Understanding

For the second version, the idea was to keep the default manager_type attribute and to add new parameters in the sonata.admin tag. Just override what needs to be overridden, KISS as they said.

The goal was simple: to use the elastica index, just add two parameters in the tag: searcher="elastica" & search_index="index.name".

I needed to use proxy classes that do the job when we need it, and let the ORM do the rest.

I had to create services for each entity to display from the ElasticSearch index. As I didn't want the potential user of the bundle to have to write a bunch of services for each admin class, I have created my first CompilerPass.

Quick explanations

I defined an ElasticaModelManager that is used over the orm ModelManager. This proxy has a baseManager attribute that corresponds to the orm ModelManager. It’s linked to the admin class thanks to the CompilerPass:

$adminService->addMethodCall('setModelManager', array(new Reference($proxyManagerServiceId)));

Most of the time it returns what the ORM does, except for the createQuery method, where it creates a new ElasticaProxyQuery object.

This class implements the ProxyQueryInterface, and stores an instance of ElasticaProxyRepository, which is used to query the ElasticSearch index.

This is a real shortcut to explain the link between the admin class and the index, but if anyone is interested in more details, the code is open :)

Impressions

Though I've already worked a little with Symfony and Sonata, I did only use them to do pretty basic stuff. Working on this bundle helped me to better understand some concepts such as the DIC, or to use the debug_batcktrace() function !

I've also discovered the CompilerPass, a very powerful tool to override and create on demand services. But with great power comes... sometimes a bad headache.

It was also my first contribution to an open source project. I think it's just awesome. When I had a problem on a Sonata bundle, my PR has been merged in a few hours. And I had feedbacks on this bundle immediately after the repo is public!

I know that it is far for complete and/or finished (and/or totally functional?) but I had fun working on it and I hope it will be improved, used and helpful!