Completed
Pull Request — master (#1266)
by Maciej
08:20
created

UnitOfWork::scheduleForUpdate()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.1668
Metric Value
dl 0
loc 17
ccs 10
cts 12
cp 0.8333
rs 8.8571
cc 6
eloc 10
nc 4
nop 1
crap 6.1668
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB;
21
22
use Doctrine\Common\Collections\ArrayCollection;
23
use Doctrine\Common\Collections\Collection;
24
use Doctrine\Common\EventManager;
25
use Doctrine\Common\NotifyPropertyChanged;
26
use Doctrine\Common\PropertyChangedListener;
27
use Doctrine\MongoDB\GridFSFile;
28
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;
29
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
30
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
31
use Doctrine\ODM\MongoDB\PersistentCollection;
32
use Doctrine\ODM\MongoDB\Persisters\PersistenceBuilder;
33
use Doctrine\ODM\MongoDB\Proxy\Proxy;
34
use Doctrine\ODM\MongoDB\Query\Query;
35
use Doctrine\ODM\MongoDB\Types\Type;
36
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
37
38
/**
39
 * The UnitOfWork is responsible for tracking changes to objects during an
40
 * "object-level" transaction and for writing out changes to the database
41
 * in the correct order.
42
 *
43
 * @since       1.0
44
 * @author      Jonathan H. Wage <[email protected]>
45
 * @author      Roman Borschel <[email protected]>
46
 */
47
class UnitOfWork implements PropertyChangedListener
48
{
49
    /**
50
     * A document is in MANAGED state when its persistence is managed by a DocumentManager.
51
     */
52
    const STATE_MANAGED = 1;
53
54
    /**
55
     * A document is new if it has just been instantiated (i.e. using the "new" operator)
56
     * and is not (yet) managed by a DocumentManager.
57
     */
58
    const STATE_NEW = 2;
59
60
    /**
61
     * A detached document is an instance with a persistent identity that is not
62
     * (or no longer) associated with a DocumentManager (and a UnitOfWork).
63
     */
64
    const STATE_DETACHED = 3;
65
66
    /**
67
     * A removed document instance is an instance with a persistent identity,
68
     * associated with a DocumentManager, whose persistent state has been
69
     * deleted (or is scheduled for deletion).
70
     */
71
    const STATE_REMOVED = 4;
72
73
    /**
74
     * The identity map holds references to all managed documents.
75
     *
76
     * Documents are grouped by their class name, and then indexed by the
77
     * serialized string of their database identifier field or, if the class
78
     * has no identifier, the SPL object hash. Serializing the identifier allows
79
     * differentiation of values that may be equal (via type juggling) but not
80
     * identical.
81
     *
82
     * Since all classes in a hierarchy must share the same identifier set,
83
     * we always take the root class name of the hierarchy.
84
     *
85
     * @var array
86
     */
87
    private $identityMap = array();
88
89
    /**
90
     * Map of all identifiers of managed documents.
91
     * Keys are object ids (spl_object_hash).
92
     *
93
     * @var array
94
     */
95
    private $documentIdentifiers = array();
96
97
    /**
98
     * Map of the original document data of managed documents.
99
     * Keys are object ids (spl_object_hash). This is used for calculating changesets
100
     * at commit time.
101
     *
102
     * @var array
103
     * @internal Note that PHPs "copy-on-write" behavior helps a lot with memory usage.
104
     *           A value will only really be copied if the value in the document is modified
105
     *           by the user.
106
     */
107
    private $originalDocumentData = array();
108
109
    /**
110
     * Map of document changes. Keys are object ids (spl_object_hash).
111
     * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
112
     *
113
     * @var array
114
     */
115
    private $documentChangeSets = array();
116
117
    /**
118
     * The (cached) states of any known documents.
119
     * Keys are object ids (spl_object_hash).
120
     *
121
     * @var array
122
     */
123
    private $documentStates = array();
124
125
    /**
126
     * Map of documents that are scheduled for dirty checking at commit time.
127
     *
128
     * Documents are grouped by their class name, and then indexed by their SPL
129
     * object hash. This is only used for documents with a change tracking
130
     * policy of DEFERRED_EXPLICIT.
131
     *
132
     * @var array
133
     * @todo rename: scheduledForSynchronization
134
     */
135
    private $scheduledForDirtyCheck = array();
136
137
    /**
138
     * A list of all pending document insertions.
139
     *
140
     * @var array
141
     */
142
    private $documentInsertions = array();
143
144
    /**
145
     * A list of all pending document updates.
146
     *
147
     * @var array
148
     */
149
    private $documentUpdates = array();
150
151
    /**
152
     * A list of all pending document upserts.
153
     *
154
     * @var array
155
     */
156
    private $documentUpserts = array();
157
158
    /**
159
     * A list of all pending document deletions.
160
     *
161
     * @var array
162
     */
163
    private $documentDeletions = array();
164
165
    /**
166
     * All pending collection deletions.
167
     *
168
     * @var array
169
     */
170
    private $collectionDeletions = array();
171
172
    /**
173
     * All pending collection updates.
174
     *
175
     * @var array
176
     */
177
    private $collectionUpdates = array();
178
    
179
    /**
180
     * A list of documents related to collections scheduled for update or deletion
181
     * 
182
     * @var array
183
     */
184
    private $hasScheduledCollections = array();
185
186
    /**
187
     * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
188
     * At the end of the UnitOfWork all these collections will make new snapshots
189
     * of their data.
190
     *
191
     * @var array
192
     */
193
    private $visitedCollections = array();
194
195
    /**
196
     * The DocumentManager that "owns" this UnitOfWork instance.
197
     *
198
     * @var DocumentManager
199
     */
200
    private $dm;
201
202
    /**
203
     * The EventManager used for dispatching events.
204
     *
205
     * @var EventManager
206
     */
207
    private $evm;
208
209
    /**
210
     * Additional documents that are scheduled for removal.
211
     *
212
     * @var array
213
     */
214
    private $orphanRemovals = array();
215
216
    /**
217
     * The HydratorFactory used for hydrating array Mongo documents to Doctrine object documents.
218
     *
219
     * @var HydratorFactory
220
     */
221
    private $hydratorFactory;
222
223
    /**
224
     * The document persister instances used to persist document instances.
225
     *
226
     * @var array
227
     */
228
    private $persisters = array();
229
230
    /**
231
     * The collection persister instance used to persist changes to collections.
232
     *
233
     * @var Persisters\CollectionPersister
234
     */
235
    private $collectionPersister;
236
237
    /**
238
     * The persistence builder instance used in DocumentPersisters.
239
     *
240
     * @var PersistenceBuilder
241
     */
242
    private $persistenceBuilder;
243
244
    /**
245
     * Array of parent associations between embedded documents
246
     *
247
     * @todo We might need to clean up this array in clear(), doDetach(), etc.
248
     * @var array
249
     */
250
    private $parentAssociations = array();
251
252
    /**
253
     * Initializes a new UnitOfWork instance, bound to the given DocumentManager.
254
     *
255
     * @param DocumentManager $dm
256
     * @param EventManager $evm
257
     * @param HydratorFactory $hydratorFactory
258
     */
259 922
    public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory)
260
    {
261 922
        $this->dm = $dm;
262 922
        $this->evm = $evm;
263 922
        $this->hydratorFactory = $hydratorFactory;
264 922
    }
265
266
    /**
267
     * Factory for returning new PersistenceBuilder instances used for preparing data into
268
     * queries for insert persistence.
269
     *
270
     * @return PersistenceBuilder $pb
271
     */
272 668
    public function getPersistenceBuilder()
273
    {
274 668
        if ( ! $this->persistenceBuilder) {
275 668
            $this->persistenceBuilder = new PersistenceBuilder($this->dm, $this);
276 668
        }
277 668
        return $this->persistenceBuilder;
278
    }
279
280
    /**
281
     * Sets the parent association for a given embedded document.
282
     *
283
     * @param object $document
284
     * @param array $mapping
285
     * @param object $parent
286
     * @param string $propertyPath
287
     */
288 180
    public function setParentAssociation($document, $mapping, $parent, $propertyPath)
289
    {
290 180
        $oid = spl_object_hash($document);
291 180
        $this->parentAssociations[$oid] = array($mapping, $parent, $propertyPath);
292 180
    }
293
294
    /**
295
     * Gets the parent association for a given embedded document.
296
     *
297
     *     <code>
298
     *     list($mapping, $parent, $propertyPath) = $this->getParentAssociation($embeddedDocument);
299
     *     </code>
300
     *
301
     * @param object $document
302
     * @return array $association
303
     */
304 207
    public function getParentAssociation($document)
305
    {
306 207
        $oid = spl_object_hash($document);
307 207
        if ( ! isset($this->parentAssociations[$oid])) {
308 203
            return null;
309
        }
310 165
        return $this->parentAssociations[$oid];
311
    }
312
313
    /**
314
     * Get the document persister instance for the given document name
315
     *
316
     * @param string $documentName
317
     * @return Persisters\DocumentPersister
318
     */
319 666
    public function getDocumentPersister($documentName)
320
    {
321 666
        if ( ! isset($this->persisters[$documentName])) {
322 652
            $class = $this->dm->getClassMetadata($documentName);
323 652
            $pb = $this->getPersistenceBuilder();
324 652
            $this->persisters[$documentName] = new Persisters\DocumentPersister($pb, $this->dm, $this->evm, $this, $this->hydratorFactory, $class);
325 652
        }
326 666
        return $this->persisters[$documentName];
327
    }
328
329
    /**
330
     * Get the collection persister instance.
331
     *
332
     * @return \Doctrine\ODM\MongoDB\Persisters\CollectionPersister
333
     */
334 666
    public function getCollectionPersister()
335
    {
336 666
        if ( ! isset($this->collectionPersister)) {
337 666
            $pb = $this->getPersistenceBuilder();
338 666
            $this->collectionPersister = new Persisters\CollectionPersister($this->dm, $pb, $this);
339 666
        }
340 666
        return $this->collectionPersister;
341
    }
342
343
    /**
344
     * Set the document persister instance to use for the given document name
345
     *
346
     * @param string $documentName
347
     * @param Persisters\DocumentPersister $persister
348
     */
349 14
    public function setDocumentPersister($documentName, Persisters\DocumentPersister $persister)
350
    {
351 14
        $this->persisters[$documentName] = $persister;
352 14
    }
353
354
    /**
355
     * Commits the UnitOfWork, executing all operations that have been postponed
356
     * up to this point. The state of all managed documents will be synchronized with
357
     * the database.
358
     *
359
     * The operations are executed in the following order:
360
     *
361
     * 1) All document insertions
362
     * 2) All document updates
363
     * 3) All document deletions
364
     *
365
     * @param object $document
366
     * @param array $options Array of options to be used with batchInsert(), update() and remove()
367
     */
368 555
    public function commit($document = null, array $options = array())
369
    {
370
        // Raise preFlush
371 555
        if ($this->evm->hasListeners(Events::preFlush)) {
372
            $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->dm));
373
        }
374
375 555
        $defaultOptions = $this->dm->getConfiguration()->getDefaultCommitOptions();
376 555
        if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options 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...
377
            $options = array_merge($defaultOptions, $options);
378
        } else {
379 555
            $options = $defaultOptions;
380
        }
381
        // Compute changes done since last commit.
382 555
        if ($document === null) {
383 549
            $this->computeChangeSets();
384 554
        } elseif (is_object($document)) {
385 12
            $this->computeSingleDocumentChangeSet($document);
386 12
        } elseif (is_array($document)) {
387 1
            foreach ($document as $object) {
388 1
                $this->computeSingleDocumentChangeSet($object);
389 1
            }
390 1
        }
391
392 553
        if ( ! ($this->documentInsertions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentInsertions 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...
393 236
            $this->documentUpserts ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentUpserts 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...
394 199
            $this->documentDeletions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentDeletions 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...
395 189
            $this->documentUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentUpdates 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...
396 23
            $this->collectionUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->collectionUpdates 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...
397 23
            $this->collectionDeletions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->collectionDeletions 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...
398 23
            $this->orphanRemovals)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals 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...
399 553
        ) {
400 23
            return; // Nothing to do.
401
        }
402
403 550
        if ($this->orphanRemovals) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals 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...
404 45
            foreach ($this->orphanRemovals as $removal) {
405 45
                $this->remove($removal);
406 45
            }
407 45
        }
408
409
        // Raise onFlush
410 550
        if ($this->evm->hasListeners(Events::onFlush)) {
411 7
            $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));
412 7
        }
413
414 550
        foreach ($this->getClassesForCommitAction($this->documentUpserts) as $classAndDocuments) {
415 78
            list($class, $documents) = $classAndDocuments;
416 78
            $this->executeUpserts($class, $documents, $options);
417 550
        }
418
419 550
        foreach ($this->getClassesForCommitAction($this->documentInsertions) as $classAndDocuments) {
420 483
            list($class, $documents) = $classAndDocuments;
421 483
            $this->executeInserts($class, $documents, $options);
422 549
        }
423
424 549
        foreach ($this->getClassesForCommitAction($this->documentUpdates) as $classAndDocuments) {
425 215
            list($class, $documents) = $classAndDocuments;
426 215
            $this->executeUpdates($class, $documents, $options);
427 549
        }
428
429 549
        foreach ($this->getClassesForCommitAction($this->documentDeletions, true) as $classAndDocuments) {
430 62
            list($class, $documents) = $classAndDocuments;
431 62
            $this->executeDeletions($class, $documents, $options);
432 549
        }
433
434
        // Raise postFlush
435 549
        if ($this->evm->hasListeners(Events::postFlush)) {
436
            $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm));
437
        }
438
439
        // Clear up
440 549
        $this->documentInsertions =
441 549
        $this->documentUpserts =
442 549
        $this->documentUpdates =
443 549
        $this->documentDeletions =
444 549
        $this->documentChangeSets =
445 549
        $this->collectionUpdates =
446 549
        $this->collectionDeletions =
447 549
        $this->visitedCollections =
448 549
        $this->scheduledForDirtyCheck =
449 549
        $this->orphanRemovals = 
450 549
        $this->hasScheduledCollections = array();
451 549
    }
452
453
    /**
454
     * Groups a list of scheduled documents by their class.
455
     *
456
     * @param array $documents Scheduled documents (e.g. $this->documentInsertions)
457
     * @param bool $includeEmbedded
458
     * @return array Tuples of ClassMetadata and a corresponding array of objects
459
     */
460 550
    private function getClassesForCommitAction($documents, $includeEmbedded = false)
461
    {
462 550
        if (empty($documents)) {
463 550
            return array();
464
        }
465 549
        $divided = array();
466 549
        $embeds = array();
467 549
        foreach ($documents as $oid => $d) {
468 549
            $className = get_class($d);
469 549
            if (isset($embeds[$className])) {
470 68
                continue;
471
            }
472 549
            if (isset($divided[$className])) {
473 135
                $divided[$className][1][$oid] = $d;
474 135
                continue;
475
            }
476 549
            $class = $this->dm->getClassMetadata($className);
477 549
            if ($class->isEmbeddedDocument && ! $includeEmbedded) {
478 165
                $embeds[$className] = true;
479 165
                continue;
480
            }
481 549
            if (empty($divided[$class->name])) {
482 549
                $divided[$class->name] = array($class, array($oid => $d));
483 549
            } else {
484 4
                $divided[$class->name][1][$oid] = $d;
485
            }
486 549
        }
487 549
        return $divided;
488
    }
489
490
    /**
491
     * Compute changesets of all documents scheduled for insertion.
492
     *
493
     * Embedded documents will not be processed.
494
     */
495 557 View Code Duplication
    private function computeScheduleInsertsChangeSets()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
496
    {
497 557
        foreach ($this->documentInsertions as $document) {
498 491
            $class = $this->dm->getClassMetadata(get_class($document));
499 491
            if ( ! $class->isEmbeddedDocument) {
500 488
                $this->computeChangeSet($class, $document);
501 487
            }
502 556
        }
503 556
    }
504
505
    /**
506
     * Compute changesets of all documents scheduled for upsert.
507
     *
508
     * Embedded documents will not be processed.
509
     */
510 556 View Code Duplication
    private function computeScheduleUpsertsChangeSets()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
511
    {
512 556
        foreach ($this->documentUpserts as $document) {
513 77
            $class = $this->dm->getClassMetadata(get_class($document));
514 77
            if ( ! $class->isEmbeddedDocument) {
515 77
                $this->computeChangeSet($class, $document);
516 77
            }
517 556
        }
518 556
    }
519
520
    /**
521
     * Only flush the given document according to a ruleset that keeps the UoW consistent.
522
     *
523
     * 1. All documents scheduled for insertion and (orphan) removals are processed as well!
524
     * 2. Proxies are skipped.
525
     * 3. Only if document is properly managed.
526
     *
527
     * @param  object $document
528
     * @throws \InvalidArgumentException If the document is not STATE_MANAGED
529
     * @return void
530
     */
531 13
    private function computeSingleDocumentChangeSet($document)
532
    {
533 13
        $state = $this->getDocumentState($document);
534
535 13
        if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) {
536 1
            throw new \InvalidArgumentException("Document has to be managed or scheduled for removal for single computation " . self::objToStr($document));
537
        }
538
539 12
        $class = $this->dm->getClassMetadata(get_class($document));
540
541 12
        if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) {
542 9
            $this->persist($document);
543 9
        }
544
545
        // Compute changes for INSERTed and UPSERTed documents first. This must always happen even in this case.
546 12
        $this->computeScheduleInsertsChangeSets();
547 12
        $this->computeScheduleUpsertsChangeSets();
548
549
        // Ignore uninitialized proxy objects
550 12
        if ($document instanceof Proxy && ! $document->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
551
            return;
552
        }
553
554
        // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here.
555 12
        $oid = spl_object_hash($document);
556
557 12 View Code Duplication
        if ( ! isset($this->documentInsertions[$oid])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
558 12
            && ! isset($this->documentUpserts[$oid])
559 12
            && ! isset($this->documentDeletions[$oid])
560 12
            && isset($this->documentStates[$oid])
561 12
        ) {
562 8
            $this->computeChangeSet($class, $document);
563 8
        }
564 12
    }
565
566
    /**
567
     * Gets the changeset for a document.
568
     *
569
     * @param object $document
570
     * @return array array('property' => array(0 => mixed|null, 1 => mixed|null))
571
     */
572 540
    public function getDocumentChangeSet($document)
573
    {
574 540
        $oid = spl_object_hash($document);
575 540
        if (isset($this->documentChangeSets[$oid])) {
576 540
            return $this->documentChangeSets[$oid];
577
        }
578 27
        return array();
579
    }
580
581
    /**
582
     * Get a documents actual data, flattening all the objects to arrays.
583
     *
584
     * @param object $document
585
     * @return array
586
     */
587 554
    public function getDocumentActualData($document)
588
    {
589 554
        $class = $this->dm->getClassMetadata(get_class($document));
590 554
        $actualData = array();
591 554
        foreach ($class->reflFields as $name => $refProp) {
592 554
            $mapping = $class->fieldMappings[$name];
593
            // skip not saved fields
594 554
            if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
595 49
                continue;
596
            }
597 554
            $value = $refProp->getValue($document);
598 554
            if (isset($mapping['file']) && ! $value instanceof GridFSFile) {
599 5
                $value = new GridFSFile($value);
600 5
                $class->reflFields[$name]->setValue($document, $value);
601 5
                $actualData[$name] = $value;
602 554
            } elseif ((isset($mapping['association']) && $mapping['type'] === 'many')
603 554
                && $value !== null && ! ($value instanceof PersistentCollection)) {
604
                // If $actualData[$name] is not a Collection then use an ArrayCollection.
605 365
                if ( ! $value instanceof Collection) {
606 118
                    $value = new ArrayCollection($value);
607 118
                }
608
609
                // Inject PersistentCollection
610 365
                $coll = new PersistentCollection($value, $this->dm, $this);
611 365
                $coll->setOwner($document, $mapping);
612 365
                $coll->setDirty( ! $value->isEmpty());
613 365
                $class->reflFields[$name]->setValue($document, $coll);
614 365
                $actualData[$name] = $coll;
615 365
            } else {
616 554
                $actualData[$name] = $value;
617
            }
618 554
        }
619 554
        return $actualData;
620
    }
621
622
    /**
623
     * Computes the changes that happened to a single document.
624
     *
625
     * Modifies/populates the following properties:
626
     *
627
     * {@link originalDocumentData}
628
     * If the document is NEW or MANAGED but not yet fully persisted (only has an id)
629
     * then it was not fetched from the database and therefore we have no original
630
     * document data yet. All of the current document data is stored as the original document data.
631
     *
632
     * {@link documentChangeSets}
633
     * The changes detected on all properties of the document are stored there.
634
     * A change is a tuple array where the first entry is the old value and the second
635
     * entry is the new value of the property. Changesets are used by persisters
636
     * to INSERT/UPDATE the persistent document state.
637
     *
638
     * {@link documentUpdates}
639
     * If the document is already fully MANAGED (has been fetched from the database before)
640
     * and any changes to its properties are detected, then a reference to the document is stored
641
     * there to mark it for an update.
642
     *
643
     * @param ClassMetadata $class The class descriptor of the document.
644
     * @param object $document The document for which to compute the changes.
645
     */
646 554
    public function computeChangeSet(ClassMetadata $class, $document)
647
    {
648 554
        if ( ! $class->isInheritanceTypeNone()) {
649 171
            $class = $this->dm->getClassMetadata(get_class($document));
650 171
        }
651
652
        // Fire PreFlush lifecycle callbacks
653 554 View Code Duplication
        if ( ! empty($class->lifecycleCallbacks[Events::preFlush])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
654 11
            $class->invokeLifecycleCallbacks(Events::preFlush, $document, array(new Event\PreFlushEventArgs($this->dm)));
655 11
        }
656
657 554
        $this->computeOrRecomputeChangeSet($class, $document);
658 553
    }
659
660
    /**
661
     * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet
662
     *
663
     * @param \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class
664
     * @param object $document
665
     * @param boolean $recompute
666
     */
667 554
    private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false)
668
    {
669 554
        $oid = spl_object_hash($document);
670 554
        $actualData = $this->getDocumentActualData($document);
671 554
        $isNewDocument = ! isset($this->originalDocumentData[$oid]);
672 554
        if ($isNewDocument) {
673
            // Document is either NEW or MANAGED but not yet fully persisted (only has an id).
674
            // These result in an INSERT.
675 554
            $this->originalDocumentData[$oid] = $actualData;
676 554
            $changeSet = array();
677 554
            foreach ($actualData as $propName => $actualValue) {
678
                /* At this PersistentCollection shouldn't be here, probably it
679
                 * was cloned and its ownership must be fixed
680
                 */
681 554
                if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) {
682
                    $actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
683
                    $actualValue = $actualData[$propName];
684
                }
685 554
                $changeSet[$propName] = array(null, $actualValue);
686 554
            }
687 554
            $this->documentChangeSets[$oid] = $changeSet;
688 554
        } else {
689
            // Document is "fully" MANAGED: it was already fully persisted before
690
            // and we have a copy of the original data
691 275
            $originalData = $this->originalDocumentData[$oid];
692 275
            $isChangeTrackingNotify = $class->isChangeTrackingNotify();
693 275
            if ($isChangeTrackingNotify && ! $recompute && isset($this->documentChangeSets[$oid])) {
694 2
                $changeSet = $this->documentChangeSets[$oid];
695 2
            } else {
696 275
                $changeSet = array();
697
            }
698
699 275
            foreach ($actualData as $propName => $actualValue) {
700
                // skip not saved fields
701 275
                if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
702
                    continue;
703
                }
704
705 275
                $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
706
707
                // skip if value has not changed
708 275
                if ($orgValue === $actualValue) {
709
                    // but consider dirty GridFSFile instances as changed
710 274
                    if ( ! (isset($class->fieldMappings[$propName]['file']) && $actualValue->isDirty())) {
711 274
                        continue;
712
                    }
713 1
                }
714
715
                // if relationship is a embed-one, schedule orphan removal to trigger cascade remove operations
716 176
                if (isset($class->fieldMappings[$propName]['embedded']) && $class->fieldMappings[$propName]['type'] === 'one') {
717 10
                    if ($orgValue !== null) {
718 5
                        $this->scheduleOrphanRemoval($orgValue);
719 5
                    }
720
721 10
                    $changeSet[$propName] = array($orgValue, $actualValue);
722 10
                    continue;
723
                }
724
725
                // if owning side of reference-one relationship
726 169
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $class->fieldMappings[$propName]['isOwningSide']) {
727 11
                    if ($orgValue !== null && $class->fieldMappings[$propName]['orphanRemoval']) {
728 1
                        $this->scheduleOrphanRemoval($orgValue);
729 1
                    }
730
731 11
                    $changeSet[$propName] = array($orgValue, $actualValue);
732 11
                    continue;
733
                }
734
735 161
                if ($isChangeTrackingNotify) {
736 2
                    continue;
737
                }
738
739
                // ignore inverse side of reference relationship
740 160
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['isInverseSide']) {
741 2
                    continue;
742
                }
743
744
                // Persistent collection was exchanged with the "originally"
745
                // created one. This can only mean it was cloned and replaced
746
                // on another document.
747 160
                if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) {
748 6
                    $actualValue = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
749 6
                }
750
751
                // if embed-many or reference-many relationship
752 160
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'many') {
753 25
                    $changeSet[$propName] = array($orgValue, $actualValue);
754
                    /* If original collection was exchanged with a non-empty value
755
                     * and $set will be issued, there is no need to $unset it first
756
                     */
757 25
                    if ($actualValue && $actualValue->isDirty() && CollectionHelper::usesSet($class->fieldMappings[$propName]['strategy'])) {
758 7
                        continue;
759
                    }
760 19
                    if ($orgValue instanceof PersistentCollection) {
761 17
                        $this->scheduleCollectionDeletion($orgValue);
762 17
                    }
763 19
                    continue;
764
                }
765
766
                // skip equivalent date values
767 146
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'date') {
768 36
                    $dateType = Type::getType('date');
769 36
                    $dbOrgValue = $dateType->convertToDatabaseValue($orgValue);
770 36
                    $dbActualValue = $dateType->convertToDatabaseValue($actualValue);
771
772 36
                    if ($dbOrgValue instanceof \MongoDate && $dbActualValue instanceof \MongoDate && $dbOrgValue == $dbActualValue) {
773 29
                        continue;
774
                    }
775 10
                }
776
777
                // regular field
778 130
                $changeSet[$propName] = array($orgValue, $actualValue);
779 275
            }
780 275
            if ($changeSet) {
781 162
                $this->documentChangeSets[$oid] = ($recompute && isset($this->documentChangeSets[$oid]))
782 162
                    ? $changeSet + $this->documentChangeSets[$oid]
783 14
                    : $changeSet;
784
785 162
                $this->originalDocumentData[$oid] = $actualData;
786 162
                $this->scheduleForUpdate($document);
787 162
            }
788
        }
789
790
        // Look for changes in associations of the document
791 554
        $associationMappings = array_filter(
792 554
            $class->associationMappings,
793
            function ($assoc) { return empty($assoc['notSaved']); }
794 554
        );
795
796 554
        foreach ($associationMappings as $mapping) {
797 428
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
798
799 428
            if ($value === null) {
800 288
                continue;
801
            }
802
803 419
            $this->computeAssociationChanges($document, $mapping, $value);
804
805 418
            if (isset($mapping['reference'])) {
806 315
                continue;
807
            }
808
809 326
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value->unwrap();
810
811 326
            foreach ($values as $obj) {
812 169
                $oid2 = spl_object_hash($obj);
813
814 169
                if (isset($this->documentChangeSets[$oid2])) {
815 167
                    $this->documentChangeSets[$oid][$mapping['fieldName']] = array($value, $value);
816
817 167
                    if ( ! $isNewDocument) {
818 71
                        $this->scheduleForUpdate($document);
819 71
                    }
820
821 167
                    break;
822
                }
823 326
            }
824 553
        }
825 553
    }
826
827
    /**
828
     * Computes all the changes that have been done to documents and collections
829
     * since the last commit and stores these changes in the _documentChangeSet map
830
     * temporarily for access by the persisters, until the UoW commit is finished.
831
     */
832 552
    public function computeChangeSets()
833
    {
834 552
        $this->computeScheduleInsertsChangeSets();
835 551
        $this->computeScheduleUpsertsChangeSets();
836
837
        // Compute changes for other MANAGED documents. Change tracking policies take effect here.
838 551
        foreach ($this->identityMap as $className => $documents) {
839 551
            $class = $this->dm->getClassMetadata($className);
840 551
            if ($class->isEmbeddedDocument) {
841
                /* we do not want to compute changes to embedded documents up front
842
                 * in case embedded document was replaced and its changeset
843
                 * would corrupt data. Embedded documents' change set will
844
                 * be calculated by reachability from owning document.
845
                 */
846 158
                continue;
847
            }
848
849
            // If change tracking is explicit or happens through notification, then only compute
850
            // changes on document of that type that are explicitly marked for synchronization.
851 551
            switch (true) {
852 551
                case ($class->isChangeTrackingDeferredImplicit()):
853 550
                    $documentsToProcess = $documents;
854 550
                    break;
855
856 3
                case (isset($this->scheduledForDirtyCheck[$className])):
857 2
                    $documentsToProcess = $this->scheduledForDirtyCheck[$className];
858 2
                    break;
859
860 3
                default:
861 3
                    $documentsToProcess = array();
862
863 3
            }
864
865 551
            foreach ($documentsToProcess as $document) {
866
                // Ignore uninitialized proxy objects
867 547
                if ($document instanceof Proxy && ! $document->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
868 10
                    continue;
869
                }
870
                // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here.
871 547
                $oid = spl_object_hash($document);
872 547 View Code Duplication
                if ( ! isset($this->documentInsertions[$oid])
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
873 547
                    && ! isset($this->documentUpserts[$oid])
874 547
                    && ! isset($this->documentDeletions[$oid])
875 547
                    && isset($this->documentStates[$oid])
876 547
                ) {
877 260
                    $this->computeChangeSet($class, $document);
878 260
                }
879 551
            }
880 551
        }
881 551
    }
882
883
    /**
884
     * Computes the changes of an association.
885
     *
886
     * @param object $parentDocument
887
     * @param array $assoc
888
     * @param mixed $value The value of the association.
889
     * @throws \InvalidArgumentException
890
     */
891 419
    private function computeAssociationChanges($parentDocument, array $assoc, $value)
892
    {
893 419
        $isNewParentDocument = isset($this->documentInsertions[spl_object_hash($parentDocument)]);
894 419
        $class = $this->dm->getClassMetadata(get_class($parentDocument));
895 419
        $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument);
896
897 419
        if ($value instanceof Proxy && ! $value->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
898 8
            return;
899
        }
900
901 418
        if ($value instanceof PersistentCollection && $value->isDirty() && ($assoc['isOwningSide'] || isset($assoc['embedded']))) {
902 225
            if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) {
903 221
                $this->scheduleCollectionUpdate($value);
904 221
            }
905 225
            $topmostOwner = $this->getOwningDocument($value->getOwner());
906 225
            $this->visitedCollections[spl_object_hash($topmostOwner)][] = $value;
907 225
            if ( ! empty($assoc['orphanRemoval']) || isset($assoc['embedded'])) {
908 132
                $value->initialize();
909 132
                foreach ($value->getDeletedDocuments() as $orphan) {
910 21
                    $this->scheduleOrphanRemoval($orphan);
911 132
                }
912 132
            }
913 225
        }
914
915
        // Look through the documents, and in any of their associations,
916
        // for transient (new) documents, recursively. ("Persistence by reachability")
917
        // Unwrap. Uninitialized collections will simply be empty.
918 418
        $unwrappedValue = ($assoc['type'] === ClassMetadata::ONE) ? array($value) : $value->unwrap();
919
920 418
        $count = 0;
921 418
        foreach ($unwrappedValue as $key => $entry) {
922 323
            if ( ! is_object($entry)) {
923 1
                throw new \InvalidArgumentException(
924 1
                        sprintf('Expected object, found "%s" in %s::%s', $entry, get_class($parentDocument), $assoc['name'])
925 1
                );
926
            }
927
928 322
            $targetClass = $this->dm->getClassMetadata(get_class($entry));
929
930 322
            $state = $this->getDocumentState($entry, self::STATE_NEW);
931
932
            // Handle "set" strategy for multi-level hierarchy
933 322
            $pathKey = ! isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key;
934 322
            $path = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name'];
935
936 322
            $count++;
937
938
            switch ($state) {
939 322
                case self::STATE_NEW:
940 56
                    if ( ! $assoc['isCascadePersist']) {
941
                        throw new \InvalidArgumentException("A new document was found through a relationship that was not"
942
                            . " configured to cascade persist operations: " . self::objToStr($entry) . "."
943
                            . " Explicitly persist the new document or configure cascading persist operations"
944
                            . " on the relationship.");
945
                    }
946
947 56
                    $this->persistNew($targetClass, $entry);
948 56
                    $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
949 56
                    $this->computeChangeSet($targetClass, $entry);
950 56
                    break;
951
952 318
                case self::STATE_MANAGED:
953 318
                    if ($targetClass->isEmbeddedDocument) {
954 161
                        list(, $knownParent, ) = $this->getParentAssociation($entry);
955 161
                        if ($knownParent && $knownParent !== $parentDocument) {
956 6
                            $entry = clone $entry;
957 6
                            if ($assoc['type'] === ClassMetadata::ONE) {
958 3
                                $class->setFieldValue($parentDocument, $assoc['name'], $entry);
959 3
                                $this->setOriginalDocumentProperty(spl_object_hash($parentDocument), $assoc['name'], $entry);
960 3
                            } else {
961
                                // must use unwrapped value to not trigger orphan removal
962 6
                                $unwrappedValue[$key] = $entry;
963
                            }
964 6
                            $this->persistNew($targetClass, $entry);
965 6
                        }
966 161
                        $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
967 161
                        $this->computeChangeSet($targetClass, $entry);
968 161
                    }
969 318
                    break;
970
971 1
                case self::STATE_REMOVED:
972
                    // Consume the $value as array (it's either an array or an ArrayAccess)
973
                    // and remove the element from Collection.
974 1
                    if ($assoc['type'] === ClassMetadata::MANY) {
975
                        unset($value[$key]);
976
                    }
977 1
                    break;
978
979
                case self::STATE_DETACHED:
980
                    // Can actually not happen right now as we assume STATE_NEW,
981
                    // so the exception will be raised from the DBAL layer (constraint violation).
982
                    throw new \InvalidArgumentException("A detached document was found through a "
983
                        . "relationship during cascading a persist operation.");
984
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
985
986
                default:
987
                    // MANAGED associated documents are already taken into account
988
                    // during changeset calculation anyway, since they are in the identity map.
989
990
            }
991 417
        }
992 417
    }
993
994
    /**
995
     * INTERNAL:
996
     * Computes the changeset of an individual document, independently of the
997
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
998
     *
999
     * The passed document must be a managed document. If the document already has a change set
1000
     * because this method is invoked during a commit cycle then the change sets are added.
1001
     * whereby changes detected in this method prevail.
1002
     *
1003
     * @ignore
1004
     * @param ClassMetadata $class The class descriptor of the document.
1005
     * @param object $document The document for which to (re)calculate the change set.
1006
     * @throws \InvalidArgumentException If the passed document is not MANAGED.
1007
     */
1008 20
    public function recomputeSingleDocumentChangeSet(ClassMetadata $class, $document)
1009
    {
1010
        // Ignore uninitialized proxy objects
1011 20
        if ($document instanceof Proxy && ! $document->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1012 1
            return;
1013
        }
1014
1015 19
        $oid = spl_object_hash($document);
1016
1017 19
        if ( ! isset($this->documentStates[$oid]) || $this->documentStates[$oid] != self::STATE_MANAGED) {
1018
            throw new \InvalidArgumentException('Document must be managed.');
1019
        }
1020
1021 19
        if ( ! $class->isInheritanceTypeNone()) {
1022 2
            $class = $this->dm->getClassMetadata(get_class($document));
1023 2
        }
1024
1025 19
        $this->computeOrRecomputeChangeSet($class, $document, true);
1026 19
    }
1027
1028
    /**
1029
     * @param ClassMetadata $class
1030
     * @param object $document
1031
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1032
     */
1033 572
    private function persistNew(ClassMetadata $class, $document)
1034
    {
1035 572
        $oid = spl_object_hash($document);
1036 572 View Code Duplication
        if ( ! empty($class->lifecycleCallbacks[Events::prePersist])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1037 156
            $class->invokeLifecycleCallbacks(Events::prePersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1038 156
        }
1039 572 View Code Duplication
        if ($this->evm->hasListeners(Events::prePersist)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1040 6
            $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($document, $this->dm));
1041 6
        }
1042
1043 572
        $upsert = false;
1044 572
        if ($class->identifier) {
1045 572
            $idValue = $class->getIdentifierValue($document);
1046 572
            $upsert = ! $class->isEmbeddedDocument && $idValue !== null;
1047
1048 572
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1049 3
                throw new \InvalidArgumentException(sprintf(
1050 3
                    "%s uses NONE identifier generation strategy but no identifier was provided when persisting.",
1051 3
                    get_class($document)
1052 3
                ));
1053
            }
1054
1055 571
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_AUTO && $idValue !== null && ! \MongoId::isValid($idValue)) {
1056 1
                throw new \InvalidArgumentException(sprintf(
1057 1
                    "%s uses AUTO identifier generation strategy but provided identifier is not valid MongoId.",
1058 1
                    get_class($document)
1059 1
                ));
1060
            }
1061
1062 570
            if ($class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1063 499
                $idValue = $class->idGenerator->generate($this->dm, $document);
1064 499
                $idValue = $class->getPHPIdentifierValue($class->getDatabaseIdentifierValue($idValue));
1065 499
                $class->setIdentifierValue($document, $idValue);
1066 499
            }
1067
1068 570
            $this->documentIdentifiers[$oid] = $idValue;
1069 570
        } else {
1070
            // this is for embedded documents without identifiers
1071 142
            $this->documentIdentifiers[$oid] = $oid;
1072
        }
1073
1074 570
        $this->documentStates[$oid] = self::STATE_MANAGED;
1075
1076 570
        if ($upsert) {
1077 81
            $this->scheduleForUpsert($class, $document);
1078 81
        } else {
1079 504
            $this->scheduleForInsert($class, $document);
1080
        }
1081 570
    }
1082
1083
    /**
1084
     * Cascades the postPersist events to embedded documents.
1085
     *
1086
     * @param ClassMetadata $class
1087
     * @param object $document
1088
     */
1089 548
    private function cascadePostPersist(ClassMetadata $class, $document)
1090
    {
1091 548
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1092
1093 548
        $embeddedMappings = array_filter(
1094 548
            $class->associationMappings,
1095
            function($assoc) { return ! empty($assoc['embedded']); }
1096 548
        );
1097
1098 548
        foreach ($embeddedMappings as $mapping) {
1099 334
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1100
1101 334
            if ($value === null) {
1102 213
                continue;
1103
            }
1104
1105 316
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1106
1107 316
            if (isset($mapping['targetDocument'])) {
1108 305
                $embeddedClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1109 305
            }
1110
1111 316
            foreach ($values as $embeddedDocument) {
1112 159
                if ( ! isset($mapping['targetDocument'])) {
1113 13
                    $embeddedClass = $this->dm->getClassMetadata(get_class($embeddedDocument));
1114 13
                }
1115
1116 159 View Code Duplication
                if ( ! empty($embeddedClass->lifecycleCallbacks[Events::postPersist])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1117 9
                    $embeddedClass->invokeLifecycleCallbacks(Events::postPersist, $embeddedDocument, array(new LifecycleEventArgs($document, $this->dm)));
0 ignored issues
show
Bug introduced by
The variable $embeddedClass does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1118 9
                }
1119 159
                if ($hasPostPersistListeners) {
1120 4
                    $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($embeddedDocument, $this->dm));
1121 4
                }
1122 159
                $this->cascadePostPersist($embeddedClass, $embeddedDocument);
1123 316
            }
1124 548
         }
1125 548
     }
1126
1127
    /**
1128
     * Executes all document insertions for documents of the specified type.
1129
     *
1130
     * @param ClassMetadata $class
1131
     * @param array $documents Array of documents to insert
1132
     * @param array $options Array of options to be used with batchInsert()
1133
     */
1134 483 View Code Duplication
    private function executeInserts(ClassMetadata $class, array $documents, array $options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1135
    {
1136 483
        $persister = $this->getDocumentPersister($class->name);
1137
1138 483
        foreach ($documents as $oid => $document) {
1139 483
            $persister->addInsert($document);
1140 483
            unset($this->documentInsertions[$oid]);
1141 483
        }
1142
1143 483
        $persister->executeInserts($options);
1144
1145 482
        $hasPostPersistLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postPersist]);
1146 482
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1147
1148 482
        foreach ($documents as $document) {
1149 482
            if ($hasPostPersistLifecycleCallbacks) {
1150 10
                $class->invokeLifecycleCallbacks(Events::postPersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1151 10
            }
1152 482
            if ($hasPostPersistListeners) {
1153 5
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1154 5
            }
1155 482
            $this->cascadePostPersist($class, $document);
1156 482
        }
1157 482
    }
1158
1159
    /**
1160
     * Executes all document upserts for documents of the specified type.
1161
     *
1162
     * @param ClassMetadata $class
1163
     * @param array $documents Array of documents to upsert
1164
     * @param array $options Array of options to be used with batchInsert()
1165
     */
1166 78 View Code Duplication
    private function executeUpserts(ClassMetadata $class, array $documents, array $options = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1167
    {
1168 78
        $persister = $this->getDocumentPersister($class->name);
1169
1170
1171 78
        foreach ($documents as $oid => $document) {
1172 78
            $persister->addUpsert($document);
1173 78
            unset($this->documentUpserts[$oid]);
1174 78
        }
1175
1176 78
        $persister->executeUpserts($options);
1177
1178 78
        $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]);
1179 78
        $hasListeners = $this->evm->hasListeners(Events::postPersist);
1180
1181 78
        foreach ($documents as $document) {
1182 78
            if ($hasLifecycleCallbacks) {
1183
                $class->invokeLifecycleCallbacks(Events::postPersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1184
            }
1185 78
            if ($hasListeners) {
1186 2
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1187 2
            }
1188 78
            $this->cascadePostPersist($class, $document);
1189 78
        }
1190 78
    }
1191
1192
    /**
1193
     * Executes all document updates for documents of the specified type.
1194
     *
1195
     * @param Mapping\ClassMetadata $class
1196
     * @param array $documents Array of documents to update
1197
     * @param array $options Array of options to be used with update()
1198
     */
1199 215
    private function executeUpdates(ClassMetadata $class, array $documents, array $options = array())
1200
    {
1201 215
        $className = $class->name;
1202 215
        $persister = $this->getDocumentPersister($className);
1203
1204 215
        $hasPreUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::preUpdate]);
1205 215
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1206 215
        $hasPostUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postUpdate]);
1207 215
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1208
1209 215
        foreach ($documents as $oid => $document) {
1210 215
            if ( ! isset($this->documentChangeSets[$oid])) {
1211
                // only ReferenceMany collection is scheduled for update
1212 59
                $this->documentChangeSets[$oid] = array();
1213 59
            }
1214 215 View Code Duplication
            if ($hasPreUpdateLifecycleCallbacks) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1215 12
                $class->invokeLifecycleCallbacks(Events::preUpdate, $document, array(
1216 12
                    new Event\PreUpdateEventArgs($document, $this->dm, $this->documentChangeSets[$oid])
1217 12
                ));
1218 12
                $this->recomputeSingleDocumentChangeSet($class, $document);
1219 12
            }
1220
1221 215 View Code Duplication
            if ($hasPreUpdateListeners) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1222 8
                $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1223 8
                    $document, $this->dm, $this->documentChangeSets[$oid])
1224 8
                );
1225 8
            }
1226 215
            $this->cascadePreUpdate($class, $document);
1227
1228 215
            if ( ! empty($this->documentChangeSets[$oid]) || $this->hasScheduledCollections($document)) {
1229 214
                $persister->update($document, $options);
1230 210
            }
1231
1232 211
            unset($this->documentUpdates[$oid]);
1233
1234 211
            if ($hasPostUpdateLifecycleCallbacks) {
1235 7
                $class->invokeLifecycleCallbacks(Events::postUpdate, $document, array(new LifecycleEventArgs($document, $this->dm)));
1236 7
            }
1237 211
            if ($hasPostUpdateListeners) {
1238 8
                $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($document, $this->dm));
1239 8
            }
1240 211
            $this->cascadePostUpdate($class, $document);
1241 211
        }
1242 210
    }
1243
1244
    /**
1245
     * Cascades the preUpdate event to embedded documents.
1246
     *
1247
     * @param ClassMetadata $class
1248
     * @param object $document
1249
     */
1250 215
    private function cascadePreUpdate(ClassMetadata $class, $document)
1251
    {
1252 215
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1253
1254 215
        $embeddedMappings = array_filter(
1255 215
            $class->associationMappings,
1256
            function ($assoc) { return ! empty($assoc['embedded']); }
1257 215
        );
1258
1259 215
        foreach ($embeddedMappings as $mapping) {
1260 133
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1261
1262 133
            if ($value === null) {
1263 49
                continue;
1264
            }
1265
1266 131
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1267
1268 131
            foreach ($values as $entry) {
1269 84
                $entryOid = spl_object_hash($entry);
1270 84
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1271
1272 84
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1273 47
                    continue;
1274
                }
1275
1276 67
                if (isset($this->documentInsertions[$entryOid])) {
1277 52
                    continue;
1278
                }
1279
1280 45 View Code Duplication
                if ( ! empty($entryClass->lifecycleCallbacks[Events::preUpdate])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1281 5
                    $entryClass->invokeLifecycleCallbacks(Events::preUpdate, $entry, array(
1282 5
                        new Event\PreUpdateEventArgs($entry, $this->dm, $this->documentChangeSets[$entryOid])
1283 5
                    ));
1284 5
                    $this->recomputeSingleDocumentChangeSet($entryClass, $entry);
1285 5
                }
1286 45 View Code Duplication
                if ($hasPreUpdateListeners) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1287 3
                    $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1288 3
                        $entry, $this->dm, $this->documentChangeSets[$entryOid])
1289 3
                    );
1290 3
                }
1291
1292 45
                $this->cascadePreUpdate($entryClass, $entry);
1293 131
            }
1294 215
        }
1295 215
    }
1296
1297
    /**
1298
     * Cascades the postUpdate and postPersist events to embedded documents.
1299
     *
1300
     * @param ClassMetadata $class
1301
     * @param object $document
1302
     */
1303 211
    private function cascadePostUpdate(ClassMetadata $class, $document)
1304
    {
1305 211
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1306 211
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1307
1308 211
        $embeddedMappings = array_filter(
1309 211
            $class->associationMappings,
1310
            function($assoc) { return ! empty($assoc['embedded']); }
1311 211
        );
1312
1313 211
        foreach ($embeddedMappings as $mapping) {
1314 129
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1315
1316 129
            if ($value === null) {
1317 52
                continue;
1318
            }
1319
1320 127
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1321
1322 127
            foreach ($values as $entry) {
1323 84
                $entryOid = spl_object_hash($entry);
1324 84
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1325
1326 84
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1327 47
                    continue;
1328
                }
1329
1330 67
                if (isset($this->documentInsertions[$entryOid])) {
1331 52 View Code Duplication
                    if ( ! empty($entryClass->lifecycleCallbacks[Events::postPersist])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1332 1
                        $entryClass->invokeLifecycleCallbacks(Events::postPersist, $entry, array(
1333 1
                            new LifecycleEventArgs($entry, $this->dm)
1334 1
                        ));
1335 1
                    }
1336 52
                    if ($hasPostPersistListeners) {
1337 3
                        $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entry, $this->dm));
1338 3
                    }
1339 52
                } else {
1340 45 View Code Duplication
                    if ( ! empty($entryClass->lifecycleCallbacks[Events::postUpdate])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1341 9
                        $entryClass->invokeLifecycleCallbacks(Events::postUpdate, $entry, array(
1342 9
                            new LifecycleEventArgs($entry, $this->dm)
1343 9
                        ));
1344 9
                    }
1345 45
                    if ($hasPostUpdateListeners) {
1346 3
                        $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entry, $this->dm));
1347 3
                    }
1348
                }
1349
1350 67
                $this->cascadePostUpdate($entryClass, $entry);
1351 127
            }
1352 211
        }
1353 211
    }
1354
1355
    /**
1356
     * Executes all document deletions for documents of the specified type.
1357
     *
1358
     * @param ClassMetadata $class
1359
     * @param array $documents Array of documents to delete
1360
     * @param array $options Array of options to be used with remove()
1361
     */
1362 62
    private function executeDeletions(ClassMetadata $class, array $documents, array $options = array())
1363
    {
1364 62
        $hasPostRemoveLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postRemove]);
1365 62
        $hasPostRemoveListeners = $this->evm->hasListeners(Events::postRemove);
1366
1367 62
        $persister = $this->getDocumentPersister($class->name);
1368
1369 62
        foreach ($documents as $oid => $document) {
1370 62
            if ( ! $class->isEmbeddedDocument) {
1371 28
                $persister->delete($document, $options);
1372 26
            }
1373
            unset(
1374 60
                $this->documentDeletions[$oid],
1375 60
                $this->documentIdentifiers[$oid],
1376 60
                $this->originalDocumentData[$oid]
1377
            );
1378
1379
            // Clear snapshot information for any referenced PersistentCollection
1380
            // http://www.doctrine-project.org/jira/browse/MODM-95
1381 60
            foreach ($class->associationMappings as $fieldMapping) {
1382 41
                if (isset($fieldMapping['type']) && $fieldMapping['type'] === ClassMetadata::MANY) {
1383 26
                    $value = $class->reflFields[$fieldMapping['fieldName']]->getValue($document);
1384 26
                    if ($value instanceof PersistentCollection) {
1385 22
                        $value->clearSnapshot();
1386 22
                    }
1387 26
                }
1388 60
            }
1389
1390
            // Document with this $oid after deletion treated as NEW, even if the $oid
1391
            // is obtained by a new document because the old one went out of scope.
1392 60
            $this->documentStates[$oid] = self::STATE_NEW;
1393
1394 60
            if ($hasPostRemoveLifecycleCallbacks) {
1395 8
                $class->invokeLifecycleCallbacks(Events::postRemove, $document, array(new LifecycleEventArgs($document, $this->dm)));
1396 8
            }
1397 60
            if ($hasPostRemoveListeners) {
1398 2
                $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($document, $this->dm));
1399 2
            }
1400 60
        }
1401 60
    }
1402
1403
    /**
1404
     * Schedules a document for insertion into the database.
1405
     * If the document already has an identifier, it will be added to the
1406
     * identity map.
1407
     *
1408
     * @param ClassMetadata $class
1409
     * @param object $document The document to schedule for insertion.
1410
     * @throws \InvalidArgumentException
1411
     */
1412 507
    public function scheduleForInsert(ClassMetadata $class, $document)
0 ignored issues
show
Unused Code introduced by
The parameter $class is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1413
    {
1414 507
        $oid = spl_object_hash($document);
1415
1416 507
        if (isset($this->documentUpdates[$oid])) {
1417
            throw new \InvalidArgumentException("Dirty document can not be scheduled for insertion.");
1418
        }
1419 507
        if (isset($this->documentDeletions[$oid])) {
1420
            throw new \InvalidArgumentException("Removed document can not be scheduled for insertion.");
1421
        }
1422 507
        if (isset($this->documentInsertions[$oid])) {
1423
            throw new \InvalidArgumentException("Document can not be scheduled for insertion twice.");
1424
        }
1425
1426 507
        $this->documentInsertions[$oid] = $document;
1427
1428 507
        if (isset($this->documentIdentifiers[$oid])) {
1429 504
            $this->addToIdentityMap($document);
1430 504
        }
1431 507
    }
1432
1433
    /**
1434
     * Schedules a document for upsert into the database and adds it to the
1435
     * identity map
1436
     *
1437
     * @param ClassMetadata $class
1438
     * @param object $document The document to schedule for upsert.
1439
     * @throws \InvalidArgumentException
1440
     */
1441 84
    public function scheduleForUpsert(ClassMetadata $class, $document)
1442
    {
1443 84
        $oid = spl_object_hash($document);
1444
1445 84
        if ($class->isEmbeddedDocument) {
1446
            throw new \InvalidArgumentException("Embedded document can not be scheduled for upsert.");
1447
        }
1448 84
        if (isset($this->documentUpdates[$oid])) {
1449
            throw new \InvalidArgumentException("Dirty document can not be scheduled for upsert.");
1450
        }
1451 84
        if (isset($this->documentDeletions[$oid])) {
1452
            throw new \InvalidArgumentException("Removed document can not be scheduled for upsert.");
1453
        }
1454 84
        if (isset($this->documentUpserts[$oid])) {
1455
            throw new \InvalidArgumentException("Document can not be scheduled for upsert twice.");
1456
        }
1457
1458 84
        $this->documentUpserts[$oid] = $document;
1459 84
        $this->documentIdentifiers[$oid] = $class->getIdentifierValue($document);
1460 84
        $this->addToIdentityMap($document);
1461 84
    }
1462
1463
    /**
1464
     * Checks whether a document is scheduled for insertion.
1465
     *
1466
     * @param object $document
1467
     * @return boolean
1468
     */
1469 70
    public function isScheduledForInsert($document)
1470
    {
1471 70
        return isset($this->documentInsertions[spl_object_hash($document)]);
1472
    }
1473
1474
    /**
1475
     * Checks whether a document is scheduled for upsert.
1476
     *
1477
     * @param object $document
1478
     * @return boolean
1479
     */
1480 5
    public function isScheduledForUpsert($document)
1481
    {
1482 5
        return isset($this->documentUpserts[spl_object_hash($document)]);
1483
    }
1484
1485
    /**
1486
     * Schedules a document for being updated.
1487
     *
1488
     * @param object $document The document to schedule for being updated.
1489
     * @throws \InvalidArgumentException
1490
     */
1491 224
    public function scheduleForUpdate($document)
1492
    {
1493 224
        $oid = spl_object_hash($document);
1494 224
        if ( ! isset($this->documentIdentifiers[$oid])) {
1495
            throw new \InvalidArgumentException("Document has no identity.");
1496
        }
1497
1498 224
        if (isset($this->documentDeletions[$oid])) {
1499
            throw new \InvalidArgumentException("Document is removed.");
1500
        }
1501
1502 224
        if ( ! isset($this->documentUpdates[$oid])
1503 224
            && ! isset($this->documentInsertions[$oid])
1504 224
            && ! isset($this->documentUpserts[$oid])) {
1505 220
            $this->documentUpdates[$oid] = $document;
1506 220
        }
1507 224
    }
1508
1509
    /**
1510
     * Checks whether a document is registered as dirty in the unit of work.
1511
     * Note: Is not very useful currently as dirty documents are only registered
1512
     * at commit time.
1513
     *
1514
     * @param object $document
1515
     * @return boolean
1516
     */
1517 13
    public function isScheduledForUpdate($document)
1518
    {
1519 13
        return isset($this->documentUpdates[spl_object_hash($document)]);
1520
    }
1521
1522 1
    public function isScheduledForDirtyCheck($document)
1523
    {
1524 1
        $class = $this->dm->getClassMetadata(get_class($document));
1525 1
        return isset($this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)]);
1526
    }
1527
1528
    /**
1529
     * INTERNAL:
1530
     * Schedules a document for deletion.
1531
     *
1532
     * @param object $document
1533
     */
1534 67
    public function scheduleForDelete($document)
1535
    {
1536 67
        $oid = spl_object_hash($document);
1537
1538 67
        if (isset($this->documentInsertions[$oid])) {
1539 2
            if ($this->isInIdentityMap($document)) {
1540 2
                $this->removeFromIdentityMap($document);
1541 2
            }
1542 2
            unset($this->documentInsertions[$oid]);
1543 2
            return; // document has not been persisted yet, so nothing more to do.
1544
        }
1545
1546 66
        if ( ! $this->isInIdentityMap($document)) {
1547 1
            return; // ignore
1548
        }
1549
1550 65
        $this->removeFromIdentityMap($document);
1551 65
        $this->documentStates[$oid] = self::STATE_REMOVED;
1552
1553 65
        if (isset($this->documentUpdates[$oid])) {
1554
            unset($this->documentUpdates[$oid]);
1555
        }
1556 65
        if ( ! isset($this->documentDeletions[$oid])) {
1557 65
            $this->documentDeletions[$oid] = $document;
1558 65
        }
1559 65
    }
1560
1561
    /**
1562
     * Checks whether a document is registered as removed/deleted with the unit
1563
     * of work.
1564
     *
1565
     * @param object $document
1566
     * @return boolean
1567
     */
1568 8
    public function isScheduledForDelete($document)
1569
    {
1570 8
        return isset($this->documentDeletions[spl_object_hash($document)]);
1571
    }
1572
1573
    /**
1574
     * Checks whether a document is scheduled for insertion, update or deletion.
1575
     *
1576
     * @param $document
1577
     * @return boolean
1578
     */
1579 224
    public function isDocumentScheduled($document)
1580
    {
1581 224
        $oid = spl_object_hash($document);
1582 224
        return isset($this->documentInsertions[$oid]) ||
1583 121
            isset($this->documentUpserts[$oid]) ||
1584 112
            isset($this->documentUpdates[$oid]) ||
1585 224
            isset($this->documentDeletions[$oid]);
1586
    }
1587
1588
    /**
1589
     * INTERNAL:
1590
     * Registers a document in the identity map.
1591
     *
1592
     * Note that documents in a hierarchy are registered with the class name of
1593
     * the root document. Identifiers are serialized before being used as array
1594
     * keys to allow differentiation of equal, but not identical, values.
1595
     *
1596
     * @ignore
1597
     * @param object $document  The document to register.
1598
     * @return boolean  TRUE if the registration was successful, FALSE if the identity of
1599
     *                  the document in question is already managed.
1600
     */
1601 599
    public function addToIdentityMap($document)
1602
    {
1603 599
        $class = $this->dm->getClassMetadata(get_class($document));
1604 599
        $id = $this->getIdForIdentityMap($document);
1605
1606 599
        if (isset($this->identityMap[$class->name][$id])) {
1607 53
            return false;
1608
        }
1609
1610 599
        $this->identityMap[$class->name][$id] = $document;
1611
1612 599
        if ($document instanceof NotifyPropertyChanged &&
1613 599
            ( ! $document instanceof Proxy || $document->__isInitialized())) {
1614 3
            $document->addPropertyChangedListener($this);
1615 3
        }
1616
1617 599
        return true;
1618
    }
1619
1620
    /**
1621
     * Gets the state of a document with regard to the current unit of work.
1622
     *
1623
     * @param object   $document
1624
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1625
     *                         This parameter can be set to improve performance of document state detection
1626
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1627
     *                         is either known or does not matter for the caller of the method.
1628
     * @return int The document state.
1629
     */
1630 575
    public function getDocumentState($document, $assume = null)
1631
    {
1632 575
        $oid = spl_object_hash($document);
1633
1634 575
        if (isset($this->documentStates[$oid])) {
1635 352
            return $this->documentStates[$oid];
1636
        }
1637
1638 575
        $class = $this->dm->getClassMetadata(get_class($document));
1639
1640 575
        if ($class->isEmbeddedDocument) {
1641 175
            return self::STATE_NEW;
1642
        }
1643
1644 572
        if ($assume !== null) {
1645 569
            return $assume;
1646
        }
1647
1648
        /* State can only be NEW or DETACHED, because MANAGED/REMOVED states are
1649
         * known. Note that you cannot remember the NEW or DETACHED state in
1650
         * _documentStates since the UoW does not hold references to such
1651
         * objects and the object hash can be reused. More generally, because
1652
         * the state may "change" between NEW/DETACHED without the UoW being
1653
         * aware of it.
1654
         */
1655 4
        $id = $class->getIdentifierObject($document);
1656
1657 4
        if ($id === null) {
1658 2
            return self::STATE_NEW;
1659
        }
1660
1661
        // Check for a version field, if available, to avoid a DB lookup.
1662 2
        if ($class->isVersioned) {
1663
            return ($class->getFieldValue($document, $class->versionField))
1664
                ? self::STATE_DETACHED
1665
                : self::STATE_NEW;
1666
        }
1667
1668
        // Last try before DB lookup: check the identity map.
1669 2
        if ($this->tryGetById($id, $class)) {
1670 1
            return self::STATE_DETACHED;
1671
        }
1672
1673
        // DB lookup
1674 2
        if ($this->getDocumentPersister($class->name)->exists($document)) {
1675 1
            return self::STATE_DETACHED;
1676
        }
1677
1678 1
        return self::STATE_NEW;
1679
    }
1680
1681
    /**
1682
     * INTERNAL:
1683
     * Removes a document from the identity map. This effectively detaches the
1684
     * document from the persistence management of Doctrine.
1685
     *
1686
     * @ignore
1687
     * @param object $document
1688
     * @throws \InvalidArgumentException
1689
     * @return boolean
1690
     */
1691 76
    public function removeFromIdentityMap($document)
1692
    {
1693 76
        $oid = spl_object_hash($document);
1694
1695
        // Check if id is registered first
1696 76
        if ( ! isset($this->documentIdentifiers[$oid])) {
1697
            return false;
1698
        }
1699
1700 76
        $class = $this->dm->getClassMetadata(get_class($document));
1701 76
        $id = $this->getIdForIdentityMap($document);
1702
1703 76
        if (isset($this->identityMap[$class->name][$id])) {
1704 76
            unset($this->identityMap[$class->name][$id]);
1705 76
            $this->documentStates[$oid] = self::STATE_DETACHED;
1706 76
            return true;
1707
        }
1708
1709
        return false;
1710
    }
1711
1712
    /**
1713
     * INTERNAL:
1714
     * Gets a document in the identity map by its identifier hash.
1715
     *
1716
     * @ignore
1717
     * @param mixed         $id    Document identifier
1718
     * @param ClassMetadata $class Document class
1719
     * @return object
1720
     * @throws InvalidArgumentException if the class does not have an identifier
1721
     */
1722 31
    public function getById($id, ClassMetadata $class)
1723
    {
1724 31
        if ( ! $class->identifier) {
1725
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1726
        }
1727
1728 31
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1729
1730 31
        return $this->identityMap[$class->name][$serializedId];
1731
    }
1732
1733
    /**
1734
     * INTERNAL:
1735
     * Tries to get a document by its identifier hash. If no document is found
1736
     * for the given hash, FALSE is returned.
1737
     *
1738
     * @ignore
1739
     * @param mixed         $id    Document identifier
1740
     * @param ClassMetadata $class Document class
1741
     * @return mixed The found document or FALSE.
1742
     * @throws InvalidArgumentException if the class does not have an identifier
1743
     */
1744 290
    public function tryGetById($id, ClassMetadata $class)
1745
    {
1746 290
        if ( ! $class->identifier) {
1747
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1748
        }
1749
1750 290
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1751
1752 290
        return isset($this->identityMap[$class->name][$serializedId]) ?
1753 290
            $this->identityMap[$class->name][$serializedId] : false;
1754
    }
1755
1756
    /**
1757
     * Schedules a document for dirty-checking at commit-time.
1758
     *
1759
     * @param object $document The document to schedule for dirty-checking.
1760
     * @todo Rename: scheduleForSynchronization
1761
     */
1762 2
    public function scheduleForDirtyCheck($document)
1763
    {
1764 2
        $class = $this->dm->getClassMetadata(get_class($document));
1765 2
        $this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)] = $document;
1766 2
    }
1767
1768
    /**
1769
     * Checks whether a document is registered in the identity map.
1770
     *
1771
     * @param object $document
1772
     * @return boolean
1773
     */
1774 76
    public function isInIdentityMap($document)
1775
    {
1776 76
        $oid = spl_object_hash($document);
1777
1778 76
        if ( ! isset($this->documentIdentifiers[$oid])) {
1779 4
            return false;
1780
        }
1781
1782 75
        $class = $this->dm->getClassMetadata(get_class($document));
1783 75
        $id = $this->getIdForIdentityMap($document);
1784
1785 75
        return isset($this->identityMap[$class->name][$id]);
1786
    }
1787
1788
    /**
1789
     * @param object $document
1790
     * @return string
1791
     */
1792 599
    private function getIdForIdentityMap($document)
1793
    {
1794 599
        $class = $this->dm->getClassMetadata(get_class($document));
1795
1796 599
        if ( ! $class->identifier) {
1797 145
            $id = spl_object_hash($document);
1798 145
        } else {
1799 598
            $id = $this->documentIdentifiers[spl_object_hash($document)];
1800 598
            $id = serialize($class->getDatabaseIdentifierValue($id));
1801
        }
1802
1803 599
        return $id;
1804
    }
1805
1806
    /**
1807
     * INTERNAL:
1808
     * Checks whether an identifier exists in the identity map.
1809
     *
1810
     * @ignore
1811
     * @param string $id
1812
     * @param string $rootClassName
1813
     * @return boolean
1814
     */
1815
    public function containsId($id, $rootClassName)
1816
    {
1817
        return isset($this->identityMap[$rootClassName][serialize($id)]);
1818
    }
1819
1820
    /**
1821
     * Persists a document as part of the current unit of work.
1822
     *
1823
     * @param object $document The document to persist.
1824
     * @throws MongoDBException If trying to persist MappedSuperclass.
1825
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1826
     */
1827 570
    public function persist($document)
1828
    {
1829 570
        $class = $this->dm->getClassMetadata(get_class($document));
1830 570
        if ($class->isMappedSuperclass) {
1831 1
            throw MongoDBException::cannotPersistMappedSuperclass($class->name);
1832
        }
1833 569
        $visited = array();
1834 569
        $this->doPersist($document, $visited);
1835 565
    }
1836
1837
    /**
1838
     * Saves a document as part of the current unit of work.
1839
     * This method is internally called during save() cascades as it tracks
1840
     * the already visited documents to prevent infinite recursions.
1841
     *
1842
     * NOTE: This method always considers documents that are not yet known to
1843
     * this UnitOfWork as NEW.
1844
     *
1845
     * @param object $document The document to persist.
1846
     * @param array $visited The already visited documents.
1847
     * @throws \InvalidArgumentException
1848
     * @throws MongoDBException
1849
     */
1850 569
    private function doPersist($document, array &$visited)
1851
    {
1852 569
        $oid = spl_object_hash($document);
1853 569
        if (isset($visited[$oid])) {
1854 24
            return; // Prevent infinite recursion
1855
        }
1856
1857 569
        $visited[$oid] = $document; // Mark visited
1858
1859 569
        $class = $this->dm->getClassMetadata(get_class($document));
1860
1861 569
        $documentState = $this->getDocumentState($document, self::STATE_NEW);
1862
        switch ($documentState) {
1863 569
            case self::STATE_MANAGED:
1864
                // Nothing to do, except if policy is "deferred explicit"
1865 44
                if ($class->isChangeTrackingDeferredExplicit()) {
1866
                    $this->scheduleForDirtyCheck($document);
1867
                }
1868 44
                break;
1869 569
            case self::STATE_NEW:
1870 569
                $this->persistNew($class, $document);
1871 567
                break;
1872
1873 2
            case self::STATE_REMOVED:
1874
                // Document becomes managed again
1875 2
                unset($this->documentDeletions[$oid]);
1876
1877 2
                $this->documentStates[$oid] = self::STATE_MANAGED;
1878 2
                break;
1879
1880
            case self::STATE_DETACHED:
1881
                throw new \InvalidArgumentException(
1882
                    "Behavior of persist() for a detached document is not yet defined.");
1883
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1884
1885
            default:
1886
                throw MongoDBException::invalidDocumentState($documentState);
1887
        }
1888
1889 567
        $this->cascadePersist($document, $visited);
1890 565
    }
1891
1892
    /**
1893
     * Deletes a document as part of the current unit of work.
1894
     *
1895
     * @param object $document The document to remove.
1896
     */
1897 66
    public function remove($document)
1898
    {
1899 66
        $visited = array();
1900 66
        $this->doRemove($document, $visited);
1901 66
    }
1902
1903
    /**
1904
     * Deletes a document as part of the current unit of work.
1905
     *
1906
     * This method is internally called during delete() cascades as it tracks
1907
     * the already visited documents to prevent infinite recursions.
1908
     *
1909
     * @param object $document The document to delete.
1910
     * @param array $visited The map of the already visited documents.
1911
     * @throws MongoDBException
1912
     */
1913 66
    private function doRemove($document, array &$visited)
1914
    {
1915 66
        $oid = spl_object_hash($document);
1916 66
        if (isset($visited[$oid])) {
1917 1
            return; // Prevent infinite recursion
1918
        }
1919
1920 66
        $visited[$oid] = $document; // mark visited
1921
1922
        /* Cascade first, because scheduleForDelete() removes the entity from
1923
         * the identity map, which can cause problems when a lazy Proxy has to
1924
         * be initialized for the cascade operation.
1925
         */
1926 66
        $this->cascadeRemove($document, $visited);
1927
1928 66
        $class = $this->dm->getClassMetadata(get_class($document));
1929 66
        $documentState = $this->getDocumentState($document);
1930
        switch ($documentState) {
1931 66
            case self::STATE_NEW:
1932 66
            case self::STATE_REMOVED:
1933
                // nothing to do
1934 1
                break;
1935 66
            case self::STATE_MANAGED:
1936 66 View Code Duplication
                if ( ! empty($class->lifecycleCallbacks[Events::preRemove])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1937 8
                    $class->invokeLifecycleCallbacks(Events::preRemove, $document, array(new LifecycleEventArgs($document, $this->dm)));
1938 8
                }
1939 66 View Code Duplication
                if ($this->evm->hasListeners(Events::preRemove)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1940 1
                    $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($document, $this->dm));
1941 1
                }
1942 66
                $this->scheduleForDelete($document);
1943 66
                break;
1944
            case self::STATE_DETACHED:
1945
                throw MongoDBException::detachedDocumentCannotBeRemoved();
1946
            default:
1947
                throw MongoDBException::invalidDocumentState($documentState);
1948
        }
1949 66
    }
1950
1951
    /**
1952
     * Merges the state of the given detached document into this UnitOfWork.
1953
     *
1954
     * @param object $document
1955
     * @return object The managed copy of the document.
1956
     */
1957 13
    public function merge($document)
1958
    {
1959 13
        $visited = array();
1960
1961 13
        return $this->doMerge($document, $visited);
1962
    }
1963
1964
    /**
1965
     * Executes a merge operation on a document.
1966
     *
1967
     * @param object      $document
1968
     * @param array       $visited
1969
     * @param object|null $prevManagedCopy
1970
     * @param array|null  $assoc
1971
     *
1972
     * @return object The managed copy of the document.
1973
     *
1974
     * @throws InvalidArgumentException If the entity instance is NEW.
1975
     * @throws LockException If the document uses optimistic locking through a
1976
     *                       version attribute and the version check against the
1977
     *                       managed copy fails.
1978
     */
1979 13
    private function doMerge($document, array &$visited, $prevManagedCopy = null, $assoc = null)
1980
    {
1981 13
        $oid = spl_object_hash($document);
1982
1983 13
        if (isset($visited[$oid])) {
1984 1
            return $visited[$oid]; // Prevent infinite recursion
1985
        }
1986
1987 13
        $visited[$oid] = $document; // mark visited
1988
1989 13
        $class = $this->dm->getClassMetadata(get_class($document));
1990
1991
        /* First we assume DETACHED, although it can still be NEW but we can
1992
         * avoid an extra DB round trip this way. If it is not MANAGED but has
1993
         * an identity, we need to fetch it from the DB anyway in order to
1994
         * merge. MANAGED documents are ignored by the merge operation.
1995
         */
1996 13
        $managedCopy = $document;
1997
1998 13
        if ($this->getDocumentState($document, self::STATE_DETACHED) !== self::STATE_MANAGED) {
1999 13
            if ($document instanceof Proxy && ! $document->__isInitialized()) {
2000
                $document->__load();
2001
            }
2002
2003
            // Try to look the document up in the identity map.
2004 13
            $id = $class->isEmbeddedDocument ? null : $class->getIdentifierObject($document);
2005
2006 13
            if ($id === null) {
2007
                // If there is no identifier, it is actually NEW.
2008 5
                $managedCopy = $class->newInstance();
2009 5
                $this->persistNew($class, $managedCopy);
2010 5
            } else {
2011 10
                $managedCopy = $this->tryGetById($id, $class);
2012
2013 10
                if ($managedCopy) {
2014
                    // We have the document in memory already, just make sure it is not removed.
2015 5
                    if ($this->getDocumentState($managedCopy) === self::STATE_REMOVED) {
2016
                        throw new \InvalidArgumentException('Removed entity detected during merge. Cannot merge with a removed entity.');
2017
                    }
2018 5
                } else {
2019
                    // We need to fetch the managed copy in order to merge.
2020 7
                    $managedCopy = $this->dm->find($class->name, $id);
2021
                }
2022
2023 10
                if ($managedCopy === null) {
2024
                    // If the identifier is ASSIGNED, it is NEW
2025
                    $managedCopy = $class->newInstance();
2026
                    $class->setIdentifierValue($managedCopy, $id);
2027
                    $this->persistNew($class, $managedCopy);
2028
                } else {
2029 10
                    if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2030
                        $managedCopy->__load();
2031
                    }
2032
                }
2033
            }
2034
2035 13
            if ($class->isVersioned) {
2036
                $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
2037
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2038
2039
                // Throw exception if versions don't match
2040
                if ($managedCopyVersion != $documentVersion) {
2041
                    throw LockException::lockFailedVersionMissmatch($document, $documentVersion, $managedCopyVersion);
2042
                }
2043
            }
2044
2045
            // Merge state of $document into existing (managed) document
2046 13
            foreach ($class->reflClass->getProperties() as $prop) {
2047 13
                $name = $prop->name;
2048 13
                $prop->setAccessible(true);
2049 13
                if ( ! isset($class->associationMappings[$name])) {
2050 13
                    if ( ! $class->isIdentifier($name)) {
2051 13
                        $prop->setValue($managedCopy, $prop->getValue($document));
2052 13
                    }
2053 13
                } else {
2054 13
                    $assoc2 = $class->associationMappings[$name];
2055
2056 13
                    if ($assoc2['type'] === 'one') {
2057 5
                        $other = $prop->getValue($document);
2058
2059 5
                        if ($other === null) {
2060 2
                            $prop->setValue($managedCopy, null);
2061 5
                        } elseif ($other instanceof Proxy && ! $other->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2062
                            // Do not merge fields marked lazy that have not been fetched
2063 1
                            continue;
2064 3
                        } elseif ( ! $assoc2['isCascadeMerge']) {
2065
                            if ($this->getDocumentState($other) === self::STATE_DETACHED) {
2066
                                $targetDocument = isset($assoc2['targetDocument']) ? $assoc2['targetDocument'] : get_class($other);
2067
                                /* @var $targetClass \Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo */
2068
                                $targetClass = $this->dm->getClassMetadata($targetDocument);
2069
                                $relatedId = $targetClass->getIdentifierObject($other);
2070
2071
                                if ($targetClass->subClasses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $targetClass->subClasses 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...
2072
                                    $other = $this->dm->find($targetClass->name, $relatedId);
2073
                                } else {
2074
                                    $other = $this
2075
                                        ->dm
2076
                                        ->getProxyFactory()
2077
                                        ->getProxy($assoc2['targetDocument'], array($targetClass->identifier => $relatedId));
2078
                                    $this->registerManaged($other, $relatedId, array());
0 ignored issues
show
Documentation introduced by
$relatedId is of type object<MongoId>, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2079
                                }
2080
                            }
2081
2082
                            $prop->setValue($managedCopy, $other);
2083
                        }
2084 4
                    } else {
2085 10
                        $mergeCol = $prop->getValue($document);
2086
2087 10
                        if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) {
2088
                            /* Do not merge fields marked lazy that have not
2089
                             * been fetched. Keep the lazy persistent collection
2090
                             * of the managed copy.
2091
                             */
2092 3
                            continue;
2093
                        }
2094
2095 7
                        $managedCol = $prop->getValue($managedCopy);
2096
2097 7
                        if ( ! $managedCol) {
2098 2
                            $managedCol = new PersistentCollection(new ArrayCollection(), $this->dm, $this);
2099 2
                            $managedCol->setOwner($managedCopy, $assoc2);
2100 2
                            $prop->setValue($managedCopy, $managedCol);
2101 2
                            $this->originalDocumentData[$oid][$name] = $managedCol;
2102 2
                        }
2103
2104
                        /* Note: do not process association's target documents.
2105
                         * They will be handled during the cascade. Initialize
2106
                         * and, if necessary, clear $managedCol for now.
2107
                         */
2108 7
                        if ($assoc2['isCascadeMerge']) {
2109 7
                            $managedCol->initialize();
2110
2111
                            // If $managedCol differs from the merged collection, clear and set dirty
2112 7
                            if ( ! $managedCol->isEmpty() && $managedCol !== $mergeCol) {
2113 2
                                $managedCol->unwrap()->clear();
2114 2
                                $managedCol->setDirty(true);
2115
2116 2
                                if ($assoc2['isOwningSide'] && $class->isChangeTrackingNotify()) {
2117
                                    $this->scheduleForDirtyCheck($managedCopy);
2118
                                }
2119 2
                            }
2120 7
                        }
2121
                    }
2122
                }
2123
2124 13
                if ($class->isChangeTrackingNotify()) {
2125
                    // Just treat all properties as changed, there is no other choice.
2126
                    $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy));
2127
                }
2128 13
            }
2129
2130 13
            if ($class->isChangeTrackingDeferredExplicit()) {
2131
                $this->scheduleForDirtyCheck($document);
2132
            }
2133 13
        }
2134
2135 13
        if ($prevManagedCopy !== null) {
2136 6
            $assocField = $assoc['fieldName'];
2137 6
            $prevClass = $this->dm->getClassMetadata(get_class($prevManagedCopy));
2138
2139 6
            if ($assoc['type'] === 'one') {
2140 2
                $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
2141 2
            } else {
2142 4
                $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
2143
2144 4
                if ($assoc['type'] === 'many' && isset($assoc['mappedBy'])) {
2145 1
                    $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy);
2146 1
                }
2147
            }
2148 6
        }
2149
2150
        // Mark the managed copy visited as well
2151 13
        $visited[spl_object_hash($managedCopy)] = true;
2152
2153 13
        $this->cascadeMerge($document, $managedCopy, $visited);
2154
2155 13
        return $managedCopy;
2156
    }
2157
2158
    /**
2159
     * Detaches a document from the persistence management. It's persistence will
2160
     * no longer be managed by Doctrine.
2161
     *
2162
     * @param object $document The document to detach.
2163
     */
2164 9
    public function detach($document)
2165
    {
2166 9
        $visited = array();
2167 9
        $this->doDetach($document, $visited);
2168 9
    }
2169
2170
    /**
2171
     * Executes a detach operation on the given document.
2172
     *
2173
     * @param object $document
2174
     * @param array $visited
2175
     * @internal This method always considers documents with an assigned identifier as DETACHED.
2176
     */
2177 12
    private function doDetach($document, array &$visited)
2178
    {
2179 12
        $oid = spl_object_hash($document);
2180 12
        if (isset($visited[$oid])) {
2181 4
            return; // Prevent infinite recursion
2182
        }
2183
2184 12
        $visited[$oid] = $document; // mark visited
2185
2186 12
        switch ($this->getDocumentState($document, self::STATE_DETACHED)) {
2187 12
            case self::STATE_MANAGED:
2188 12
                $this->removeFromIdentityMap($document);
2189 12
                unset($this->documentInsertions[$oid], $this->documentUpdates[$oid],
2190 12
                    $this->documentDeletions[$oid], $this->documentIdentifiers[$oid],
2191 12
                    $this->documentStates[$oid], $this->originalDocumentData[$oid],
2192 12
                    $this->parentAssociations[$oid], $this->documentUpserts[$oid],
2193 12
                    $this->hasScheduledCollections[$oid]);
2194 12
                break;
2195 4
            case self::STATE_NEW:
2196 4
            case self::STATE_DETACHED:
2197 4
                return;
2198 12
        }
2199
2200 12
        $this->cascadeDetach($document, $visited);
2201 12
    }
2202
2203
    /**
2204
     * Refreshes the state of the given document from the database, overwriting
2205
     * any local, unpersisted changes.
2206
     *
2207
     * @param object $document The document to refresh.
2208
     * @throws \InvalidArgumentException If the document is not MANAGED.
2209
     */
2210 20
    public function refresh($document)
2211
    {
2212 20
        $visited = array();
2213 20
        $this->doRefresh($document, $visited);
2214 19
    }
2215
2216
    /**
2217
     * Executes a refresh operation on a document.
2218
     *
2219
     * @param object $document The document to refresh.
2220
     * @param array $visited The already visited documents during cascades.
2221
     * @throws \InvalidArgumentException If the document is not MANAGED.
2222
     */
2223 20
    private function doRefresh($document, array &$visited)
2224
    {
2225 20
        $oid = spl_object_hash($document);
2226 20
        if (isset($visited[$oid])) {
2227
            return; // Prevent infinite recursion
2228
        }
2229
2230 20
        $visited[$oid] = $document; // mark visited
2231
2232 20
        $class = $this->dm->getClassMetadata(get_class($document));
2233
2234 20
        if ( ! $class->isEmbeddedDocument) {
2235 20
            if ($this->getDocumentState($document) == self::STATE_MANAGED) {
2236 19
                $id = $class->getDatabaseIdentifierValue($this->documentIdentifiers[$oid]);
2237 19
                $this->getDocumentPersister($class->name)->refresh($id, $document);
2238 19
            } else {
2239 1
                throw new \InvalidArgumentException("Document is not MANAGED.");
2240
            }
2241 19
        }
2242
2243 19
        $this->cascadeRefresh($document, $visited);
2244 19
    }
2245
2246
    /**
2247
     * Cascades a refresh operation to associated documents.
2248
     *
2249
     * @param object $document
2250
     * @param array $visited
2251
     */
2252 19
    private function cascadeRefresh($document, array &$visited)
2253
    {
2254 19
        $class = $this->dm->getClassMetadata(get_class($document));
2255
2256 19
        $associationMappings = array_filter(
2257 19
            $class->associationMappings,
2258
            function ($assoc) { return $assoc['isCascadeRefresh']; }
2259 19
        );
2260
2261 19
        foreach ($associationMappings as $mapping) {
2262 15
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2263 15
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2264 15
                if ($relatedDocuments instanceof PersistentCollection) {
2265
                    // Unwrap so that foreach() does not initialize
2266 15
                    $relatedDocuments = $relatedDocuments->unwrap();
2267 15
                }
2268 15
                foreach ($relatedDocuments as $relatedDocument) {
2269
                    $this->doRefresh($relatedDocument, $visited);
2270 15
                }
2271 15
            } elseif ($relatedDocuments !== null) {
2272 2
                $this->doRefresh($relatedDocuments, $visited);
2273 2
            }
2274 19
        }
2275 19
    }
2276
2277
    /**
2278
     * Cascades a detach operation to associated documents.
2279
     *
2280
     * @param object $document
2281
     * @param array $visited
2282
     */
2283 12 View Code Duplication
    private function cascadeDetach($document, array &$visited)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2284
    {
2285 12
        $class = $this->dm->getClassMetadata(get_class($document));
2286 12
        foreach ($class->fieldMappings as $mapping) {
2287 12
            if ( ! $mapping['isCascadeDetach']) {
2288 12
                continue;
2289
            }
2290 7
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2291 7
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2292 7
                if ($relatedDocuments instanceof PersistentCollection) {
2293
                    // Unwrap so that foreach() does not initialize
2294 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2295 6
                }
2296 7
                foreach ($relatedDocuments as $relatedDocument) {
2297 5
                    $this->doDetach($relatedDocument, $visited);
2298 7
                }
2299 7
            } elseif ($relatedDocuments !== null) {
2300 5
                $this->doDetach($relatedDocuments, $visited);
2301 5
            }
2302 12
        }
2303 12
    }
2304
    /**
2305
     * Cascades a merge operation to associated documents.
2306
     *
2307
     * @param object $document
2308
     * @param object $managedCopy
2309
     * @param array $visited
2310
     */
2311 13
    private function cascadeMerge($document, $managedCopy, array &$visited)
2312
    {
2313 13
        $class = $this->dm->getClassMetadata(get_class($document));
2314
2315 13
        $associationMappings = array_filter(
2316 13
            $class->associationMappings,
2317
            function ($assoc) { return $assoc['isCascadeMerge']; }
2318 13
        );
2319
2320 13
        foreach ($associationMappings as $assoc) {
2321 12
            $relatedDocuments = $class->reflFields[$assoc['fieldName']]->getValue($document);
2322
2323 12
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2324 8
                if ($relatedDocuments === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
2325
                    // Collections are the same, so there is nothing to do
2326
                    continue;
2327
                }
2328
2329 8
                if ($relatedDocuments instanceof PersistentCollection) {
2330
                    // Unwrap so that foreach() does not initialize
2331 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2332 6
                }
2333
2334 8
                foreach ($relatedDocuments as $relatedDocument) {
2335 4
                    $this->doMerge($relatedDocument, $visited, $managedCopy, $assoc);
2336 8
                }
2337 12
            } elseif ($relatedDocuments !== null) {
2338 3
                $this->doMerge($relatedDocuments, $visited, $managedCopy, $assoc);
2339 3
            }
2340 13
        }
2341 13
    }
2342
2343
    /**
2344
     * Cascades the save operation to associated documents.
2345
     *
2346
     * @param object $document
2347
     * @param array $visited
2348
     */
2349 567
    private function cascadePersist($document, array &$visited)
2350
    {
2351 567
        $class = $this->dm->getClassMetadata(get_class($document));
2352
2353 567
        $associationMappings = array_filter(
2354 567
            $class->associationMappings,
2355
            function ($assoc) { return $assoc['isCascadePersist']; }
2356 567
        );
2357
2358 567
        foreach ($associationMappings as $fieldName => $mapping) {
2359 389
            $relatedDocuments = $class->reflFields[$fieldName]->getValue($document);
2360
2361 389
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2362 339
                if ($relatedDocuments instanceof PersistentCollection) {
2363 17
                    if ($relatedDocuments->getOwner() !== $document) {
2364 2
                        $relatedDocuments = $this->fixPersistentCollectionOwnership($relatedDocuments, $document, $class, $mapping['fieldName']);
2365 2
                    }
2366
                    // Unwrap so that foreach() does not initialize
2367 17
                    $relatedDocuments = $relatedDocuments->unwrap();
2368 17
                }
2369
2370 339
                $count = 0;
2371 339
                foreach ($relatedDocuments as $relatedKey => $relatedDocument) {
2372 187
                    if ( ! empty($mapping['embedded'])) {
2373 113
                        list(, $knownParent, ) = $this->getParentAssociation($relatedDocument);
2374 113
                        if ($knownParent && $knownParent !== $document) {
2375 4
                            $relatedDocument = clone $relatedDocument;
2376 4
                            $relatedDocuments[$relatedKey] = $relatedDocument;
2377 4
                        }
2378 113
                        $pathKey = ! isset($mapping['strategy']) || CollectionHelper::isList($mapping['strategy']) ? $count++ : $relatedKey;
2379 113
                        $this->setParentAssociation($relatedDocument, $mapping, $document, $mapping['fieldName'] . '.' . $pathKey);
2380 113
                    }
2381 187
                    $this->doPersist($relatedDocument, $visited);
2382 338
                }
2383 389
            } elseif ($relatedDocuments !== null) {
2384 120
                if ( ! empty($mapping['embedded'])) {
2385 66
                    list(, $knownParent, ) = $this->getParentAssociation($relatedDocuments);
2386 66
                    if ($knownParent && $knownParent !== $document) {
2387 5
                        $relatedDocuments = clone $relatedDocuments;
2388 5
                        $class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments);
2389 5
                    }
2390 66
                    $this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']);
2391 66
                }
2392 120
                $this->doPersist($relatedDocuments, $visited);
2393 119
            }
2394 566
        }
2395 565
    }
2396
2397
    /**
2398
     * Cascades the delete operation to associated documents.
2399
     *
2400
     * @param object $document
2401
     * @param array $visited
2402
     */
2403 66 View Code Duplication
    private function cascadeRemove($document, array &$visited)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2404
    {
2405 66
        $class = $this->dm->getClassMetadata(get_class($document));
2406 66
        foreach ($class->fieldMappings as $mapping) {
2407 66
            if ( ! $mapping['isCascadeRemove']) {
2408 66
                continue;
2409
            }
2410 33
            if ($document instanceof Proxy && ! $document->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2411 2
                $document->__load();
2412 2
            }
2413
2414 33
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2415 33
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2416
                // If its a PersistentCollection initialization is intended! No unwrap!
2417 24
                foreach ($relatedDocuments as $relatedDocument) {
2418 13
                    $this->doRemove($relatedDocument, $visited);
2419 24
                }
2420 33
            } elseif ($relatedDocuments !== null) {
2421 12
                $this->doRemove($relatedDocuments, $visited);
2422 12
            }
2423 66
        }
2424 66
    }
2425
2426
    /**
2427
     * Acquire a lock on the given document.
2428
     *
2429
     * @param object $document
2430
     * @param int $lockMode
2431
     * @param int $lockVersion
2432
     * @throws LockException
2433
     * @throws \InvalidArgumentException
2434
     */
2435 9
    public function lock($document, $lockMode, $lockVersion = null)
2436
    {
2437 9
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2438 1
            throw new \InvalidArgumentException("Document is not MANAGED.");
2439
        }
2440
2441 8
        $documentName = get_class($document);
2442 8
        $class = $this->dm->getClassMetadata($documentName);
2443
2444 8
        if ($lockMode == LockMode::OPTIMISTIC) {
2445 3
            if ( ! $class->isVersioned) {
2446 1
                throw LockException::notVersioned($documentName);
2447
            }
2448
2449 2
            if ($lockVersion != null) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $lockVersion of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison !== instead.
Loading history...
2450 2
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2451 2
                if ($documentVersion != $lockVersion) {
2452 1
                    throw LockException::lockFailedVersionMissmatch($document, $lockVersion, $documentVersion);
2453
                }
2454 1
            }
2455 6
        } elseif (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) {
2456 5
            $this->getDocumentPersister($class->name)->lock($document, $lockMode);
2457 5
        }
2458 6
    }
2459
2460
    /**
2461
     * Releases a lock on the given document.
2462
     *
2463
     * @param object $document
2464
     * @throws \InvalidArgumentException
2465
     */
2466 1
    public function unlock($document)
2467
    {
2468 1
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2469
            throw new \InvalidArgumentException("Document is not MANAGED.");
2470
        }
2471 1
        $documentName = get_class($document);
2472 1
        $this->getDocumentPersister($documentName)->unlock($document);
2473 1
    }
2474
2475
    /**
2476
     * Clears the UnitOfWork.
2477
     *
2478
     * @param string|null $documentName if given, only documents of this type will get detached.
2479
     */
2480 386
    public function clear($documentName = null)
2481
    {
2482 386
        if ($documentName === null) {
2483 380
            $this->identityMap =
2484 380
            $this->documentIdentifiers =
2485 380
            $this->originalDocumentData =
2486 380
            $this->documentChangeSets =
2487 380
            $this->documentStates =
2488 380
            $this->scheduledForDirtyCheck =
2489 380
            $this->documentInsertions =
2490 380
            $this->documentUpserts =
2491 380
            $this->documentUpdates =
2492 380
            $this->documentDeletions =
2493 380
            $this->collectionUpdates =
2494 380
            $this->collectionDeletions =
2495 380
            $this->parentAssociations =
2496 380
            $this->orphanRemovals = 
2497 380
            $this->hasScheduledCollections = array();
2498 380
        } else {
2499 6
            $visited = array();
2500 6
            foreach ($this->identityMap as $className => $documents) {
2501 6
                if ($className === $documentName) {
2502 3
                    foreach ($documents as $document) {
2503 3
                        $this->doDetach($document, $visited, true);
0 ignored issues
show
Unused Code introduced by
The call to UnitOfWork::doDetach() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2504 3
                    }
2505 3
                }
2506 6
            }
2507
        }
2508
2509 386 View Code Duplication
        if ($this->evm->hasListeners(Events::onClear)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2510
            $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->dm, $documentName));
2511
        }
2512 386
    }
2513
2514
    /**
2515
     * INTERNAL:
2516
     * Schedules an embedded document for removal. The remove() operation will be
2517
     * invoked on that document at the beginning of the next commit of this
2518
     * UnitOfWork.
2519
     *
2520
     * @ignore
2521
     * @param object $document
2522
     */
2523 47
    public function scheduleOrphanRemoval($document)
2524
    {
2525 47
        $this->orphanRemovals[spl_object_hash($document)] = $document;
2526 47
    }
2527
2528
    /**
2529
     * INTERNAL:
2530
     * Unschedules an embedded or referenced object for removal.
2531
     *
2532
     * @ignore
2533
     * @param object $document
2534
     */
2535 103
    public function unscheduleOrphanRemoval($document)
2536
    {
2537 103
        $oid = spl_object_hash($document);
2538 103
        if (isset($this->orphanRemovals[$oid])) {
2539 1
            unset($this->orphanRemovals[$oid]);
2540 1
        }
2541 103
    }
2542
2543
    /**
2544
     * Fixes PersistentCollection state if it wasn't used exactly as we had in mind:
2545
     *  1) sets owner if it was cloned
2546
     *  2) clones collection, sets owner, updates document's property and, if necessary, updates originalData
2547
     *  3) NOP if state is OK
2548
     * Returned collection should be used from now on (only important with 2nd point)
2549
     *
2550
     * @param PersistentCollection $coll
2551
     * @param object $document
2552
     * @param ClassMetadata $class
2553
     * @param string $propName
2554
     * @return PersistentCollection
2555
     */
2556 8
    private function fixPersistentCollectionOwnership(PersistentCollection $coll, $document, ClassMetadata $class, $propName)
2557
    {
2558 8
        $owner = $coll->getOwner();
2559 8
        if ($owner === null) { // cloned
2560 6
            $coll->setOwner($document, $class->fieldMappings[$propName]);
2561 8
        } elseif ($owner !== $document) { // no clone, we have to fix
2562 2
            if ( ! $coll->isInitialized()) {
2563 1
                $coll->initialize(); // we have to do this otherwise the cols share state
2564 1
            }
2565 2
            $newValue = clone $coll;
2566 2
            $newValue->setOwner($document, $class->fieldMappings[$propName]);
2567 2
            $class->reflFields[$propName]->setValue($document, $newValue);
2568 2
            if ($this->isScheduledForUpdate($document)) {
2569
                // @todo following line should be superfluous once collections are stored in change sets
2570
                $this->setOriginalDocumentProperty(spl_object_hash($document), $propName, $newValue);
2571
            }
2572 2
            return $newValue;
2573
        }
2574 6
        return $coll;
2575
    }
2576
2577
    /**
2578
     * INTERNAL:
2579
     * Schedules a complete collection for removal when this UnitOfWork commits.
2580
     *
2581
     * @param PersistentCollection $coll
2582
     */
2583 41
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2584
    {
2585 41
        $oid = spl_object_hash($coll);
2586 41
        unset($this->collectionUpdates[$oid]);
2587 41
        if ( ! isset($this->collectionDeletions[$oid])) {
2588 41
            $this->collectionDeletions[$oid] = $coll;
2589 41
            $this->scheduleCollectionOwner($coll);
2590 41
        }
2591 41
    }
2592
2593
    /**
2594
     * Checks whether a PersistentCollection is scheduled for deletion.
2595
     *
2596
     * @param PersistentCollection $coll
2597
     * @return boolean
2598
     */
2599 106
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2600
    {
2601 106
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2602
    }
2603
    
2604
    /**
2605
     * INTERNAL:
2606
     * Unschedules a collection from being deleted when this UnitOfWork commits.
2607
     * 
2608
     * @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
2609
     */
2610 205 View Code Duplication
    public function unscheduleCollectionDeletion(PersistentCollection $coll)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2611
    {
2612 205
        $oid = spl_object_hash($coll);
2613 205
        if (isset($this->collectionDeletions[$oid])) {
2614 11
            $topmostOwner = $this->getOwningDocument($coll->getOwner());
2615 11
            unset($this->collectionDeletions[$oid]);
2616 11
            unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2617 11
        }
2618 205
    }
2619
2620
    /**
2621
     * INTERNAL:
2622
     * Schedules a collection for update when this UnitOfWork commits.
2623
     *
2624
     * @param PersistentCollection $coll
2625
     */
2626 221
    public function scheduleCollectionUpdate(PersistentCollection $coll)
2627
    {
2628 221
        $mapping = $coll->getMapping();
2629 221
        if (CollectionHelper::usesSet($mapping['strategy'])) {
2630
            /* There is no need to $unset collection if it will be $set later
2631
             * This is NOP if collection is not scheduled for deletion
2632
             */
2633 40
            $this->unscheduleCollectionDeletion($coll);
2634 40
        }
2635 221
        $oid = spl_object_hash($coll);
2636 221
        if ( ! isset($this->collectionUpdates[$oid])) {
2637 221
            $this->collectionUpdates[$oid] = $coll;
2638 221
            $this->scheduleCollectionOwner($coll);
2639 221
        }
2640 221
    }
2641
    
2642
    /**
2643
     * INTERNAL:
2644
     * Unschedules a collection from being updated when this UnitOfWork commits.
2645
     * 
2646
     * @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
2647
     */
2648 205 View Code Duplication
    public function unscheduleCollectionUpdate(PersistentCollection $coll)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2649
    {
2650 205
        $oid = spl_object_hash($coll);
2651 205
        if (isset($this->collectionUpdates[$oid])) {
2652 195
            $topmostOwner = $this->getOwningDocument($coll->getOwner());
2653 195
            unset($this->collectionUpdates[$oid]);
2654 195
            unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2655 195
        }
2656 205
    }
2657
    
2658
    /**
2659
     * Checks whether a PersistentCollection is scheduled for update.
2660
     *
2661
     * @param PersistentCollection $coll
2662
     * @return boolean
2663
     */
2664 122
    public function isCollectionScheduledForUpdate(PersistentCollection $coll)
2665
    {
2666 122
        return isset($this->collectionUpdates[spl_object_hash($coll)]);
2667
    }
2668
2669
    /**
2670
     * INTERNAL:
2671
     * Gets PersistentCollections that have been visited during computing change
2672
     * set of $document
2673
     *
2674
     * @param object $document
2675
     * @return PersistentCollection[]
2676
     */
2677 535
    public function getVisitedCollections($document)
2678
    {
2679 535
        $oid = spl_object_hash($document);
2680 535
        return isset($this->visitedCollections[$oid])
2681 535
                ? $this->visitedCollections[$oid]
2682 535
                : array();
2683
    }
2684
    
2685
    /**
2686
     * INTERNAL:
2687
     * Gets PersistentCollections that are scheduled to update and related to $document
2688
     * 
2689
     * @param object $document
2690
     * @return array
2691
     */
2692 535
    public function getScheduledCollections($document)
2693
    {
2694 535
        $oid = spl_object_hash($document);
2695 535
        return isset($this->hasScheduledCollections[$oid]) 
2696 535
                ? $this->hasScheduledCollections[$oid]
2697 535
                : array();
2698
    }
2699
    
2700
    /**
2701
     * Checks whether the document is related to a PersistentCollection
2702
     * scheduled for update or deletion.
2703
     *
2704
     * @param object $document
2705
     * @return boolean
2706
     */
2707 59
    public function hasScheduledCollections($document)
2708
    {
2709 59
        return isset($this->hasScheduledCollections[spl_object_hash($document)]);
2710
    }
2711
    
2712
    /**
2713
     * Marks the PersistentCollection's top-level owner as having a relation to
2714
     * a collection scheduled for update or deletion.
2715
     *
2716
     * If the owner is not scheduled for any lifecycle action, it will be
2717
     * scheduled for update to ensure that versioning takes place if necessary.
2718
     *
2719
     * If the collection is nested within atomic collection, it is immediately
2720
     * unscheduled and atomic one is scheduled for update instead. This makes
2721
     * calculating update data way easier.
2722
     * 
2723
     * @param PersistentCollection $coll
2724
     */
2725 223
    private function scheduleCollectionOwner(PersistentCollection $coll)
2726
    {
2727 223
        $document = $this->getOwningDocument($coll->getOwner());
2728 223
        $this->hasScheduledCollections[spl_object_hash($document)][spl_object_hash($coll)] = $coll;
2729
2730 223
        if ($document !== $coll->getOwner()) {
2731 24
            $parent = $coll->getOwner();
2732 24
            while (null !== ($parentAssoc = $this->getParentAssociation($parent))) {
2733 24
                list($mapping, $parent, ) = $parentAssoc;
2734 24
            }
2735 24
            if (isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
2736 7
                $class = $this->dm->getClassMetadata(get_class($document));
2737 7
                $atomicCollection = $class->getFieldValue($document, $mapping['fieldName']);
2738 7
                $this->scheduleCollectionUpdate($atomicCollection);
2739 7
                $this->unscheduleCollectionDeletion($coll);
2740 7
                $this->unscheduleCollectionUpdate($coll);
2741 7
            }
2742 24
        }
2743
2744 223
        if ( ! $this->isDocumentScheduled($document)) {
2745 92
            $this->scheduleForUpdate($document);
2746 92
        }
2747 223
    }
2748
2749
    /**
2750
     * Get the top-most owning document of a given document
2751
     *
2752
     * If a top-level document is provided, that same document will be returned.
2753
     * For an embedded document, we will walk through parent associations until
2754
     * we find a top-level document.
2755
     *
2756
     * @param object $document
2757
     * @throws \UnexpectedValueException when a top-level document could not be found
2758
     * @return object
2759
     */
2760 225
    public function getOwningDocument($document)
2761
    {
2762 225
        $class = $this->dm->getClassMetadata(get_class($document));
2763 225
        while ($class->isEmbeddedDocument) {
2764 38
            $parentAssociation = $this->getParentAssociation($document);
2765
2766 38
            if ( ! $parentAssociation) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parentAssociation 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...
2767
                throw new \UnexpectedValueException("Could not determine parent association for " . get_class($document));
2768
            }
2769
2770 38
            list(, $document, ) = $parentAssociation;
2771 38
            $class = $this->dm->getClassMetadata(get_class($document));
2772 38
        }
2773
2774 225
        return $document;
2775
    }
2776
2777
    /**
2778
     * Gets the class name for an association (embed or reference) with respect
2779
     * to any discriminator value.
2780
     *
2781
     * @param array      $mapping Field mapping for the association
2782
     * @param array|null $data    Data for the embedded document or reference
2783
     */
2784 207
    public function getClassNameForAssociation(array $mapping, $data)
2785
    {
2786 207
        $discriminatorField = isset($mapping['discriminatorField']) ? $mapping['discriminatorField'] : null;
2787
2788 207
        $discriminatorValue = null;
2789 207
        if (isset($discriminatorField, $data[$discriminatorField])) {
2790 21
            $discriminatorValue = $data[$discriminatorField];
2791 207
        } elseif (isset($mapping['defaultDiscriminatorValue'])) {
2792
            $discriminatorValue = $mapping['defaultDiscriminatorValue'];
2793
        }
2794
2795 207
        if ($discriminatorValue !== null) {
2796 21
            return isset($mapping['discriminatorMap'][$discriminatorValue])
2797 21
                ? $mapping['discriminatorMap'][$discriminatorValue]
2798 21
                : $discriminatorValue;
2799
        }
2800
2801 187
            $class = $this->dm->getClassMetadata($mapping['targetDocument']);
2802
2803 187 View Code Duplication
        if (isset($class->discriminatorField, $data[$class->discriminatorField])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2804 15
            $discriminatorValue = $data[$class->discriminatorField];
2805 187
        } elseif ($class->defaultDiscriminatorValue !== null) {
2806 1
            $discriminatorValue = $class->defaultDiscriminatorValue;
2807 1
        }
2808
2809 187
        if ($discriminatorValue !== null) {
2810 16
            return isset($class->discriminatorMap[$discriminatorValue])
2811 16
                ? $class->discriminatorMap[$discriminatorValue]
2812 16
                : $discriminatorValue;
2813
        }
2814
2815 171
        return $mapping['targetDocument'];
2816
    }
2817
2818
    /**
2819
     * INTERNAL:
2820
     * Creates a document. Used for reconstitution of documents during hydration.
2821
     *
2822
     * @ignore
2823
     * @param string $className The name of the document class.
2824
     * @param array $data The data for the document.
2825
     * @param array $hints Any hints to account for during reconstitution/lookup of the document.
2826
     * @param object The document to be hydrated into in case of creation
2827
     * @return object The document instance.
2828
     * @internal Highly performance-sensitive method.
2829
     */
2830 380
    public function getOrCreateDocument($className, $data, &$hints = array(), $document = null)
2831
    {
2832 380
        $class = $this->dm->getClassMetadata($className);
2833
2834
        // @TODO figure out how to remove this
2835 380
        $discriminatorValue = null;
2836 380 View Code Duplication
        if (isset($class->discriminatorField, $data[$class->discriminatorField])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2837 17
            $discriminatorValue = $data[$class->discriminatorField];
2838 380
        } elseif (isset($class->defaultDiscriminatorValue)) {
2839 2
            $discriminatorValue = $class->defaultDiscriminatorValue;
2840 2
        }
2841
2842 380
        if ($discriminatorValue !== null) {
2843 18
            $className = isset($class->discriminatorMap[$discriminatorValue])
2844 18
                ? $class->discriminatorMap[$discriminatorValue]
2845 18
                : $discriminatorValue;
2846
2847 18
            $class = $this->dm->getClassMetadata($className);
2848
2849 18
            unset($data[$class->discriminatorField]);
2850 18
        }
2851
2852 380
        $id = $class->getDatabaseIdentifierValue($data['_id']);
2853 380
        $serializedId = serialize($id);
2854
2855 380
        if (isset($this->identityMap[$class->name][$serializedId])) {
2856 89
            $document = $this->identityMap[$class->name][$serializedId];
2857 89
            $oid = spl_object_hash($document);
2858 89
            if ($document instanceof Proxy && ! $document->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2859 10
                $document->__isInitialized__ = true;
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2860 10
                $overrideLocalValues = true;
2861 10
                if ($document instanceof NotifyPropertyChanged) {
2862
                    $document->addPropertyChangedListener($this);
2863
                }
2864 10
            } else {
2865 85
                $overrideLocalValues = ! empty($hints[Query::HINT_REFRESH]);
2866
            }
2867 89
            if ($overrideLocalValues) {
2868 46
                $data = $this->hydratorFactory->hydrate($document, $data, $hints);
2869 46
                $this->originalDocumentData[$oid] = $data;
2870 46
            }
2871 89
        } else {
2872 352
            if ($document === null) {
2873 352
                $document = $class->newInstance();
2874 352
            }
2875 352
            $this->registerManaged($document, $id, $data);
2876 352
            $oid = spl_object_hash($document);
2877 352
            $this->documentStates[$oid] = self::STATE_MANAGED;
2878 352
            $this->identityMap[$class->name][$serializedId] = $document;
2879 352
            $data = $this->hydratorFactory->hydrate($document, $data, $hints);
2880 352
            $this->originalDocumentData[$oid] = $data;
2881
        }
2882 380
        return $document;
2883
    }
2884
2885
    /**
2886
     * Initializes (loads) an uninitialized persistent collection of a document.
2887
     *
2888
     * @param PersistentCollection $collection The collection to initialize.
2889
     */
2890 157
    public function loadCollection(PersistentCollection $collection)
2891
    {
2892 157
        $this->getDocumentPersister(get_class($collection->getOwner()))->loadCollection($collection);
2893 157
    }
2894
2895
    /**
2896
     * Gets the identity map of the UnitOfWork.
2897
     *
2898
     * @return array
2899
     */
2900
    public function getIdentityMap()
2901
    {
2902
        return $this->identityMap;
2903
    }
2904
2905
    /**
2906
     * Gets the original data of a document. The original data is the data that was
2907
     * present at the time the document was reconstituted from the database.
2908
     *
2909
     * @param object $document
2910
     * @return array
2911
     */
2912 1
    public function getOriginalDocumentData($document)
2913
    {
2914 1
        $oid = spl_object_hash($document);
2915 1
        if (isset($this->originalDocumentData[$oid])) {
2916 1
            return $this->originalDocumentData[$oid];
2917
        }
2918
        return array();
2919
    }
2920
2921
    /**
2922
     * @ignore
2923
     */
2924 50
    public function setOriginalDocumentData($document, array $data)
2925
    {
2926 50
        $this->originalDocumentData[spl_object_hash($document)] = $data;
2927 50
    }
2928
2929
    /**
2930
     * INTERNAL:
2931
     * Sets a property value of the original data array of a document.
2932
     *
2933
     * @ignore
2934
     * @param string $oid
2935
     * @param string $property
2936
     * @param mixed $value
2937
     */
2938 3
    public function setOriginalDocumentProperty($oid, $property, $value)
2939
    {
2940 3
        $this->originalDocumentData[$oid][$property] = $value;
2941 3
    }
2942
2943
    /**
2944
     * Gets the identifier of a document.
2945
     *
2946
     * @param object $document
2947
     * @return mixed The identifier value
2948
     */
2949 354
    public function getDocumentIdentifier($document)
2950
    {
2951 354
        return isset($this->documentIdentifiers[spl_object_hash($document)]) ?
2952 354
            $this->documentIdentifiers[spl_object_hash($document)] : null;
2953
    }
2954
2955
    /**
2956
     * Checks whether the UnitOfWork has any pending insertions.
2957
     *
2958
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2959
     */
2960
    public function hasPendingInsertions()
2961
    {
2962
        return ! empty($this->documentInsertions);
2963
    }
2964
2965
    /**
2966
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2967
     * number of documents in the identity map.
2968
     *
2969
     * @return integer
2970
     */
2971 2
    public function size()
2972
    {
2973 2
        $count = 0;
2974 2
        foreach ($this->identityMap as $documentSet) {
2975 2
            $count += count($documentSet);
2976 2
        }
2977 2
        return $count;
2978
    }
2979
2980
    /**
2981
     * INTERNAL:
2982
     * Registers a document as managed.
2983
     *
2984
     * TODO: This method assumes that $id is a valid PHP identifier for the
2985
     * document class. If the class expects its database identifier to be a
2986
     * MongoId, and an incompatible $id is registered (e.g. an integer), the
2987
     * document identifiers map will become inconsistent with the identity map.
2988
     * In the future, we may want to round-trip $id through a PHP and database
2989
     * conversion and throw an exception if it's inconsistent.
2990
     *
2991
     * @param object $document The document.
2992
     * @param array $id The identifier values.
2993
     * @param array $data The original document data.
2994
     */
2995 374
    public function registerManaged($document, $id, array $data)
2996
    {
2997 374
        $oid = spl_object_hash($document);
2998 374
        $class = $this->dm->getClassMetadata(get_class($document));
2999
3000 374
        if ( ! $class->identifier || $id === null) {
3001 102
            $this->documentIdentifiers[$oid] = $oid;
3002 102
        } else {
3003 368
            $this->documentIdentifiers[$oid] = $class->getPHPIdentifierValue($id);
3004
        }
3005
3006 374
        $this->documentStates[$oid] = self::STATE_MANAGED;
3007 374
        $this->originalDocumentData[$oid] = $data;
3008 374
        $this->addToIdentityMap($document);
3009 374
    }
3010
3011
    /**
3012
     * INTERNAL:
3013
     * Clears the property changeset of the document with the given OID.
3014
     *
3015
     * @param string $oid The document's OID.
3016
     */
3017 1
    public function clearDocumentChangeSet($oid)
3018
    {
3019 1
        $this->documentChangeSets[$oid] = array();
3020 1
    }
3021
3022
    /* PropertyChangedListener implementation */
3023
3024
    /**
3025
     * Notifies this UnitOfWork of a property change in a document.
3026
     *
3027
     * @param object $document The document that owns the property.
3028
     * @param string $propertyName The name of the property that changed.
3029
     * @param mixed $oldValue The old value of the property.
3030
     * @param mixed $newValue The new value of the property.
3031
     */
3032 2
    public function propertyChanged($document, $propertyName, $oldValue, $newValue)
3033
    {
3034 2
        $oid = spl_object_hash($document);
3035 2
        $class = $this->dm->getClassMetadata(get_class($document));
3036
3037 2
        if ( ! isset($class->fieldMappings[$propertyName])) {
3038 1
            return; // ignore non-persistent fields
3039
        }
3040
3041
        // Update changeset and mark document for synchronization
3042 2
        $this->documentChangeSets[$oid][$propertyName] = array($oldValue, $newValue);
3043 2
        if ( ! isset($this->scheduledForDirtyCheck[$class->name][$oid])) {
3044 2
            $this->scheduleForDirtyCheck($document);
3045 2
        }
3046 2
    }
3047
3048
    /**
3049
     * Gets the currently scheduled document insertions in this UnitOfWork.
3050
     *
3051
     * @return array
3052
     */
3053 5
    public function getScheduledDocumentInsertions()
3054
    {
3055 5
        return $this->documentInsertions;
3056
    }
3057
3058
    /**
3059
     * Gets the currently scheduled document upserts in this UnitOfWork.
3060
     *
3061
     * @return array
3062
     */
3063 3
    public function getScheduledDocumentUpserts()
3064
    {
3065 3
        return $this->documentUpserts;
3066
    }
3067
3068
    /**
3069
     * Gets the currently scheduled document updates in this UnitOfWork.
3070
     *
3071
     * @return array
3072
     */
3073 3
    public function getScheduledDocumentUpdates()
3074
    {
3075 3
        return $this->documentUpdates;
3076
    }
3077
3078
    /**
3079
     * Gets the currently scheduled document deletions in this UnitOfWork.
3080
     *
3081
     * @return array
3082
     */
3083
    public function getScheduledDocumentDeletions()
3084
    {
3085
        return $this->documentDeletions;
3086
    }
3087
3088
    /**
3089
     * Get the currently scheduled complete collection deletions
3090
     *
3091
     * @return array
3092
     */
3093
    public function getScheduledCollectionDeletions()
3094
    {
3095
        return $this->collectionDeletions;
3096
    }
3097
3098
    /**
3099
     * Gets the currently scheduled collection inserts, updates and deletes.
3100
     *
3101
     * @return array
3102
     */
3103
    public function getScheduledCollectionUpdates()
3104
    {
3105
        return $this->collectionUpdates;
3106
    }
3107
3108
    /**
3109
     * Helper method to initialize a lazy loading proxy or persistent collection.
3110
     *
3111
     * @param object
3112
     * @return void
3113
     */
3114
    public function initializeObject($obj)
3115
    {
3116
        if ($obj instanceof Proxy) {
3117
            $obj->__load();
3118
        } elseif ($obj instanceof PersistentCollection) {
3119
            $obj->initialize();
3120
        }
3121
    }
3122
3123 1
    private static function objToStr($obj)
3124
    {
3125 1
        return method_exists($obj, '__toString') ? (string)$obj : get_class($obj) . '@' . spl_object_hash($obj);
3126
    }
3127
}
3128