Completed
Push — master ( 02d0de...7396c2 )
by Maciej
08:20
created

UnitOfWork::getVisitedCollections()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4286
c 1
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 2
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 921
    public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory)
260
    {
261 921
        $this->dm = $dm;
262 921
        $this->evm = $evm;
263 921
        $this->hydratorFactory = $hydratorFactory;
264 921
    }
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 667
    public function getPersistenceBuilder()
273
    {
274 667
        if ( ! $this->persistenceBuilder) {
275 667
            $this->persistenceBuilder = new PersistenceBuilder($this->dm, $this);
276 667
        }
277 667
        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 665
    public function getDocumentPersister($documentName)
320
    {
321 665
        if ( ! isset($this->persisters[$documentName])) {
322 651
            $class = $this->dm->getClassMetadata($documentName);
323 651
            $pb = $this->getPersistenceBuilder();
324 651
            $this->persisters[$documentName] = new Persisters\DocumentPersister($pb, $this->dm, $this->evm, $this, $this->hydratorFactory, $class);
325 651
        }
326 665
        return $this->persisters[$documentName];
327
    }
328
329
    /**
330
     * Get the collection persister instance.
331
     *
332
     * @return \Doctrine\ODM\MongoDB\Persisters\CollectionPersister
333
     */
334 665
    public function getCollectionPersister()
335
    {
336 665
        if ( ! isset($this->collectionPersister)) {
337 665
            $pb = $this->getPersistenceBuilder();
338 665
            $this->collectionPersister = new Persisters\CollectionPersister($this->dm, $pb, $this);
339 665
        }
340 665
        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 554
    public function commit($document = null, array $options = array())
369
    {
370
        // Raise preFlush
371 554
        if ($this->evm->hasListeners(Events::preFlush)) {
372
            $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->dm));
373
        }
374
375 554
        $defaultOptions = $this->dm->getConfiguration()->getDefaultCommitOptions();
376 554
        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 554
            $options = $defaultOptions;
380
        }
381
        // Compute changes done since last commit.
382 554
        if ($document === null) {
383 548
            $this->computeChangeSets();
384 553
        } 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 552
        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 235
            $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 198
            $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 188
            $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 552
        ) {
400 23
            return; // Nothing to do.
401
        }
402
403 549
        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 549
        if ($this->evm->hasListeners(Events::onFlush)) {
411 7
            $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));
412 7
        }
413
414 549
        foreach ($this->getClassesForCommitAction($this->documentUpserts) as $classAndDocuments) {
415 78
            list($class, $documents) = $classAndDocuments;
416 78
            $this->executeUpserts($class, $documents, $options);
417 549
        }
418
419 549
        foreach ($this->getClassesForCommitAction($this->documentInsertions) as $classAndDocuments) {
420 482
            list($class, $documents) = $classAndDocuments;
421 482
            $this->executeInserts($class, $documents, $options);
422 548
        }
423
424 548
        foreach ($this->getClassesForCommitAction($this->documentUpdates) as $classAndDocuments) {
425 214
            list($class, $documents) = $classAndDocuments;
426 214
            $this->executeUpdates($class, $documents, $options);
427 548
        }
428
429 548
        foreach ($this->getClassesForCommitAction($this->documentDeletions, true) as $classAndDocuments) {
430 62
            list($class, $documents) = $classAndDocuments;
431 62
            $this->executeDeletions($class, $documents, $options);
432 548
        }
433
434
        // Raise postFlush
435 548
        if ($this->evm->hasListeners(Events::postFlush)) {
436
            $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm));
437
        }
438
439
        // Clear up
440 548
        $this->documentInsertions =
441 548
        $this->documentUpserts =
442 548
        $this->documentUpdates =
443 548
        $this->documentDeletions =
444 548
        $this->documentChangeSets =
445 548
        $this->collectionUpdates =
446 548
        $this->collectionDeletions =
447 548
        $this->visitedCollections =
448 548
        $this->scheduledForDirtyCheck =
449 548
        $this->orphanRemovals = 
450 548
        $this->hasScheduledCollections = array();
451 548
    }
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 549
    private function getClassesForCommitAction($documents, $includeEmbedded = false)
461
    {
462 549
        if (empty($documents)) {
463 549
            return array();
464
        }
465 548
        $divided = array();
466 548
        $embeds = array();
467 548
        foreach ($documents as $oid => $d) {
468 548
            $className = get_class($d);
469 548
            if (isset($embeds[$className])) {
470 68
                continue;
471
            }
472 548
            if (isset($divided[$className])) {
473 135
                $divided[$className][1][$oid] = $d;
474 135
                continue;
475
            }
476 548
            $class = $this->dm->getClassMetadata($className);
477 548
            if ($class->isEmbeddedDocument && ! $includeEmbedded) {
478 165
                $embeds[$className] = true;
479 165
                continue;
480
            }
481 548
            if (empty($divided[$class->name])) {
482 548
                $divided[$class->name] = array($class, array($oid => $d));
483 548
            } else {
484 4
                $divided[$class->name][1][$oid] = $d;
485
            }
486 548
        }
487 548
        return $divided;
488
    }
489
490
    /**
491
     * Compute changesets of all documents scheduled for insertion.
492
     *
493
     * Embedded documents will not be processed.
494
     */
495 556 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 556
        foreach ($this->documentInsertions as $document) {
498 490
            $class = $this->dm->getClassMetadata(get_class($document));
499 490
            if ( ! $class->isEmbeddedDocument) {
500 487
                $this->computeChangeSet($class, $document);
501 486
            }
502 555
        }
503 555
    }
504
505
    /**
506
     * Compute changesets of all documents scheduled for upsert.
507
     *
508
     * Embedded documents will not be processed.
509
     */
510 555 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 555
        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 555
        }
518 555
    }
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 539
    public function getDocumentChangeSet($document)
573
    {
574 539
        $oid = spl_object_hash($document);
575 539
        if (isset($this->documentChangeSets[$oid])) {
576 539
            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 553
    public function getDocumentActualData($document)
588
    {
589 553
        $class = $this->dm->getClassMetadata(get_class($document));
590 553
        $actualData = array();
591 553
        foreach ($class->reflFields as $name => $refProp) {
592 553
            $mapping = $class->fieldMappings[$name];
593
            // skip not saved fields
594 553
            if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
595 49
                continue;
596
            }
597 553
            $value = $refProp->getValue($document);
598 553
            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 553
            } elseif ((isset($mapping['association']) && $mapping['type'] === 'many')
603 553
                && $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 553
                $actualData[$name] = $value;
617
            }
618 553
        }
619 553
        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 553
    public function computeChangeSet(ClassMetadata $class, $document)
647
    {
648 553
        if ( ! $class->isInheritanceTypeNone()) {
649 171
            $class = $this->dm->getClassMetadata(get_class($document));
650 171
        }
651
652
        // Fire PreFlush lifecycle callbacks
653 553 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 10
            $class->invokeLifecycleCallbacks(Events::preFlush, $document, array(new Event\PreFlushEventArgs($this->dm)));
655 10
        }
656
657 553
        $this->computeOrRecomputeChangeSet($class, $document);
658 552
    }
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 553
    private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false)
668
    {
669 553
        $oid = spl_object_hash($document);
670 553
        $actualData = $this->getDocumentActualData($document);
671 553
        $isNewDocument = ! isset($this->originalDocumentData[$oid]);
672 553
        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 553
            $this->originalDocumentData[$oid] = $actualData;
676 553
            $changeSet = array();
677 553
            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 553
                if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) {
682
                    $actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
683
                    $actualValue = $actualData[$propName];
684
                }
685 553
                $changeSet[$propName] = array(null, $actualValue);
686 553
            }
687 553
            $this->documentChangeSets[$oid] = $changeSet;
688 553
        } else {
689
            // Document is "fully" MANAGED: it was already fully persisted before
690
            // and we have a copy of the original data
691 274
            $originalData = $this->originalDocumentData[$oid];
692 274
            $isChangeTrackingNotify = $class->isChangeTrackingNotify();
693 274
            if ($isChangeTrackingNotify && ! $recompute && isset($this->documentChangeSets[$oid])) {
694 2
                $changeSet = $this->documentChangeSets[$oid];
695 2
            } else {
696 274
                $changeSet = array();
697
            }
698
699 274
            foreach ($actualData as $propName => $actualValue) {
700
                // skip not saved fields
701 274
                if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
702
                    continue;
703
                }
704
705 274
                $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
706
707
                // skip if value has not changed
708 274
                if ($orgValue === $actualValue) {
709
                    // but consider dirty GridFSFile instances as changed
710 273
                    if ( ! (isset($class->fieldMappings[$propName]['file']) && $actualValue->isDirty())) {
711 273
                        continue;
712
                    }
713 1
                }
714
715
                // if relationship is a embed-one, schedule orphan removal to trigger cascade remove operations
716 175
                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 168
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $class->fieldMappings[$propName]['isOwningSide']) {
727 10
                    if ($orgValue !== null && $class->fieldMappings[$propName]['orphanRemoval']) {
728 1
                        $this->scheduleOrphanRemoval($orgValue);
729 1
                    }
730
731 10
                    $changeSet[$propName] = array($orgValue, $actualValue);
732 10
                    continue;
733
                }
734
735 160
                if ($isChangeTrackingNotify) {
736 2
                    continue;
737
                }
738
739
                // ignore inverse side of reference-many relationship
740 159
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'many' && $class->fieldMappings[$propName]['isInverseSide']) {
741
                    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 159
                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 159
                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 145
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'date') {
768 35
                    $dateType = Type::getType('date');
769 35
                    $dbOrgValue = $dateType->convertToDatabaseValue($orgValue);
770 35
                    $dbActualValue = $dateType->convertToDatabaseValue($actualValue);
771
772 35
                    if ($dbOrgValue instanceof \MongoDate && $dbActualValue instanceof \MongoDate && $dbOrgValue == $dbActualValue) {
773 29
                        continue;
774
                    }
775 9
                }
776
777
                // regular field
778 129
                $changeSet[$propName] = array($orgValue, $actualValue);
779 274
            }
780 274
            if ($changeSet) {
781 161
                $this->documentChangeSets[$oid] = ($recompute && isset($this->documentChangeSets[$oid]))
782 161
                    ? $changeSet + $this->documentChangeSets[$oid]
783 13
                    : $changeSet;
784
785 161
                $this->originalDocumentData[$oid] = $actualData;
786 161
                $this->scheduleForUpdate($document);
787 161
            }
788
        }
789
790
        // Look for changes in associations of the document
791 553
        $associationMappings = array_filter(
792 553
            $class->associationMappings,
793
            function ($assoc) { return empty($assoc['notSaved']); }
794 553
        );
795
796 553
        foreach ($associationMappings as $mapping) {
797 427
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
798
799 427
            if ($value === null) {
800 287
                continue;
801
            }
802
803 418
            $this->computeAssociationChanges($document, $mapping, $value);
804
805 417
            if (isset($mapping['reference'])) {
806 314
                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 552
        }
825 552
    }
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 551
    public function computeChangeSets()
833
    {
834 551
        $this->computeScheduleInsertsChangeSets();
835 550
        $this->computeScheduleUpsertsChangeSets();
836
837
        // Compute changes for other MANAGED documents. Change tracking policies take effect here.
838 550
        foreach ($this->identityMap as $className => $documents) {
839 550
            $class = $this->dm->getClassMetadata($className);
840 550
            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 550
            switch (true) {
852 550
                case ($class->isChangeTrackingDeferredImplicit()):
853 549
                    $documentsToProcess = $documents;
854 549
                    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 550
            foreach ($documentsToProcess as $document) {
866
                // Ignore uninitialized proxy objects
867 546
                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 546
                $oid = spl_object_hash($document);
872 546 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 546
                    && ! isset($this->documentUpserts[$oid])
874 546
                    && ! isset($this->documentDeletions[$oid])
875 546
                    && isset($this->documentStates[$oid])
876 546
                ) {
877 259
                    $this->computeChangeSet($class, $document);
878 259
                }
879 550
            }
880 550
        }
881 550
    }
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 418
    private function computeAssociationChanges($parentDocument, array $assoc, $value)
892
    {
893 418
        $isNewParentDocument = isset($this->documentInsertions[spl_object_hash($parentDocument)]);
894 418
        $class = $this->dm->getClassMetadata(get_class($parentDocument));
895 418
        $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument);
896
897 418
        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 417
        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 417
        $unwrappedValue = ($assoc['type'] === ClassMetadata::ONE) ? array($value) : $value->unwrap();
919
920 417
        $count = 0;
921 417
        foreach ($unwrappedValue as $key => $entry) {
922 322
            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 321
            $targetClass = $this->dm->getClassMetadata(get_class($entry));
929
930 321
            $state = $this->getDocumentState($entry, self::STATE_NEW);
931
932
            // Handle "set" strategy for multi-level hierarchy
933 321
            $pathKey = ! isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key;
934 321
            $path = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name'];
935
936 321
            $count++;
937
938
            switch ($state) {
939 321
                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 317
                case self::STATE_MANAGED:
953 317
                    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 317
                    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 416
        }
992 416
    }
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 19
    public function recomputeSingleDocumentChangeSet(ClassMetadata $class, $document)
1009
    {
1010
        // Ignore uninitialized proxy objects
1011 19
        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 18
        $oid = spl_object_hash($document);
1016
1017 18
        if ( ! isset($this->documentStates[$oid]) || $this->documentStates[$oid] != self::STATE_MANAGED) {
1018
            throw new \InvalidArgumentException('Document must be managed.');
1019
        }
1020
1021 18
        if ( ! $class->isInheritanceTypeNone()) {
1022 2
            $class = $this->dm->getClassMetadata(get_class($document));
1023 2
        }
1024
1025 18
        $this->computeOrRecomputeChangeSet($class, $document, true);
1026 18
    }
1027
1028
    /**
1029
     * @param ClassMetadata $class
1030
     * @param object $document
1031
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1032
     */
1033 571
    private function persistNew(ClassMetadata $class, $document)
1034
    {
1035 571
        $oid = spl_object_hash($document);
1036 571 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 155
            $class->invokeLifecycleCallbacks(Events::prePersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1038 155
        }
1039 571 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 571
        $upsert = false;
1044 571
        if ($class->identifier) {
1045 571
            $idValue = $class->getIdentifierValue($document);
1046 571
            $upsert = ! $class->isEmbeddedDocument && $idValue !== null;
1047
1048 571
            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 570
            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 569
            if ($class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1063 498
                $idValue = $class->idGenerator->generate($this->dm, $document);
1064 498
                $idValue = $class->getPHPIdentifierValue($class->getDatabaseIdentifierValue($idValue));
1065 498
                $class->setIdentifierValue($document, $idValue);
1066 498
            }
1067
1068 569
            $this->documentIdentifiers[$oid] = $idValue;
1069 569
        } else {
1070
            // this is for embedded documents without identifiers
1071 142
            $this->documentIdentifiers[$oid] = $oid;
1072
        }
1073
1074 569
        $this->documentStates[$oid] = self::STATE_MANAGED;
1075
1076 569
        if ($upsert) {
1077 81
            $this->scheduleForUpsert($class, $document);
1078 81
        } else {
1079 503
            $this->scheduleForInsert($class, $document);
1080
        }
1081 569
    }
1082
1083
    /**
1084
     * Cascades the postPersist events to embedded documents.
1085
     *
1086
     * @param ClassMetadata $class
1087
     * @param object $document
1088
     */
1089 547
    private function cascadePostPersist(ClassMetadata $class, $document)
1090
    {
1091 547
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1092
1093 547
        $embeddedMappings = array_filter(
1094 547
            $class->associationMappings,
1095
            function($assoc) { return ! empty($assoc['embedded']); }
1096 547
        );
1097
1098 547
        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 547
         }
1125 547
     }
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 482 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 482
        $persister = $this->getDocumentPersister($class->name);
1137
1138 482
        foreach ($documents as $oid => $document) {
1139 482
            $persister->addInsert($document);
1140 482
            unset($this->documentInsertions[$oid]);
1141 482
        }
1142
1143 482
        $persister->executeInserts($options);
1144
1145 481
        $hasPostPersistLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postPersist]);
1146 481
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1147
1148 481
        foreach ($documents as $document) {
1149 481
            if ($hasPostPersistLifecycleCallbacks) {
1150 9
                $class->invokeLifecycleCallbacks(Events::postPersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1151 9
            }
1152 481
            if ($hasPostPersistListeners) {
1153 5
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1154 5
            }
1155 481
            $this->cascadePostPersist($class, $document);
1156 481
        }
1157 481
    }
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 214
    private function executeUpdates(ClassMetadata $class, array $documents, array $options = array())
1200
    {
1201 214
        $className = $class->name;
1202 214
        $persister = $this->getDocumentPersister($className);
1203
1204 214
        $hasPreUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::preUpdate]);
1205 214
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1206 214
        $hasPostUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postUpdate]);
1207 214
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1208
1209 214
        foreach ($documents as $oid => $document) {
1210 214
            if ( ! isset($this->documentChangeSets[$oid])) {
1211
                // only ReferenceMany collection is scheduled for update
1212 59
                $this->documentChangeSets[$oid] = array();
1213 59
            }
1214 214 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 11
                $class->invokeLifecycleCallbacks(Events::preUpdate, $document, array(
1216 11
                    new Event\PreUpdateEventArgs($document, $this->dm, $this->documentChangeSets[$oid])
1217 11
                ));
1218 11
                $this->recomputeSingleDocumentChangeSet($class, $document);
1219 11
            }
1220
1221 214 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 214
            $this->cascadePreUpdate($class, $document);
1227
1228 214
            if ( ! empty($this->documentChangeSets[$oid]) || $this->hasScheduledCollections($document)) {
1229 213
                $persister->update($document, $options);
1230 209
            }
1231
1232 210
            unset($this->documentUpdates[$oid]);
1233
1234 210
            if ($hasPostUpdateLifecycleCallbacks) {
1235 6
                $class->invokeLifecycleCallbacks(Events::postUpdate, $document, array(new LifecycleEventArgs($document, $this->dm)));
1236 6
            }
1237 210
            if ($hasPostUpdateListeners) {
1238 8
                $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($document, $this->dm));
1239 8
            }
1240 210
            $this->cascadePostUpdate($class, $document);
1241 210
        }
1242 209
    }
1243
1244
    /**
1245
     * Cascades the preUpdate event to embedded documents.
1246
     *
1247
     * @param ClassMetadata $class
1248
     * @param object $document
1249
     */
1250 214
    private function cascadePreUpdate(ClassMetadata $class, $document)
1251
    {
1252 214
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1253
1254 214
        $embeddedMappings = array_filter(
1255 214
            $class->associationMappings,
1256
            function ($assoc) { return ! empty($assoc['embedded']); }
1257 214
        );
1258
1259 214
        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 214
        }
1295 214
    }
1296
1297
    /**
1298
     * Cascades the postUpdate and postPersist events to embedded documents.
1299
     *
1300
     * @param ClassMetadata $class
1301
     * @param object $document
1302
     */
1303 210
    private function cascadePostUpdate(ClassMetadata $class, $document)
1304
    {
1305 210
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1306 210
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1307
1308 210
        $embeddedMappings = array_filter(
1309 210
            $class->associationMappings,
1310
            function($assoc) { return ! empty($assoc['embedded']); }
1311 210
        );
1312
1313 210
        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 210
        }
1353 210
    }
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 506
    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 506
        $oid = spl_object_hash($document);
1415
1416 506
        if (isset($this->documentUpdates[$oid])) {
1417
            throw new \InvalidArgumentException("Dirty document can not be scheduled for insertion.");
1418
        }
1419 506
        if (isset($this->documentDeletions[$oid])) {
1420
            throw new \InvalidArgumentException("Removed document can not be scheduled for insertion.");
1421
        }
1422 506
        if (isset($this->documentInsertions[$oid])) {
1423
            throw new \InvalidArgumentException("Document can not be scheduled for insertion twice.");
1424
        }
1425
1426 506
        $this->documentInsertions[$oid] = $document;
1427
1428 506
        if (isset($this->documentIdentifiers[$oid])) {
1429 503
            $this->addToIdentityMap($document);
1430 503
        }
1431 506
    }
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 223
    public function scheduleForUpdate($document)
1492
    {
1493 223
        $oid = spl_object_hash($document);
1494 223
        if ( ! isset($this->documentIdentifiers[$oid])) {
1495
            throw new \InvalidArgumentException("Document has no identity.");
1496
        }
1497
1498 223
        if (isset($this->documentDeletions[$oid])) {
1499
            throw new \InvalidArgumentException("Document is removed.");
1500
        }
1501
1502 223
        if ( ! isset($this->documentUpdates[$oid])
1503 223
            && ! isset($this->documentInsertions[$oid])
1504 223
            && ! isset($this->documentUpserts[$oid])) {
1505 219
            $this->documentUpdates[$oid] = $document;
1506 219
        }
1507 223
    }
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 598
    public function addToIdentityMap($document)
1602
    {
1603 598
        $class = $this->dm->getClassMetadata(get_class($document));
1604 598
        $id = $this->getIdForIdentityMap($document);
1605
1606 598
        if (isset($this->identityMap[$class->name][$id])) {
1607 53
            return false;
1608
        }
1609
1610 598
        $this->identityMap[$class->name][$id] = $document;
1611
1612 598
        if ($document instanceof NotifyPropertyChanged &&
1613 598
            ( ! $document instanceof Proxy || $document->__isInitialized())) {
1614 3
            $document->addPropertyChangedListener($this);
1615 3
        }
1616
1617 598
        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 574
    public function getDocumentState($document, $assume = null)
1631
    {
1632 574
        $oid = spl_object_hash($document);
1633
1634 574
        if (isset($this->documentStates[$oid])) {
1635 351
            return $this->documentStates[$oid];
1636
        }
1637
1638 574
        $class = $this->dm->getClassMetadata(get_class($document));
1639
1640 574
        if ($class->isEmbeddedDocument) {
1641 175
            return self::STATE_NEW;
1642
        }
1643
1644 571
        if ($assume !== null) {
1645 568
            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 598
    private function getIdForIdentityMap($document)
1793
    {
1794 598
        $class = $this->dm->getClassMetadata(get_class($document));
1795
1796 598
        if ( ! $class->identifier) {
1797 145
            $id = spl_object_hash($document);
1798 145
        } else {
1799 597
            $id = $this->documentIdentifiers[spl_object_hash($document)];
1800 597
            $id = serialize($class->getDatabaseIdentifierValue($id));
1801
        }
1802
1803 598
        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 569
    public function persist($document)
1828
    {
1829 569
        $class = $this->dm->getClassMetadata(get_class($document));
1830 569
        if ($class->isMappedSuperclass) {
1831 1
            throw MongoDBException::cannotPersistMappedSuperclass($class->name);
1832
        }
1833 568
        $visited = array();
1834 568
        $this->doPersist($document, $visited);
1835 564
    }
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 568
    private function doPersist($document, array &$visited)
1851
    {
1852 568
        $oid = spl_object_hash($document);
1853 568
        if (isset($visited[$oid])) {
1854 24
            return; // Prevent infinite recursion
1855
        }
1856
1857 568
        $visited[$oid] = $document; // Mark visited
1858
1859 568
        $class = $this->dm->getClassMetadata(get_class($document));
1860
1861 568
        $documentState = $this->getDocumentState($document, self::STATE_NEW);
1862
        switch ($documentState) {
1863 568
            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 568
            case self::STATE_NEW:
1870 568
                $this->persistNew($class, $document);
1871 566
                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 566
        $this->cascadePersist($document, $visited);
1890 564
    }
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 566
    private function cascadePersist($document, array &$visited)
2350
    {
2351 566
        $class = $this->dm->getClassMetadata(get_class($document));
2352
2353 566
        $associationMappings = array_filter(
2354 566
            $class->associationMappings,
2355
            function ($assoc) { return $assoc['isCascadePersist']; }
2356 566
        );
2357
2358 566
        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 565
        }
2395 564
    }
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 534
    public function getVisitedCollections($document)
2678
    {
2679 534
        $oid = spl_object_hash($document);
2680 534
        return isset($this->visitedCollections[$oid])
2681 534
                ? $this->visitedCollections[$oid]
2682 534
                : 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 534
    public function getScheduledCollections($document)
2693
    {
2694 534
        $oid = spl_object_hash($document);
2695 534
        return isset($this->hasScheduledCollections[$oid]) 
2696 534
                ? $this->hasScheduledCollections[$oid]
2697 534
                : 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 353
    public function getDocumentIdentifier($document)
2950
    {
2951 353
        return isset($this->documentIdentifiers[spl_object_hash($document)]) ?
2952 353
            $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