vendor/friendsofsymfony/elastica-bundle/src/Doctrine/Listener.php line 82

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the FOSElasticaBundle package.
  4.  *
  5.  * (c) FriendsOfSymfony <https://friendsofsymfony.github.com/>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace FOS\ElasticaBundle\Doctrine;
  11. use Doctrine\Persistence\Event\LifecycleEventArgs;
  12. use FOS\ElasticaBundle\Persister\ObjectPersister;
  13. use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
  14. use FOS\ElasticaBundle\Provider\IndexableInterface;
  15. use Psr\Log\LoggerInterface;
  16. use Symfony\Component\PropertyAccess\PropertyAccess;
  17. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  18. /**
  19.  * Automatically update ElasticSearch based on changes to the Doctrine source
  20.  * data. One listener is generated for each Doctrine entity / ElasticSearch type.
  21.  */
  22. class Listener
  23. {
  24.     /**
  25.      * Objects scheduled for insertion.
  26.      */
  27.     public array $scheduledForInsertion = [];
  28.     /**
  29.      * Objects scheduled to be updated or removed.
  30.      */
  31.     public array $scheduledForUpdate = [];
  32.     /**
  33.      * IDs of objects scheduled for removal.
  34.      */
  35.     public array $scheduledForDeletion = [];
  36.     /**
  37.      * Object persister.
  38.      */
  39.     protected ObjectPersisterInterface $objectPersister;
  40.     /**
  41.      * PropertyAccessor instance.
  42.      */
  43.     protected PropertyAccessorInterface $propertyAccessor;
  44.     /**
  45.      * Configuration for the listener.
  46.      */
  47.     private array $config;
  48.     private IndexableInterface $indexable;
  49.     public function __construct(
  50.         ObjectPersisterInterface $objectPersister,
  51.         IndexableInterface $indexable,
  52.         array $config = [],
  53.         ?LoggerInterface $logger null
  54.     ) {
  55.         $this->config \array_merge([
  56.             'identifier' => 'id',
  57.             'defer' => false,
  58.         ], $config);
  59.         $this->indexable $indexable;
  60.         $this->objectPersister $objectPersister;
  61.         $this->propertyAccessor PropertyAccess::createPropertyAccessor();
  62.         if ($logger && $this->objectPersister instanceof ObjectPersister) {
  63.             $this->objectPersister->setLogger($logger);
  64.         }
  65.     }
  66.     /**
  67.      * Handler for the "kernel.terminate" and "console.terminate" Symfony events.
  68.      * These event are subscribed to if the listener is configured to persist asynchronously.
  69.      */
  70.     public function onTerminate()
  71.     {
  72.         if ($this->config['defer']) {
  73.             $this->config['defer'] = false;
  74.             $this->persistScheduled();
  75.             $this->config['defer'] = true;
  76.         }
  77.     }
  78.     /**
  79.      * Looks for new objects that should be indexed.
  80.      */
  81.     public function postPersist(LifecycleEventArgs $eventArgs)
  82.     {
  83.         $entity $eventArgs->getObject();
  84.         if ($this->objectPersister->handlesObject($entity) && $this->isObjectIndexable($entity)) {
  85.             $this->scheduledForInsertion[] = $entity;
  86.         }
  87.     }
  88.     /**
  89.      * Looks for objects being updated that should be indexed or removed from the index.
  90.      */
  91.     public function postUpdate(LifecycleEventArgs $eventArgs)
  92.     {
  93.         $entity $eventArgs->getObject();
  94.         if ($this->objectPersister->handlesObject($entity)) {
  95.             if ($this->isObjectIndexable($entity)) {
  96.                 $this->scheduledForUpdate[] = $entity;
  97.             } else {
  98.                 // Delete if no longer indexable
  99.                 $this->scheduleForDeletion($entity);
  100.             }
  101.         }
  102.     }
  103.     /**
  104.      * Delete objects preRemove instead of postRemove so that we have access to the id.  Because this is called
  105.      * preRemove, first check that the entity is managed by Doctrine.
  106.      */
  107.     public function preRemove(LifecycleEventArgs $eventArgs)
  108.     {
  109.         $entity $eventArgs->getObject();
  110.         if ($this->objectPersister->handlesObject($entity)) {
  111.             $this->scheduleForDeletion($entity);
  112.         }
  113.     }
  114.     /**
  115.      * Iterate through scheduled actions before flushing to emulate 2.x behavior.
  116.      * Note that the ElasticSearch index will fall out of sync with the source
  117.      * data in the event of a crash during flush.
  118.      *
  119.      * This method is only called in legacy configurations of the listener.
  120.      *
  121.      * @deprecated This method should only be called in applications that depend
  122.      *             on the behaviour that entities are indexed regardless of if a
  123.      *             flush is successful
  124.      */
  125.     public function preFlush()
  126.     {
  127.         $this->persistScheduled();
  128.     }
  129.     /**
  130.      * Iterating through scheduled actions *after* flushing ensures that the
  131.      * ElasticSearch index will be affected only if the query is successful.
  132.      */
  133.     public function postFlush()
  134.     {
  135.         $this->persistScheduled();
  136.     }
  137.     /**
  138.      * Determines whether or not it is okay to persist now.
  139.      *
  140.      * @return bool
  141.      */
  142.     private function shouldPersist()
  143.     {
  144.         return !$this->config['defer'];
  145.     }
  146.     /**
  147.      * Persist scheduled objects to ElasticSearch
  148.      * After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls.
  149.      */
  150.     private function persistScheduled()
  151.     {
  152.         if ($this->shouldPersist()) {
  153.             if (\count($this->scheduledForInsertion)) {
  154.                 $this->objectPersister->insertMany($this->scheduledForInsertion);
  155.                 $this->scheduledForInsertion = [];
  156.             }
  157.             if (\count($this->scheduledForUpdate)) {
  158.                 $this->objectPersister->replaceMany($this->scheduledForUpdate);
  159.                 $this->scheduledForUpdate = [];
  160.             }
  161.             if (\count($this->scheduledForDeletion)) {
  162.                 $this->objectPersister->deleteManyByIdentifiers($this->scheduledForDeletion);
  163.                 $this->scheduledForDeletion = [];
  164.             }
  165.         }
  166.     }
  167.     /**
  168.      * Record the specified identifier to delete. Do not need to entire object.
  169.      *
  170.      * @param object $object
  171.      */
  172.     private function scheduleForDeletion($object)
  173.     {
  174.         if ($identifierValue $this->propertyAccessor->getValue($object$this->config['identifier'])) {
  175.             $this->scheduledForDeletion[] = !\is_scalar($identifierValue) ? (string) $identifierValue $identifierValue;
  176.         }
  177.     }
  178.     /**
  179.      * Checks if the object is indexable or not.
  180.      *
  181.      * @param object $object
  182.      *
  183.      * @return bool
  184.      */
  185.     private function isObjectIndexable($object)
  186.     {
  187.         return $this->indexable->isObjectIndexable(
  188.             $this->config['indexName'],
  189.             $object
  190.         );
  191.     }
  192. }