Fos elastica: обновить данные ES на вложенном объекте

Я работаю над проектом Symfony2 с fos:elastica. Пользователи могут ставить лайки и подписываться на места, которые им нравятся. По умолчанию, когда пользователю нравится место, мы автоматически добавляем действие «подписаться».

Итак, прежде всего, я сохраняю эти 2 действия в базе данных (doctrine2). Мои данные правильно сохраняются в БД.

Когда я делаю запрос ES с индексом «user_action», я получаю все действия, связанные с местом (предыдущее и следующее).

Но когда я делаю то же самое с индексом «место», я получаю только первое действие (например).

Кажется, ES не удается обновить userAction в объекте предложения.

С другой стороны, если я удаляю запрос, который сохраняет «следующее» действие (добавляется автоматически после лайка), и я делаю второй вызов (через API), мое действие сохраняется в БД, а также обновляется на месте объекта.

Надеюсь, кто-нибудь поймет, что я сказал и что я пытаюсь сделать ^^

Отображение

Место

place:
     mappings:
      id:
       type: integer
      userAction:
       type: nested
       properties:
         userId:
           type: integer
         userActionTypeId:
           type: integer
         userActionType:
           type: nested
           properties:
             name:
               type: string

Действие пользователя

user_action:
    mappings:
          userId:
            type: integer
          placeId: ~
          place:
            type: nested
            properties:
              id:
                type: integer
          userActionType:
            type: nested
            properties:
              name: ~

Слушатель

Услуга

`fos_elastica.listener.place.user_action`:
   class: API\Rest\v1\PlaceBundle\EventListener\ElasticaUserActionListener
   arguments:
       - @fos_elastica.object_persister.search.user_action
       - ['postPersist', 'postRemove']
       - @fos_elastica.indexable
   calls:
       - [ setContainer, ['@service_container', @fos_elastica.object_persister.search.place ] ]
   tags:
       - { name: 'doctrine.event_subscriber' }

Класс

<?php

namespace API\Rest\v1\PlaceBundle\EventListener;

use Doctrine\Common\EventArgs;
use FOS\ElasticaBundle\Doctrine\Listener as BaseListener;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use Symfony\Component\DependencyInjection\ContainerInterface;
use API\Rest\v1\UserActionBundle\Entity\UserAction;

class ElasticaUserActionListener extends BaseListener
{
    private $container;
    private $objectPersisterSuggestion;

    public function setContainer(ContainerInterface $container, ObjectPersister $objectPersisterSuggestion)
    {
        $this->container                 = $container;
        $this->objectPersisterPlace = $objectPersisterPlace;
    }

    public function postPersist(EventArgs $eventArgs)
    {
        $entity = $eventArgs->getEntity();

        if ($entity instanceof UserAction) {
            $this->scheduledForInsertion[] = $entity;
            $this->objectPersisterPlace->replaceOne($entity->getPlace());
        }
    }

    public function postRemove(EventArgs $eventArgs)
    {
        $entity = $eventArgs->getEntity();

        if ($entity instanceof UserAction) {
            $this->scheduleForDeletion($entity);
            $this->objectPersisterPlace->replaceOne($entity->getPlace());
        }
    }
}

Текущий результат

Получить место (#320) с вложенным объектом userAction

query : http://localhost:9200/search/place/_search
"hits" : [
    {
        "_index" : "search",
        "_type" : "place",
        "_id" : "320",
        "_score" : 5.7004805,
        "_source":{
            "userAction":[
                {
                    "userActionType":{
                        "name":"like"
                    },
                    "userActionTypeId":3,
                    "userId":3
                }
            ]
        }
    }
]

Получить действие пользователя, связанное с местом с идентификатором # 320

query : http://localhost:9200/search/user_action/_search
"hits" : [
    {
        "_index" : "search",
        "_type" : "user_action",
        "_id" : "50",
        "_score" : 5.7004805,
        "_source" : {
            "userId" : 4,
            "placeId" : 320,
            "userActionType" : {
                "name" : "following"
                }
            }
    },
    {
        "_index" : "search",
        "_type" : "user_action",
        "_id" : "49",
        "_score" : 5.402646,
        "_source" : {
            "userId" : 4,
            "placeId" : 320,
            "userActionType" : {
                "name" : "like"
            }
        }
    }
]

ОБНОВЛЕНИЕ (решение)

Наконец-то я нахожу правильный способ сделать это. Я слишком рано сбрасывал данные.

Я заменил этот неправильный код

$em = $this->getEntityManager();
$em->persist($like);
$em->flush();  

$em = $this->getEntityManager();
$em->persist($following);
$em->flush();  

by

$em = $this->getEntityManager();
$em->persist($like);
$em->persist($following);  
$em->flush();

и это работает!!!!

Надеюсь, это может помочь кому-то.


person Anthony    schedule 13.03.2015    source источник
comment
Вы можете использовать профилировщик Symfony, чтобы проверить, какие запросы ES выполняются во время определенных запросов Symfony. Это должно помочь вам узнать, что здесь происходит.   -  person Jakub Matczak    schedule 13.03.2015
comment
Я только что обновил свой пост с решением. Я слишком поздно увидел твой совет. Однако, спасибо   -  person Anthony    schedule 13.03.2015


Ответы (1)


Что хорошо работает для меня с FOSElasticaBundle 3.0.x для обновления вложенных объектов, как правило, следующее:

Добавьте это в app/config/config.yml

services:
    # your other services

    fos_elastica.listener.search.user_action:
        class: "Acme\Bundle\UserActionBundle\EventListener\ElasticaUserActionListener"
        arguments:
            - @fos_elastica.object_persister.search.user_action
            - ['postPersist', 'postInsert', 'postUpdate', 'preRemove']
            - @fos_elastica.indexable
            - { indexName: 'user_action', typeName: 'userAction'}
        calls:
            - [ setContainer, ['@service_container', @fos_elastica.object_persister.search.user_action] ]
        tags:
            - { name: 'doctrine.event_subscriber' }

Затем создайте новый файл в UserActionBundle\EventListener:

# Acme\Bundle\UserActionBundle\EventListener\ElasticaUserActionListener.php
namespace Acme\Bundle\UserActionBundle\EventListener;

use Doctrine\Common\EventArgs;
use FOS\ElasticaBundle\Doctrine\Listener as BaseListener;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use Symfony\Component\DependencyInjection\ContainerInterface;

class ElasticaUserActionListener extends BaseListener
{
    private $container;
    private $objectPersisterUserAction;

    public function setContainer(ContainerInterface $container, ObjectPersister $objectPersisterUserAction)
    {
        $this->container = $container;
        $this->objectPersisterUserAction = $objectPersisterUserAction;
    }

    public function getClassAccessorArray() {
        return [
            [
                'accessor' => 'getPlace', 
                'class' => new Place(),
            ],
            // add more child objects here, if needed
        ];
    }

    public function postPersist(EventArgs $args)
    {
        $entity = $args->getEntity();

        foreach ($this->getClassAccessorArray() as $accessorArray) {
            $class = $accessorArray['class'];
            $accessorFunction = $accessorArray['accessor'];
            if ($entity instanceof $class) {
                $this->scheduledForInsertion[] = $entity;
                $this->objectPersisterUserAction->insertOne($entity->$accessorFunction());
                break;
            }
        }
    }

    public function postUpdate(EventArgs $args)
    {
        $entity = $args->getEntity();

        foreach ($this->getClassAccessorArray() as $accessorArray) {
            $class = $accessorArray['class'];
            $accessorFunction = $accessorArray['accessor'];
            if ($entity instanceof $class) {
                $this->scheduledForUpdate[] = $entity;
                $this->objectPersisterUserAction->replaceOne($entity->$accessorFunction());
                break;
            }
        }
    }

    public function preRemove(EventArgs $eventArgs) {
        $entity = $eventArgs->getEntity();

        foreach ($this->getClassAccessorArray() as $accessorArray) {
            $class = $accessorArray['class'];
            $accessorFunction = $accessorArray['accessor'];

            if ($entity instanceof $class) {
                $this->scheduleForDeletion($entity);
                $this->objectPersisterUserAction->deleteOne($entity->$accessorFunction());
            }
        }
    }
}

Это хорошо работает для обновления индекса ElasticSearch при непосредственном обновлении вложенных объектов. В вашем случае, поскольку у вас есть фактически двунаправленная связь между Places и UserActions, вы можете либо расширить класс Listener для обработки обоих классов, либо просто продублировать и создать также службу и класс ElasticaPlaceListener.

person likeitlikeit    schedule 10.06.2015