【问题标题】:Symfony2 FOSElasticaBundle update index for all entities related to the entity updatedSymfony2 FOSElasticaBundle 更新与更新实体相关的所有实体的索引
【发布时间】:2014-02-20 13:48:32
【问题描述】:

我在我的项目中使用 FOSElasticaBundle 和 Doctrine,我的代码适用于使用 Doctrine 生命周期事件的选择性索引更新。我遇到的问题是我是否单独更新相关实体。

例如,一个人可能通过多对多关系与一家公司相关联。如果我直接通过公司实体更新公司名称,那么与公司相关的人的索引将过期,仍然与公司的旧名称相关。

我有点迷茫如何处理这个问题,有人有什么建议吗?我是否必须依赖计划的索引更新并同时处理不准确的索引数据,或者有什么方法可以调用与已更新实体相关的实体的更新。

我依靠 JMSSerializer 组来建立映射。我明白这可能不是长期做事的最佳方式。

【问题讨论】:

    标签: symfony doctrine symfony-2.4 foselasticabundle


    【解决方案1】:

    我也遇到了同样的问题。不过,我的安装(Symfony 2.5.4 和 FOSElastica 3.0.4)似乎与您的安装有很大不同。因此,使代码正常工作存在一些问题。我正在发布我的解决方案,因为它可能对其他开发人员有用。

    监听器不在 FOS\ElasticaBundle\Doctrine\ORM\ 中,而是在 FOS\ElasticaBundle\Doctrine 中。所以你必须使用那个。 我还必须使用 Doctrine\Common\EventArgs 而不是 Doctrine\ORM\Event\LifecycleEventArgs,因为否则我自己的 postUpdate 方法与 BaseListener 中的方法不兼容。

    在我的应用程序中,一个课程(研讨会)可以有很多会话,但在这个项目中,elastica 只会使用这些会话。该应用程序需要知道与课程会话相关的课程的一些详细信息。所以,这是我的代码:

    在 config.yml 中,我的 elastica bundle 配置如下所示:

    fos_elastica:
        clients:
            default: { host: localhost, port: 9200 }
        indexes:
            courses:
                index_name: courses
                types:
                    session:
                        mappings:
                            id: ~
                            name: ~
                            course:
                                type: "nested"
                                properties:
                                    id: ~
                                    name: ~
    

    再进一步,还在config.yml中

    services:
         # some other services here
    
         fos_elastica.listener.courses.course:
             class: XXX\CourseBundle\EventListener\ElasticaCourseListener
             arguments:
                 - @fos_elastica.object_persister.courses.course
                 - ['postPersist', 'postUpdate', 'preRemove']
                 - @fos_elastica.indexable
             calls:
                 - [ setContainer, ['@service_container', @fos_elastica.object_persister.courses.session ] ]
             tags:
                 - { name: 'doctrine.event_subscriber' }
    

    我自己的监听器(XXX\CourseBundle\EventListener\ElasticaCourseListener)看起来像这样:

    <?php
    
    namespace XXX\CourseBundle\EventListener;
    
    use Doctrine\Common\EventArgs;
    use FOS\ElasticaBundle\Doctrine\Listener as BaseListener;
    use FOS\ElasticaBundle\Persister\ObjectPersister;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    use XXX\CourseBundle\Entity\Course;
    
    class ElasticaCourseListener extends BaseListener
    {
        private $container;
        private $objectPersisterSession;
    
        public function setContainer(ContainerInterface $container, ObjectPersister $objectPersisterSession)
        {
            $this->container = $container;
            $this->objectPersisterSession = $objectPersisterSession;
        }
    
        public function postUpdate(EventArgs $args)
        {
            $entity = $args->getEntity();
    
            if ($entity instanceof Course) {
                $this->scheduledForUpdate[] = $entity;
                foreach ($entity->getSessions() as $session) {
                    $this->objectPersisterSession->replaceOne($session);
                }
            }
        }
    }
    

    现在,当我更新课程时,它将在 ElasticSearch 中更新为嵌套对象;-)

    【讨论】:

    • @maercky 我刚刚更新了 FOSElastica 并使用您的代码作为指南。谢谢!!
    • 在 FOSElastica 3.0.0.alpha6 中我不得不使用第四个参数更新服务定义 - { indexName: 'courses', typeName: 'course'} 以避免 php 通知和测试错误
    • 这 - 不幸的是 - 仅适用于 FOSElasticaBundle 3.0 版本。 3.1 版本似乎使用了不同的侦听器架构。
    • 我注意到 3.1 中的 BC 重大更改。我将在另一个 ElasticSearch 项目中更新我的答案。
    • 我有 "friendsofsymfony/elastica-bundle": "~3.0" 和:[Symfony\Component\Debug\Exception\ContextErrorException] 运行时通知:Artel\SiteBundle\EventListener\ElasticaCourseListener::postUpdate 声明() 应该兼容 FOS\ElasticaBundle\Doctrine\Listener::postUpdate(Doctrine\Common\Persistence\Event\LifecycleEventArgs $eventArgs)
    【解决方案2】:

    我想我已经在这个页面上找到了解决方案https://groups.google.com/forum/#!topic/elastica-php-client/WTONX-zBTI4 谢谢卡夏诺

    基本上,您需要扩展 FOS\ElasticaBundle\Doctrine\ORM\Listener,以便您可以查找相关实体,然后也更新它们的索引。

    class CompanyListener extends BaseListener
    {
    
        /** @var \Symfony\Component\DependencyInjection\ContainerInterface */
        private $container;
    
        public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container) {
            $this->container = $container;
        }
    
        protected function initialiseJob() {
            $this->objectPersisterJob = $this->container->get('fos_elastica.object_persister.application.job');
            $this->em = $this->container->get('doctrine')->getEntityManager(); //maybe move this to postUpdate function so it can be used for all
        }
    
        /**
         * @param \Doctrine\ORM\Event\LifecycleEventArgs $eventArgs
         */
        public function postUpdate(LifecycleEventArgs $eventArgs)
        {
            /** @var $entity Story */
            $entity = $eventArgs->getEntity();
    
            if ($entity instanceof $this->objectClass) {
                if ($this->isObjectIndexable($entity)) {
                    $this->objectPersister->replaceOne($entity);
                    $this->initialiseJob();
                    foreach ($entity->getJobOpenings() as $job) {
                        $this->objectPersisterJob->replaceOne($job);
                    }
                } else {
                    $this->scheduleForRemoval($entity, $eventArgs->getEntityManager());
                    $this->removeIfScheduled($entity);
                }
            }
        }
    
        public function preRemove(\Doctrine\Common\EventArgs $eventArgs)
        {
            $entity = $eventArgs->getEntity();
    
            if ($entity instanceof $this->objectClass) {
    
                $this->scheduleForDeletion($entity);
                $this->initialiseJob();
                foreach ($entity->getJobOpenings() as $job) {
                    $this->objectPersisterJob->replaceOne($job);
                }
            }
        }
    
    
    }
    

    以及您的服务定义如下

    fos_elastica.listener.application.company:
        class: 'xxx\RMSBundle\EventListener\CompanyListener'
        arguments:
            - '@fos_elastica.object_persister.application.company'
            - 'xxx\RMSBundle\Entity\Company'
            - ['postPersist', 'postUpdate', 'postRemove', 'preRemove']
            - id
        calls:
            - [ setContainer, [ '@service_container' ] ]
        tags:
            - { name: 'doctrine.event_subscriber' }
    

    这将更新两者的索引:-)

    【讨论】:

    • 能否将您的 config.yml elastica 配置与上述代码一起添加?我正在关注您的解决方案并且遗漏了一些东西。不知何故,它对我不起作用。
    【解决方案3】:

    我正在使用 FosElastica 3.1.0,我尝试了 Julien Rm 提供的解决方案但没有成功:-(

    经过多天的研究,终于找到了解决办法 here

    $persister = $this->get('fos_elastica.object_persister.jaiuneidee.post');
    $persister->insertOne($post);
    

    希望对您有所帮助!

    【讨论】:

      【解决方案4】:

      通过所有 cmets 和我的研究,我为使用 fosElastica 的自动索引子对象制作了一个通用 Gist:

      https://gist.github.com/Nightbr/ddb586394d95877dde8ed7445c51d973

      事实上,我覆盖了 FOSElastica 的默认侦听器并添加了function updateRelations($entity)。我们将搜索链接到$entity的所有关系,如果在ES中有索引(ES类型存在),它将更新相关文档。

      如果有人想看看它并做出任何改进,那就太好了! ^^

      提前致谢

      【讨论】:

      • 谢谢。看起来很棒。我倾向于使用scheduledForInsertion,因为scheduledForUpdate 不能很好地将值更新为null,至少在我使用的版本中没有。我认为它也可能快一点。
      • 我正在使用带有 FOSElastica 分支的 ES 2.3:github.com/fazland/FOSElasticaBundle
      【解决方案5】:

      抱歉,我无法在您的回答下发表评论,但解决方案中缺少某些内容。您也必须覆盖 preRemove。

      public function preRemove(\Doctrine\Common\EventArgs $eventArgs)
      {
          $entity = $eventArgs->getEntity();
      
      
      
          if ($entity instanceof $this->objectClass) {
      
              $this->scheduleForDeletion($entity);
              $this->initialiseJob();
              foreach ($entity->getJobOpenings() as $job) {
                      $this->objectPersisterJob->replaceOne($job);
                  }
          }
      }
      

      【讨论】:

        【解决方案6】:

        随着 FosElastica 3.1.0 的 BC Break #729,情况发生了变化,上面的代码不起作用:

        BC BREAK: Removed Doctrine\Listener#getSubscribedEvents. The container configuration now configures tags with the methods to call to avoid loading this class on every request where doctrine is active. #729
        

        对于那些试图使其与 FOSElastica 3.1.X 一起工作的人,这是我在持久化/更新/删除嵌套实体时如何设法使嵌套对象被索引到其父级中的 Elastic Search :

        定义服务监听器:

        fos_elastica.listener.entity.nested:
            class: XX\CoreBundle\EventListener\EventSubscriber\ElasticaNestedListener
            arguments:
                - @fos_elastica.object_persister.app.entityname
                - @fos_elastica.indexable
                - {"indexName" : "app", "typeName": "entityname"}
            tags:
                - { name: 'doctrine.event_subscriber' }
        

        创建监听器:

        <?php
        class ElasticaNestedListener implements EventSubscriber
        { // some indentations missing!
        
        public function getSubscribedEvents()
        {
            return array(
                'postPersist',
                'preRemove',
                'postUpdate',
                'preFlush',
                'postFlush',
            );
        }
        
        /**
         * Object persister.
         *
         * @var ObjectPersisterInterface
         */
        protected $objectPersister;
        
        /**
         * Configuration for the listener.
         *
         * @var array
         */
        private $config;
        
        /**
         * Objects scheduled for insertion.
         *
         * @var array
         */
        public $scheduledForInsertion = array();
        
        /**
         * Objects scheduled to be updated or removed.
         *
         * @var array
         */
        public $scheduledForUpdate = array();
        
        /**
         * IDs of objects scheduled for removal.
         *
         * @var array
         */
        public $scheduledForDeletion = array();
        
        /**
         * PropertyAccessor instance.
         *
         * @var PropertyAccessorInterface
         */
        protected $propertyAccessor;
        
        /**
         * @var IndexableInterface
         */
        private $indexable;
        
        /**
         * Constructor.
         *
         * @param ObjectPersisterInterface $objectPersister
         * @param IndexableInterface       $indexable
         * @param array                    $config
         * @param LoggerInterface          $logger
         */
        public function __construct(
            ObjectPersisterInterface $objectPersister,
            IndexableInterface $indexable,
            array $config = array(),
            LoggerInterface $logger = null
        ) {
            $this->config = array_merge(array(
                    'identifier' => 'id',
                ), $config);
            $this->indexable = $indexable;
            $this->objectPersister = $objectPersister;
            $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
        
            if ($logger && $this->objectPersister instanceof ObjectPersister) {
                $this->objectPersister->setLogger($logger);
            }
        }
        
        
        
        /**
         * Looks for objects being updated that should be indexed or removed from the index.
         *
         * @param LifecycleEventArgs $eventArgs
         */
        public function postUpdate(LifecycleEventArgs $eventArgs)
        {
            $entity = $eventArgs->getObject();
        
            if ($entity instanceof EntityName) {
        
                $question = $entity->getParent();
                if ($this->objectPersister->handlesObject($question)) {
                    if ($this->isObjectIndexable($question)) {
                        $this->scheduledForUpdate[] = $question;
                    } else {
                        // Delete if no longer indexable
                        $this->scheduleForDeletion($question);
                    }
                }
            }
        
        
        }
        
        
        public function postPersist(LifecycleEventArgs $eventArgs)
        {
            $entity = $eventArgs->getObject();
        
            if ($entity instanceof EntityName) {
                $question = $entity->getParent();
                if ($this->objectPersister->handlesObject($question)) {
                    if ($this->isObjectIndexable($question)) {
                        $this->scheduledForUpdate[] = $question;
                    } else {
                        // Delete if no longer indexable
                        $this->scheduleForDeletion($question);
                    }
                }
            }
        
        
        }
        
        
        /**
         * Delete objects preRemove instead of postRemove so that we have access to the id.  Because this is called
         * preRemove, first check that the entity is managed by Doctrine.
         *
         * @param LifecycleEventArgs $eventArgs
         */
        public function preRemove(LifecycleEventArgs $eventArgs)
        {
            $entity = $eventArgs->getObject();
        
            if ($this->objectPersister->handlesObject($entity)) {
                $this->scheduleForDeletion($entity);
            }
        }
        
        /**
         * Persist scheduled objects to ElasticSearch
         * After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls.
         */
        private function persistScheduled()
        {
            if (count($this->scheduledForInsertion)) {
                $this->objectPersister->insertMany($this->scheduledForInsertion);
                $this->scheduledForInsertion = array();
            }
            if (count($this->scheduledForUpdate)) {
                $this->objectPersister->replaceMany($this->scheduledForUpdate);
                $this->scheduledForUpdate = array();
            }
            if (count($this->scheduledForDeletion)) {
                $this->objectPersister->deleteManyByIdentifiers($this->scheduledForDeletion);
                $this->scheduledForDeletion = array();
            }
        }
        
        /**
         * Iterate through scheduled actions before flushing to emulate 2.x behavior.
         * Note that the ElasticSearch index will fall out of sync with the source
         * data in the event of a crash during flush.
         *
         * This method is only called in legacy configurations of the listener.
         *
         * @deprecated This method should only be called in applications that depend
         *             on the behaviour that entities are indexed regardless of if a
         *             flush is successful.
         */
        public function preFlush()
        {
            $this->persistScheduled();
        }
        
        /**
         * Iterating through scheduled actions *after* flushing ensures that the
         * ElasticSearch index will be affected only if the query is successful.
         */
        public function postFlush()
        {
            $this->persistScheduled();
        }
        
        /**
         * Record the specified identifier to delete. Do not need to entire object.
         *
         * @param object $object
         */
        private function scheduleForDeletion($object)
        {
            if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) {
                $this->scheduledForDeletion[] = $identifierValue;
            }
        }
        
        /**
         * Checks if the object is indexable or not.
         *
         * @param object $object
         *
         * @return bool
         */
        private function isObjectIndexable($object)
        {
            return $this->indexable->isObjectIndexable(
                $this->config['indexName'],
                $this->config['typeName'],
                $object
            );
        }
        }
        

        EntityName 可以是评论,getParent() 可以是拥有该评论的文章...

        希望有帮助!

        【讨论】:

        • 朱利安:一个问题!对于实体Article,哪个函数getParent() 必须返回null?谢谢漂亮!
        【解决方案7】:

        我使用的是 Symphony 3 和 FOSElasticaBundle 3.2,但我做的事情有点不同。 在查看了其他答案中给出的代码后,这很有帮助,我决定不扩展默认侦听器。相反,我让它做它的事情,我只是添加了我自己的监听器。

        我有一些类别(1)可以有多个(多对多)主题(2)可以有多个(一对多) 帖子 (3)。 帖子是保存在 Elasticsearch 中的实体,其中包含有关其各自主题及其自己的类别的信息。

        像这样:

        fos_elastica:
          #...
          indexes:
            my_index:
              #...
              types:
                post: # (3)
                  mappings:
                    field_one: ~
                    # ... Other fields
                    subject: # (2)
                      type: "object"
                      properties:
                        subject_field_one: ~
                        # ... Other fields
                        categories: # (1)
                          type: "nested"
                          properties:
                            category_field_one: ~
                            # ... Other fields
        

        服务定义(app/config/services.yml)

        services:
          # ...
          app.update_elastica_post.listener:
            class: AppBundle\EventListener\UpdateElasticaPostListener
            arguments: ['@service_container']
            tags:
              - { name: doctrine.event_listener, event: postUpdate }
        

        以及监听器 AppBundle\EventListener\UpdateElasticaPostListener.php

        namespace AppBundle\EventListener;
        
        use Doctrine\ORM\Event\LifecycleEventArgs;
        use Symfony\Component\DependencyInjection\ContainerInterface;
        
        use AppBundle\Entity\Category;
        use AppBundle\Entity\Subject;
        
        class UpdateElasticaPostListener
        {
            private $container;
            private $objectPersisterPost;
        
            public function __construct(ContainerInterface $container)
            {
                $this->container = $container;
                $this->objectPersisterPost = null;
            }
        
            /**
             * @param \Doctrine\ORM\Event\LifecycleEventArgs $eventArgs
             */
            public function postUpdate(LifecycleEventArgs $eventArgs)
            {
                $this->checkAndUpdate($eventArgs);
            }
        
            protected function checkAndUpdate(LifecycleEventArgs $eventArgs)
            {
                $entity = $eventArgs->getEntity();
        
                if ($entity instanceof Category) {
                    foreach ($entity->getSubjects() as $subject) {
                        $this->updateSubjectPosts($subject);
                    }
                } elseif ($entity instanceof Subject) {
                    $this->updateSubjectPosts($entity);
                }
            }
        
            protected function updateSubjectPosts(Subject $subject)
            {
                $this->initPostPersister();
                foreach ($subject->getPosts() as $post) {
                    $this->objectPersisterPost->replaceOne($post);
                }
            }
        
            protected function initPostPersister()
            {
                if (null === $this->objectPersisterPost) {
                    // fos_elastica.object_persister.<index_name>.<type_name>
                    $this->objectPersisterPost = $this->container->get('fos_elastica.object_persister.my_index.post');
                }
            }
        }
        

        就是这样!我没有为 remove 事件尝试它,现在我想起来了,也许这个解决方案不是最好的解决方案......但也许它是......

        非常感谢上面的@Ben Stinton 和@maercky。

        希望对你有帮助! (这是我在这里的第一个答案,所以我希望我没有搞砸)

        【讨论】:

          猜你喜欢
          • 2019-04-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多