Search code examples
phpsymfonyelasticafoselasticabundle

FOSElasticaBundle: Is it possible to change "query_builder_method" in controller?


According to FOSElasticaBundle documentation it is possible to configure application to use custom query builder method like this:

user:
   persistence:
       elastica_to_model_transformer:
           query_builder_method: createSearchQueryBuilder

But is it possible to choose QB method live, e.g. in controller action?

I'd like to be able to control what's being fetched from DB while transforming Elastica results to Doctrine entities. E.g. sometimes I'll want to do eager fetch on some relations, but can't do that by default.


Solution

  • Since FOSElasticaBundle documentation is not very precise, I went through its code and found it impossible to control what query builder is used on controller level.

    It is possible to change whole elastica_to_model_transformer to a custom service, but still it's statically defined in configuration. Maybe with some dirty solution it would be possible going this way, but I don't think it's worth it.

    I decided to just not using this feature of FOSElasticaBundle. The main problem I had was that when you use fos_elastica.index instead of fos_elastica.finder or elastica repository (in order to get plain not transformed results Elastica\Resultset), there's no findPaginated method with returns Pagerfanta paginator object, which is very helpful in my case.

    Fortunately although it's not mentioned in documentation it's possible to create the Pagerfanta this way too, but a little bit more manually.

    Here's a code snippet:

    //generate ElaticaQuery somehow.
    $browseQuery = $browseData->getBrowseQuery(); 
    
    $search = $this->container->get('fos_elastica.index.indexName.typName');
    //create pagerfanta's adapter manually
    $adapter = new \Pagerfanta\Adapter\ElasticaAdapterElasticaAdapter($search, $browseQuery);
    // now you can create the paginator too.
    $pager = new Pagerfanta($adapter);
    
    //do some paging work on it...
    $pager->setMaxPerPage($browseData->getPerPage());
    try {
        $pager->setCurrentPage($browseData->getPage());
    } catch(OutOfRangeCurrentPageException $e) {
        $pager->setCurrentPage(1);
    }
    
    //and get current page results. 
    /** @var Result[] $elasticaResults */
    $elasticaResults = $pager->getCurrentPageResults();
    
    // we have to grab ids manyally, but it's done the same way inside FOSElasticaBundle with previous approach
    $ids = array();
    foreach($elasticaResults as $elasticaResult) {
        $ids[] = $elasticaResult->getId();
    }
    
    //use regular Doctrine's repository to fetch Entities any way you want.
    $entities = $this->getDoctrine()->getRepository(MyEntity::class)->findByIdentifiers($ids);
    

    This actually has a few advantages. In general it gives you back control over your data and doesn't tie ElasticSearch with Doctrine. Therefore you can resign on fetching data from Doctrine if you have all needed data in ElasticSearch (if they are read only data of course). This lets you optimize your application performance but reducing amount of SQL queries.

    The code above may be wrapped with some kind of service in order to prevent making mess in controllers.