Completed
Push — master ( d466fc...57558f )
by Pavel
03:56
created

UnitOfWork::isLoaded()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 2
eloc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace Bankiru\Api\Doctrine;
4
5
use Bankiru\Api\Doctrine\Exception\MappingException;
6
use Bankiru\Api\Doctrine\Hydration\EntityHydrator;
7
use Bankiru\Api\Doctrine\Mapping\ApiMetadata;
8
use Bankiru\Api\Doctrine\Mapping\EntityMetadata;
9
use Bankiru\Api\Doctrine\Persister\ApiPersister;
10
use Bankiru\Api\Doctrine\Persister\EntityPersister;
11
use Bankiru\Api\Doctrine\Utility\IdentifierFlattener;
12
use Doctrine\Common\NotifyPropertyChanged;
13
use Doctrine\Common\Persistence\ObjectManagerAware;
14
use Doctrine\Common\PropertyChangedListener;
15
use Doctrine\Common\Proxy\Proxy;
16
17
class UnitOfWork implements PropertyChangedListener
18
{
19
    /**
20
     * An entity is in MANAGED state when its persistence is managed by an EntityManager.
21
     */
22
    const STATE_MANAGED = 1;
23
    /**
24
     * An entity is new if it has just been instantiated (i.e. using the "new" operator)
25
     * and is not (yet) managed by an EntityManager.
26
     */
27
    const STATE_NEW = 2;
28
    /**
29
     * A detached entity is an instance with persistent state and identity that is not
30
     * (or no longer) associated with an EntityManager (and a UnitOfWork).
31
     */
32
    const STATE_DETACHED = 3;
33
    /**
34
     * A removed entity instance is an instance with a persistent identity,
35
     * associated with an EntityManager, whose persistent state will be deleted
36
     * on commit.
37
     */
38
    const STATE_REMOVED = 4;
39
40
41
    /**
42
     * The (cached) states of any known entities.
43
     * Keys are object ids (spl_object_hash).
44
     *
45
     * @var array
46
     */
47
    private $entityStates = [];
48
49
50
    /** @var  EntityManager */
51
    private $manager;
52
    /** @var EntityPersister[] */
53
    private $persisters = [];
54
    /** @var  array */
55
    private $entityIdentifiers;
56
    /** @var  object[][] */
57
    private $identityMap;
58
    /** @var IdentifierFlattener */
59
    private $identifierFlattener;
60
    /** @var  array */
61
    private $originalEntityData;
62
63
    /**
64
     * UnitOfWork constructor.
65
     *
66
     * @param EntityManager $manager
67
     */
68 14
    public function __construct(EntityManager $manager)
69
    {
70 14
        $this->manager                    = $manager;
71 14
        $this->identifierFlattener        = new IdentifierFlattener($this->manager);
72 14
    }
73
74
    /**
75
     * @param $className
76
     *
77
     * @return EntityPersister
78
     */
79 13
    public function getEntityPersister($className)
80
    {
81 13
        if (!array_key_exists($className, $this->persisters)) {
82
            /** @var ApiMetadata $classMetadata */
83 13
            $classMetadata                = $this->manager->getClassMetadata($className);
84 13
            $this->persisters[$className] = new ApiPersister($this->manager, $classMetadata);
85 13
        }
86
87 13
        return $this->persisters[$className];
88
    }
89
90
    /**
91
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
92
     *
93
     * @param object $entity
94
     *
95
     * @return boolean
96
     */
97
    public function isInIdentityMap($entity)
98
    {
99
        $oid = spl_object_hash($entity);
100
101
        if (!isset($this->entityIdentifiers[$oid])) {
102
            return false;
103
        }
104
105
        /** @var EntityMetadata $classMetadata */
106
        $classMetadata = $this->manager->getClassMetadata(get_class($entity));
107
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
108
109
        if ($idHash === '') {
110
            return false;
111
        }
112
113
        return isset($this->identityMap[$classMetadata->rootEntityName][$idHash]);
114
    }
115
116
    /**
117
     * Gets the identifier of an entity.
118
     * The returned value is always an array of identifier values. If the entity
119
     * has a composite identifier then the identifier values are in the same
120
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
121
     *
122
     * @param object $entity
123
     *
124
     * @return array The identifier values.
125
     */
126
    public function getEntityIdentifier($entity)
127
    {
128
        return $this->entityIdentifiers[spl_object_hash($entity)];
129
    }
130
131
    /**
132
     * @param             $className
133
     * @param \stdClass   $data
134
     * @param object|null $unmanagedProxy
135
     *
136
     * @return ObjectManagerAware|object
137
     * @throws MappingException
138
     */
139 12
    public function getOrCreateEntity($className, \stdClass $data, $unmanagedProxy = null)
140
    {
141
        /** @var EntityMetadata $class */
142 12
        $class    = $this->manager->getClassMetadata($className);
143 12
        $hydrator = new EntityHydrator($this->manager, $class);
144
145 12
        $tmpEntity = $hydrator->hydarate($data);
146
147 12
        $id     = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($tmpEntity));
148 12
        $idHash = implode(' ', $id);
149
150 12
        $overrideLocalValues = false;
151 12
        if (isset($this->identityMap[$class->rootEntityName][$idHash])) {
152 2
            $entity = $this->identityMap[$class->rootEntityName][$idHash];
153 2
            $oid    = spl_object_hash($entity);
154
155 2
            if ($entity instanceof Proxy && !$entity->__isInitialized()) {
156 2
                $entity->__setInitialized(true);
157
158 2
                $overrideLocalValues            = true;
159 2
                $this->originalEntityData[$oid] = $data;
160
161 2
                if ($entity instanceof NotifyPropertyChanged) {
162
                    $entity->addPropertyChangedListener($this);
163
                }
164 2
            }
165 2
        } else {
166 11
            $entity = $unmanagedProxy;
167 11
            if (null === $entity) {
168 11
                $entity = $this->newInstance($class);
169 11
            }
170 11
            $this->registerManaged($entity, $id, $data);
171 11
            $overrideLocalValues = true;
172
        }
173
174 12
        if (!$overrideLocalValues) {
175
            return $entity;
176
        }
177
178 12
        $entity = $hydrator->hydarate($data, $entity);
179
180 12
        return $entity;
181
    }
182
183
184
    /**
185
     * @param ApiMetadata $class
186
     *
187
     * @return \Doctrine\Common\Persistence\ObjectManagerAware|object
188
     */
189 11
    private function newInstance(ApiMetadata $class)
190
    {
191 11
        $entity = $class->newInstance();
192
193 11
        if ($entity instanceof ObjectManagerAware) {
194
            $entity->injectObjectManager($this->manager, $class);
195
        }
196
197 11
        return $entity;
198
    }
199
200
    /**
201
     * INTERNAL:
202
     * Registers an entity as managed.
203
     *
204
     * @param object         $entity The entity.
205
     * @param array          $id     The identifier values.
206
     * @param \stdClass|null $data   The original entity data.
207
     *
208
     * @return void
209
     */
210 12
    public function registerManaged($entity, array $id, \stdClass $data = null)
211
    {
212 12
        $oid = spl_object_hash($entity);
213
214 12
        $this->entityIdentifiers[$oid]  = $id;
215 12
        $this->entityStates[$oid]       = self::STATE_MANAGED;
216 12
        $this->originalEntityData[$oid] = $data;
217
218 12
        $this->addToIdentityMap($entity);
219
220 12
        if ($entity instanceof NotifyPropertyChanged && (!$entity instanceof Proxy || $entity->__isInitialized())) {
221
            $entity->addPropertyChangedListener($this);
222
        }
223 12
    }
224
225
    /**
226
     * INTERNAL:
227
     * Registers an entity in the identity map.
228
     * Note that entities in a hierarchy are registered with the class name of
229
     * the root entity.
230
     *
231
     * @ignore
232
     *
233
     * @param object $entity The entity to register.
234
     *
235
     * @return boolean TRUE if the registration was successful, FALSE if the identity of
236
     *                 the entity in question is already managed.
237
     *
238
     */
239 12
    public function addToIdentityMap($entity)
240
    {
241
        /** @var EntityMetadata $classMetadata */
242 12
        $classMetadata = $this->manager->getClassMetadata(get_class($entity));
243 12
        $idHash        = implode(' ', $this->entityIdentifiers[spl_object_hash($entity)]);
244
245 12
        if ($idHash === '') {
246
            throw new \InvalidArgumentException('Entitty does not have valid identifiers to be stored at identity map');
247
        }
248
249 12
        $className = $classMetadata->rootEntityName;
250
251 12
        if (isset($this->identityMap[$className][$idHash])) {
252
            return false;
253
        }
254
255 12
        $this->identityMap[$className][$idHash] = $entity;
256
257 12
        return true;
258
    }
259
260
    /**
261
     * Gets the identity map of the UnitOfWork.
262
     *
263
     * @return array
264
     */
265
    public function getIdentityMap()
266
    {
267
        return $this->identityMap;
268
    }
269
270
    /**
271
     * Gets the original data of an entity. The original data is the data that was
272
     * present at the time the entity was reconstituted from the database.
273
     *
274
     * @param object $entity
275
     *
276
     * @return array
277
     */
278
    public function getOriginalEntityData($entity)
279
    {
280
        $oid = spl_object_hash($entity);
281
282
        if (isset($this->originalEntityData[$oid])) {
283
            return $this->originalEntityData[$oid];
284
        }
285
286
        return [];
287
    }
288
289
    /**
290
     * INTERNAL:
291
     * Checks whether an identifier hash exists in the identity map.
292
     *
293
     * @ignore
294
     *
295
     * @param string $idHash
296
     * @param string $rootClassName
297
     *
298
     * @return boolean
299
     */
300
    public function containsIdHash($idHash, $rootClassName)
301
    {
302
        return isset($this->identityMap[$rootClassName][$idHash]);
303
    }
304
305
    /**
306
     * INTERNAL:
307
     * Gets an entity in the identity map by its identifier hash.
308
     *
309
     * @ignore
310
     *
311
     * @param string $idHash
312
     * @param string $rootClassName
313
     *
314
     * @return object
315
     */
316
    public function getByIdHash($idHash, $rootClassName)
317
    {
318
        return $this->identityMap[$rootClassName][$idHash];
319
    }
320
321
    /**
322
     * INTERNAL:
323
     * Tries to get an entity by its identifier hash. If no entity is found for
324
     * the given hash, FALSE is returned.
325
     *
326
     * @ignore
327
     *
328
     * @param mixed  $idHash (must be possible to cast it to string)
329
     * @param string $rootClassName
330
     *
331
     * @return object|bool The found entity or FALSE.
332
     */
333
    public function tryGetByIdHash($idHash, $rootClassName)
334
    {
335
        $stringIdHash = (string)$idHash;
336
337
        if (isset($this->identityMap[$rootClassName][$stringIdHash])) {
338
            return $this->identityMap[$rootClassName][$stringIdHash];
339
        }
340
341
        return false;
342
    }
343
344
    /**
345
     * Gets the state of an entity with regard to the current unit of work.
346
     *
347
     * @param object   $entity
348
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
349
     *                         This parameter can be set to improve performance of entity state detection
350
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
351
     *                         is either known or does not matter for the caller of the method.
352
     *
353
     * @return int The entity state.
354
     */
355
    public function getEntityState($entity, $assume = null)
356
    {
357
        $oid = spl_object_hash($entity);
358
        if (isset($this->entityStates[$oid])) {
359
            return $this->entityStates[$oid];
360
        }
361
        if ($assume !== null) {
362
            return $assume;
363
        }
364
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
365
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
366
        // the UoW does not hold references to such objects and the object hash can be reused.
367
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
368
        $class = $this->manager->getClassMetadata(get_class($entity));
369
        $id    = $class->getIdentifierValues($entity);
370
        if (!$id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
371
            return self::STATE_NEW;
372
        }
373
374
        return self::STATE_DETACHED;
375
    }
376
377
    /**
378
     * Tries to find an entity with the given identifier in the identity map of
379
     * this UnitOfWork.
380
     *
381
     * @param mixed  $id            The entity identifier to look for.
382
     * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
383
     *
384
     * @return object|bool Returns the entity with the specified identifier if it exists in
385
     *                     this UnitOfWork, FALSE otherwise.
386
     */
387 11
    public function tryGetById($id, $rootClassName)
388
    {
389
        /** @var EntityMetadata $metadata */
390 11
        $metadata = $this->manager->getClassMetadata($rootClassName);
391 11
        $idHash   = implode(' ', (array)$this->identifierFlattener->flattenIdentifier($metadata, $id));
392
393 11
        if (isset($this->identityMap[$rootClassName][$idHash])) {
394 4
            return $this->identityMap[$rootClassName][$idHash];
395
        }
396
397 11
        return false;
398
    }
399
400
    /**
401
     * Notifies the listener of a property change.
402
     *
403
     * @param object $sender       The object on which the property changed.
404
     * @param string $propertyName The name of the property that changed.
405
     * @param mixed  $oldValue     The old value of the property that changed.
406
     * @param mixed  $newValue     The new value of the property that changed.
407
     *
408
     * @return void
409
     */
410
    public function propertyChanged($sender, $propertyName, $oldValue, $newValue)
411
    {
412
    }
413
}
414