Completed
Push — master ( 5a9d4b...696369 )
by Karel
34:22 queued 30:54
created

Listener::onKernelTerminate()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 4
nc 2
nop 0
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 11
    public function __construct(
75
        ObjectPersisterInterface $objectPersister,
76
        IndexableInterface $indexable,
77
        array $config = array(),
78
        LoggerInterface $logger = null
79
    ) {
80 11
        $this->config = array_merge(array(
81 11
            'identifier' => 'id',
82 11
            'async' => false,
83
            'defer' => false
84 11
        ), $config);
85 11
        $this->indexable = $indexable;
86 11
        $this->objectPersister = $objectPersister;
87 11
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
88
89 11
        if ($logger && $this->objectPersister instanceof ObjectPersister) {
90
            $this->objectPersister->setLogger($logger);
91
        }
92 11
    }
93
94
    /**
95
     * Handler for the "kernel.terminate" Symfony event. This event is subscribed to if the listener is configured to
96
     * persist asynchronously.
97
     */
98 1
    public function onKernelTerminate()
99
    {
100 1
        if($this->config['async'] && $this->config['defer']) {
101 1
            $this->config['defer'] = false;
102 1
            $this->persistScheduled();
103 1
        }
104 1
    }
105
106
    /**
107
     * Looks for new objects that should be indexed.
108
     *
109
     * @param LifecycleEventArgs $eventArgs
110
     */
111 3
    public function postPersist(LifecycleEventArgs $eventArgs)
112
    {
113 3
        $entity = $eventArgs->getObject();
114
115 3
        if ($this->objectPersister->handlesObject($entity) && $this->isObjectIndexable($entity)) {
116 2
            $this->scheduledForInsertion[] = $entity;
117 2
        }
118 3
    }
119
120
    /**
121
     * Looks for objects being updated that should be indexed or removed from the index.
122
     *
123
     * @param LifecycleEventArgs $eventArgs
124
     */
125 2
    public function postUpdate(LifecycleEventArgs $eventArgs)
126
    {
127 2
        $entity = $eventArgs->getObject();
128
129 2
        if ($this->objectPersister->handlesObject($entity)) {
130 2
            if ($this->isObjectIndexable($entity)) {
131 1
                $this->scheduledForUpdate[] = $entity;
132 1
            } else {
133
                // Delete if no longer indexable
134 1
                $this->scheduleForDeletion($entity);
135
            }
136 2
        }
137 2
    }
138
139
    /**
140
     * Delete objects preRemove instead of postRemove so that we have access to the id.  Because this is called
141
     * preRemove, first check that the entity is managed by Doctrine.
142
     *
143
     * @param LifecycleEventArgs $eventArgs
144
     */
145 2
    public function preRemove(LifecycleEventArgs $eventArgs)
146
    {
147 2
        $entity = $eventArgs->getObject();
148
149 2
        if ($this->objectPersister->handlesObject($entity)) {
150 2
            $this->scheduleForDeletion($entity);
151 2
        }
152 2
    }
153
154
    /**
155
     * Determines whether or not it is okay to persist now.
156
     *
157
     * @return bool
158
     */
159 8
    private function shouldPersist()
160
    {
161 8
        return !$this->config['defer'];
162
    }
163
164
    /**
165
     * Persist scheduled objects to ElasticSearch
166
     * After persisting, clear the scheduled queue to prevent multiple data updates when using multiple flush calls.
167
     */
168 8
    private function persistScheduled()
169
    {
170 8
        if($this->shouldPersist()) {
171 7
            if (count($this->scheduledForInsertion)) {
172 2
                $this->objectPersister->insertMany($this->scheduledForInsertion);
173 2
                $this->scheduledForInsertion = array();
174 2
            }
175 7
            if (count($this->scheduledForUpdate)) {
176 1
                $this->objectPersister->replaceMany($this->scheduledForUpdate);
177 1
                $this->scheduledForUpdate = array();
178 1
            }
179 7
            if (count($this->scheduledForDeletion)) {
180 3
                $this->objectPersister->deleteManyByIdentifiers($this->scheduledForDeletion);
181 3
                $this->scheduledForDeletion = array();
182 3
            }
183 7
        }
184 8
    }
185
186
    /**
187
     * Iterate through scheduled actions before flushing to emulate 2.x behavior.
188
     * Note that the ElasticSearch index will fall out of sync with the source
189
     * data in the event of a crash during flush.
190
     *
191
     * This method is only called in legacy configurations of the listener.
192
     *
193
     * @deprecated This method should only be called in applications that depend
194
     *             on the behaviour that entities are indexed regardless of if a
195
     *             flush is successful.
196
     */
197
    public function preFlush()
198
    {
199
        $this->persistScheduled();
200
    }
201
202
    /**
203
     * Iterating through scheduled actions *after* flushing ensures that the
204
     * ElasticSearch index will be affected only if the query is successful.
205
     */
206 7
    public function postFlush()
207
    {
208 7
        $this->persistScheduled();
209 7
    }
210
211
    /**
212
     * Record the specified identifier to delete. Do not need to entire object.
213
     *
214
     * @param object $object
215
     */
216 3
    private function scheduleForDeletion($object)
217
    {
218 3
        if ($identifierValue = $this->propertyAccessor->getValue($object, $this->config['identifier'])) {
219 3
            $this->scheduledForDeletion[] = (string)$identifierValue;
220 3
        }
221 3
    }
222
223
    /**
224
     * Checks if the object is indexable or not.
225
     *
226
     * @param object $object
227
     *
228
     * @return bool
229
     */
230 5
    private function isObjectIndexable($object)
231
    {
232 5
        return $this->indexable->isObjectIndexable(
233 5
            $this->config['indexName'],
234 5
            $this->config['typeName'],
235
            $object
236 5
        );
237
    }
238
}
239