Completed
Pull Request — master (#1145)
by
unknown
06:24
created

Listener::postPersist()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

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