Completed
Push — master ( e2f665...9d55dc )
by Karel
14s
created

Listener::isObjectIndexable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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