Completed
Pull Request — master (#1219)
by Maciej
10:06
created

UnitOfWork::setOriginalDocumentData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4286
cc 1
eloc 4
nc 1
nop 2
crap 1
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\DefaultPersistentCollectionFactory;
32
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionFactory;
33
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface;
34
use Doctrine\ODM\MongoDB\Persisters\PersistenceBuilder;
35
use Doctrine\ODM\MongoDB\Proxy\Proxy;
36
use Doctrine\ODM\MongoDB\Query\Query;
37
use Doctrine\ODM\MongoDB\Types\Type;
38
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
39
40
/**
41
 * The UnitOfWork is responsible for tracking changes to objects during an
42
 * "object-level" transaction and for writing out changes to the database
43
 * in the correct order.
44
 *
45
 * @since       1.0
46
 * @author      Jonathan H. Wage <[email protected]>
47
 * @author      Roman Borschel <[email protected]>
48
 */
49
class UnitOfWork implements PropertyChangedListener
50
{
51
    /**
52
     * A document is in MANAGED state when its persistence is managed by a DocumentManager.
53
     */
54
    const STATE_MANAGED = 1;
55
56
    /**
57
     * A document is new if it has just been instantiated (i.e. using the "new" operator)
58
     * and is not (yet) managed by a DocumentManager.
59
     */
60
    const STATE_NEW = 2;
61
62
    /**
63
     * A detached document is an instance with a persistent identity that is not
64
     * (or no longer) associated with a DocumentManager (and a UnitOfWork).
65
     */
66
    const STATE_DETACHED = 3;
67
68
    /**
69
     * A removed document instance is an instance with a persistent identity,
70
     * associated with a DocumentManager, whose persistent state has been
71
     * deleted (or is scheduled for deletion).
72
     */
73
    const STATE_REMOVED = 4;
74
75
    /**
76
     * The identity map holds references to all managed documents.
77
     *
78
     * Documents are grouped by their class name, and then indexed by the
79
     * serialized string of their database identifier field or, if the class
80
     * has no identifier, the SPL object hash. Serializing the identifier allows
81
     * differentiation of values that may be equal (via type juggling) but not
82
     * identical.
83
     *
84
     * Since all classes in a hierarchy must share the same identifier set,
85
     * we always take the root class name of the hierarchy.
86
     *
87
     * @var array
88
     */
89
    private $identityMap = array();
90
91
    /**
92
     * Map of all identifiers of managed documents.
93
     * Keys are object ids (spl_object_hash).
94
     *
95
     * @var array
96
     */
97
    private $documentIdentifiers = array();
98
99
    /**
100
     * Map of the original document data of managed documents.
101
     * Keys are object ids (spl_object_hash). This is used for calculating changesets
102
     * at commit time.
103
     *
104
     * @var array
105
     * @internal Note that PHPs "copy-on-write" behavior helps a lot with memory usage.
106
     *           A value will only really be copied if the value in the document is modified
107
     *           by the user.
108
     */
109
    private $originalDocumentData = array();
110
111
    /**
112
     * Map of document changes. Keys are object ids (spl_object_hash).
113
     * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
114
     *
115
     * @var array
116
     */
117
    private $documentChangeSets = array();
118
119
    /**
120
     * The (cached) states of any known documents.
121
     * Keys are object ids (spl_object_hash).
122
     *
123
     * @var array
124
     */
125
    private $documentStates = array();
126
127
    /**
128
     * Map of documents that are scheduled for dirty checking at commit time.
129
     *
130
     * Documents are grouped by their class name, and then indexed by their SPL
131
     * object hash. This is only used for documents with a change tracking
132
     * policy of DEFERRED_EXPLICIT.
133
     *
134
     * @var array
135
     * @todo rename: scheduledForSynchronization
136
     */
137
    private $scheduledForDirtyCheck = array();
138
139
    /**
140
     * A list of all pending document insertions.
141
     *
142
     * @var array
143
     */
144
    private $documentInsertions = array();
145
146
    /**
147
     * A list of all pending document updates.
148
     *
149
     * @var array
150
     */
151
    private $documentUpdates = array();
152
153
    /**
154
     * A list of all pending document upserts.
155
     *
156
     * @var array
157
     */
158
    private $documentUpserts = array();
159
160
    /**
161
     * A list of all pending document deletions.
162
     *
163
     * @var array
164
     */
165
    private $documentDeletions = array();
166
167
    /**
168
     * All pending collection deletions.
169
     *
170
     * @var array
171
     */
172
    private $collectionDeletions = array();
173
174
    /**
175
     * All pending collection updates.
176
     *
177
     * @var array
178
     */
179
    private $collectionUpdates = array();
180
    
181
    /**
182
     * A list of documents related to collections scheduled for update or deletion
183
     * 
184
     * @var array
185
     */
186
    private $hasScheduledCollections = array();
187
188
    /**
189
     * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
190
     * At the end of the UnitOfWork all these collections will make new snapshots
191
     * of their data.
192
     *
193
     * @var array
194
     */
195
    private $visitedCollections = array();
196
197
    /**
198
     * The DocumentManager that "owns" this UnitOfWork instance.
199
     *
200
     * @var DocumentManager
201
     */
202
    private $dm;
203
204
    /**
205
     * The EventManager used for dispatching events.
206
     *
207
     * @var EventManager
208
     */
209
    private $evm;
210
211
    /**
212
     * Additional documents that are scheduled for removal.
213
     *
214
     * @var array
215
     */
216
    private $orphanRemovals = array();
217
218
    /**
219
     * The HydratorFactory used for hydrating array Mongo documents to Doctrine object documents.
220
     *
221
     * @var HydratorFactory
222
     */
223
    private $hydratorFactory;
224
225
    /**
226
     * The document persister instances used to persist document instances.
227
     *
228
     * @var array
229
     */
230
    private $persisters = array();
231
232
    /**
233
     * The collection persister instance used to persist changes to collections.
234
     *
235
     * @var Persisters\CollectionPersister
236
     */
237
    private $collectionPersister;
238
239
    /**
240
     * The persistence builder instance used in DocumentPersisters.
241
     *
242
     * @var PersistenceBuilder
243
     */
244
    private $persistenceBuilder;
245
246
    /**
247
     * Array of parent associations between embedded documents
248
     *
249
     * @todo We might need to clean up this array in clear(), doDetach(), etc.
250
     * @var array
251
     */
252
    private $parentAssociations = array();
253
254
    /**
255
     * Factory class for persistent collections.
256
     *
257
     * @var PersistentCollectionFactory
258
     */
259
    private $persistentCollectionFactory;
260
261
    /**
262
     * Initializes a new UnitOfWork instance, bound to the given DocumentManager.
263
     *
264
     * @param DocumentManager $dm
265
     * @param EventManager $evm
266
     * @param HydratorFactory $hydratorFactory
267
     */
268 929
    public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory)
269
    {
270 929
        $this->dm = $dm;
271 929
        $this->evm = $evm;
272 929
        $this->hydratorFactory = $hydratorFactory;
273 929
        $config = $dm->getConfiguration() ?: new Configuration();
274 929
        $this->persistentCollectionFactory = new DefaultPersistentCollectionFactory(
275 929
                $dm,
276 929
                $this,
277 929
                $config->getPersistentCollectionDir(),
278 929
                $config->getPersistentCollectionNamespace(),
279 929
                $config->getAutoGeneratePersistentCollectionClasses()
280 929
        );
281 929
    }
282
283
    /**
284
     * Factory for returning new PersistenceBuilder instances used for preparing data into
285
     * queries for insert persistence.
286
     *
287
     * @return PersistenceBuilder $pb
288
     */
289 672
    public function getPersistenceBuilder()
290
    {
291 672
        if ( ! $this->persistenceBuilder) {
292 672
            $this->persistenceBuilder = new PersistenceBuilder($this->dm, $this);
293 672
        }
294 672
        return $this->persistenceBuilder;
295
    }
296
297
    /**
298
     * Factory for returning specialized PersistentCollection instances.
299
     *
300
     * @return PersistentCollectionFactory
301
     */
302 251
    public function getPersistentCollectionFactory()
303
    {
304 251
        return $this->persistentCollectionFactory;
305
    }
306
307
    /**
308
     * Sets the parent association for a given embedded document.
309
     *
310
     * @param object $document
311
     * @param array $mapping
312
     * @param object $parent
313
     * @param string $propertyPath
314
     */
315 182
    public function setParentAssociation($document, $mapping, $parent, $propertyPath)
316
    {
317 182
        $oid = spl_object_hash($document);
318 182
        $this->parentAssociations[$oid] = array($mapping, $parent, $propertyPath);
319 182
    }
320
321
    /**
322
     * Gets the parent association for a given embedded document.
323
     *
324
     *     <code>
325
     *     list($mapping, $parent, $propertyPath) = $this->getParentAssociation($embeddedDocument);
326
     *     </code>
327
     *
328
     * @param object $document
329
     * @return array $association
330
     */
331 209
    public function getParentAssociation($document)
332
    {
333 209
        $oid = spl_object_hash($document);
334 209
        if ( ! isset($this->parentAssociations[$oid])) {
335 205
            return null;
336
        }
337 167
        return $this->parentAssociations[$oid];
338
    }
339
340
    /**
341
     * Get the document persister instance for the given document name
342
     *
343
     * @param string $documentName
344
     * @return Persisters\DocumentPersister
345
     */
346 670
    public function getDocumentPersister($documentName)
347
    {
348 670
        if ( ! isset($this->persisters[$documentName])) {
349 656
            $class = $this->dm->getClassMetadata($documentName);
350 656
            $pb = $this->getPersistenceBuilder();
351 656
            $this->persisters[$documentName] = new Persisters\DocumentPersister($pb, $this->dm, $this->evm, $this, $this->hydratorFactory, $class);
352 656
        }
353 670
        return $this->persisters[$documentName];
354
    }
355
356
    /**
357
     * Get the collection persister instance.
358
     *
359
     * @return \Doctrine\ODM\MongoDB\Persisters\CollectionPersister
360
     */
361 670
    public function getCollectionPersister()
362
    {
363 670
        if ( ! isset($this->collectionPersister)) {
364 670
            $pb = $this->getPersistenceBuilder();
365 670
            $this->collectionPersister = new Persisters\CollectionPersister($this->dm, $pb, $this);
366 670
        }
367 670
        return $this->collectionPersister;
368
    }
369
370
    /**
371
     * Set the document persister instance to use for the given document name
372
     *
373
     * @param string $documentName
374
     * @param Persisters\DocumentPersister $persister
375
     */
376 14
    public function setDocumentPersister($documentName, Persisters\DocumentPersister $persister)
377
    {
378 14
        $this->persisters[$documentName] = $persister;
379 14
    }
380
381
    /**
382
     * Commits the UnitOfWork, executing all operations that have been postponed
383
     * up to this point. The state of all managed documents will be synchronized with
384
     * the database.
385
     *
386
     * The operations are executed in the following order:
387
     *
388
     * 1) All document insertions
389
     * 2) All document updates
390
     * 3) All document deletions
391
     *
392
     * @param object $document
393
     * @param array $options Array of options to be used with batchInsert(), update() and remove()
394
     */
395 559
    public function commit($document = null, array $options = array())
396
    {
397
        // Raise preFlush
398 559
        if ($this->evm->hasListeners(Events::preFlush)) {
399
            $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->dm));
400
        }
401
402 559
        $defaultOptions = $this->dm->getConfiguration()->getDefaultCommitOptions();
403 559
        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...
404
            $options = array_merge($defaultOptions, $options);
405
        } else {
406 559
            $options = $defaultOptions;
407
        }
408
        // Compute changes done since last commit.
409 559
        if ($document === null) {
410 553
            $this->computeChangeSets();
411 558
        } elseif (is_object($document)) {
412 12
            $this->computeSingleDocumentChangeSet($document);
413 12
        } elseif (is_array($document)) {
414 1
            foreach ($document as $object) {
415 1
                $this->computeSingleDocumentChangeSet($object);
416 1
            }
417 1
        }
418
419 557
        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...
420 237
            $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...
421 200
            $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...
422 190
            $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...
423 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...
424 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...
425 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...
426 557
        ) {
427 23
            return; // Nothing to do.
428
        }
429
430 554
        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...
431 45
            foreach ($this->orphanRemovals as $removal) {
432 45
                $this->remove($removal);
433 46
            }
434 45
        }
435
436
        // Raise onFlush
437 554
        if ($this->evm->hasListeners(Events::onFlush)) {
438 7
            $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));
439 7
        }
440
441 554
        foreach ($this->getClassesForCommitAction($this->documentUpserts) as $classAndDocuments) {
442 78
            list($class, $documents) = $classAndDocuments;
443 78
            $this->executeUpserts($class, $documents, $options);
444 554
        }
445
446 554
        foreach ($this->getClassesForCommitAction($this->documentInsertions) as $classAndDocuments) {
447 487
            list($class, $documents) = $classAndDocuments;
448 487
            $this->executeInserts($class, $documents, $options);
449 553
        }
450
451 553
        foreach ($this->getClassesForCommitAction($this->documentUpdates) as $classAndDocuments) {
452 216
            list($class, $documents) = $classAndDocuments;
453 216
            $this->executeUpdates($class, $documents, $options);
454 553
        }
455
456 553
        foreach ($this->getClassesForCommitAction($this->documentDeletions, true) as $classAndDocuments) {
457 62
            list($class, $documents) = $classAndDocuments;
458 62
            $this->executeDeletions($class, $documents, $options);
459 553
        }
460
461
        // Raise postFlush
462 553
        if ($this->evm->hasListeners(Events::postFlush)) {
463
            $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm));
464
        }
465
466
        // Clear up
467 553
        $this->documentInsertions =
468 553
        $this->documentUpserts =
469 553
        $this->documentUpdates =
470 553
        $this->documentDeletions =
471 553
        $this->documentChangeSets =
472 553
        $this->collectionUpdates =
473 553
        $this->collectionDeletions =
474 553
        $this->visitedCollections =
475 553
        $this->scheduledForDirtyCheck =
476 553
        $this->orphanRemovals = 
477 553
        $this->hasScheduledCollections = array();
478 553
    }
479
480
    /**
481
     * Groups a list of scheduled documents by their class.
482
     *
483
     * @param array $documents Scheduled documents (e.g. $this->documentInsertions)
484
     * @param bool $includeEmbedded
485
     * @return array Tuples of ClassMetadata and a corresponding array of objects
486
     */
487 554
    private function getClassesForCommitAction($documents, $includeEmbedded = false)
488
    {
489 554
        if (empty($documents)) {
490 554
            return array();
491
        }
492 553
        $divided = array();
493 553
        $embeds = array();
494 553
        foreach ($documents as $oid => $d) {
495 553
            $className = get_class($d);
496 553
            if (isset($embeds[$className])) {
497 69
                continue;
498
            }
499 553
            if (isset($divided[$className])) {
500 136
                $divided[$className][1][$oid] = $d;
501 136
                continue;
502
            }
503 553
            $class = $this->dm->getClassMetadata($className);
504 553
            if ($class->isEmbeddedDocument && ! $includeEmbedded) {
505 167
                $embeds[$className] = true;
506 167
                continue;
507
            }
508 553
            if (empty($divided[$class->name])) {
509 553
                $divided[$class->name] = array($class, array($oid => $d));
510 553
            } else {
511 4
                $divided[$class->name][1][$oid] = $d;
512
            }
513 553
        }
514 553
        return $divided;
515
    }
516
517
    /**
518
     * Compute changesets of all documents scheduled for insertion.
519
     *
520
     * Embedded documents will not be processed.
521
     */
522 561 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...
523
    {
524 561
        foreach ($this->documentInsertions as $document) {
525 495
            $class = $this->dm->getClassMetadata(get_class($document));
526 495
            if ( ! $class->isEmbeddedDocument) {
527 492
                $this->computeChangeSet($class, $document);
528 491
            }
529 560
        }
530 560
    }
531
532
    /**
533
     * Compute changesets of all documents scheduled for upsert.
534
     *
535
     * Embedded documents will not be processed.
536
     */
537 560 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...
538
    {
539 560
        foreach ($this->documentUpserts as $document) {
540 77
            $class = $this->dm->getClassMetadata(get_class($document));
541 77
            if ( ! $class->isEmbeddedDocument) {
542 77
                $this->computeChangeSet($class, $document);
543 77
            }
544 560
        }
545 560
    }
546
547
    /**
548
     * Only flush the given document according to a ruleset that keeps the UoW consistent.
549
     *
550
     * 1. All documents scheduled for insertion and (orphan) removals are processed as well!
551
     * 2. Proxies are skipped.
552
     * 3. Only if document is properly managed.
553
     *
554
     * @param  object $document
555
     * @throws \InvalidArgumentException If the document is not STATE_MANAGED
556
     * @return void
557
     */
558 13
    private function computeSingleDocumentChangeSet($document)
559
    {
560 13
        $state = $this->getDocumentState($document);
561
562 13
        if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) {
563 1
            throw new \InvalidArgumentException("Document has to be managed or scheduled for removal for single computation " . $this->objToStr($document));
564
        }
565
566 12
        $class = $this->dm->getClassMetadata(get_class($document));
567
568 12
        if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) {
569 9
            $this->persist($document);
570 9
        }
571
572
        // Compute changes for INSERTed and UPSERTed documents first. This must always happen even in this case.
573 12
        $this->computeScheduleInsertsChangeSets();
574 12
        $this->computeScheduleUpsertsChangeSets();
575
576
        // Ignore uninitialized proxy objects
577 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...
578
            return;
579
        }
580
581
        // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here.
582 12
        $oid = spl_object_hash($document);
583
584 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...
585 12
            && ! isset($this->documentUpserts[$oid])
586 12
            && ! isset($this->documentDeletions[$oid])
587 12
            && isset($this->documentStates[$oid])
588 12
        ) {
589 8
            $this->computeChangeSet($class, $document);
590 8
        }
591 12
    }
592
593
    /**
594
     * Gets the changeset for a document.
595
     *
596
     * @param object $document
597
     * @return array array('property' => array(0 => mixed|null, 1 => mixed|null))
598
     */
599 544
    public function getDocumentChangeSet($document)
600
    {
601 544
        $oid = spl_object_hash($document);
602 544
        if (isset($this->documentChangeSets[$oid])) {
603 544
            return $this->documentChangeSets[$oid];
604
        }
605 27
        return array();
606
    }
607
608
    /**
609
     * Get a documents actual data, flattening all the objects to arrays.
610
     *
611
     * @param object $document
612
     * @return array
613
     */
614 558
    public function getDocumentActualData($document)
615
    {
616 558
        $class = $this->dm->getClassMetadata(get_class($document));
617 558
        $actualData = array();
618 558
        foreach ($class->reflFields as $name => $refProp) {
619 558
            $mapping = $class->fieldMappings[$name];
620
            // skip not saved fields
621 558
            if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
622 49
                continue;
623
            }
624 558
            $value = $refProp->getValue($document);
625 558
            if (isset($mapping['file']) && ! $value instanceof GridFSFile) {
626 5
                $value = new GridFSFile($value);
627 5
                $class->reflFields[$name]->setValue($document, $value);
628 5
                $actualData[$name] = $value;
629 558
            } elseif ((isset($mapping['association']) && $mapping['type'] === 'many')
630 558
                && $value !== null && ! ($value instanceof PersistentCollectionInterface)) {
631
                // If $actualData[$name] is not a Collection then use an ArrayCollection.
632 368
                if ( ! $value instanceof Collection) {
633 119
                    $value = new ArrayCollection($value);
634 119
                }
635
636
                // Inject PersistentCollection
637 368
                $coll = $this->persistentCollectionFactory->create($mapping, $value);
638 368
                $coll->setOwner($document, $mapping);
639 368
                $coll->setDirty( ! $value->isEmpty());
640 368
                $class->reflFields[$name]->setValue($document, $coll);
641 368
                $actualData[$name] = $coll;
642 368
            } else {
643 558
                $actualData[$name] = $value;
644
            }
645 558
        }
646 558
        return $actualData;
647
    }
648
649
    /**
650
     * Computes the changes that happened to a single document.
651
     *
652
     * Modifies/populates the following properties:
653
     *
654
     * {@link originalDocumentData}
655
     * If the document is NEW or MANAGED but not yet fully persisted (only has an id)
656
     * then it was not fetched from the database and therefore we have no original
657
     * document data yet. All of the current document data is stored as the original document data.
658
     *
659
     * {@link documentChangeSets}
660
     * The changes detected on all properties of the document are stored there.
661
     * A change is a tuple array where the first entry is the old value and the second
662
     * entry is the new value of the property. Changesets are used by persisters
663
     * to INSERT/UPDATE the persistent document state.
664
     *
665
     * {@link documentUpdates}
666
     * If the document is already fully MANAGED (has been fetched from the database before)
667
     * and any changes to its properties are detected, then a reference to the document is stored
668
     * there to mark it for an update.
669
     *
670
     * @param ClassMetadata $class The class descriptor of the document.
671
     * @param object $document The document for which to compute the changes.
672
     */
673 558
    public function computeChangeSet(ClassMetadata $class, $document)
674
    {
675 558
        if ( ! $class->isInheritanceTypeNone()) {
676 172
            $class = $this->dm->getClassMetadata(get_class($document));
677 172
        }
678
679
        // Fire PreFlush lifecycle callbacks
680 558 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...
681 10
            $class->invokeLifecycleCallbacks(Events::preFlush, $document, array(new Event\PreFlushEventArgs($this->dm)));
682 10
        }
683
684 558
        $this->computeOrRecomputeChangeSet($class, $document);
685 557
    }
686
687
    /**
688
     * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet
689
     *
690
     * @param \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class
691
     * @param object $document
692
     * @param boolean $recompute
693
     */
694 558
    private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false)
695
    {
696 558
        $oid = spl_object_hash($document);
697 558
        $actualData = $this->getDocumentActualData($document);
698 558
        $isNewDocument = ! isset($this->originalDocumentData[$oid]);
699 558
        if ($isNewDocument) {
700
            // Document is either NEW or MANAGED but not yet fully persisted (only has an id).
701
            // These result in an INSERT.
702 558
            $this->originalDocumentData[$oid] = $actualData;
703 558
            $changeSet = array();
704 558
            foreach ($actualData as $propName => $actualValue) {
705
                /* At this PersistentCollection shouldn't be here, probably it
706
                 * was cloned and its ownership must be fixed
707
                 */
708 558
                if ($actualValue instanceof PersistentCollectionInterface && $actualValue->getOwner() !== $document) {
709
                    $actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
710
                    $actualValue = $actualData[$propName];
711
                }
712 558
                $changeSet[$propName] = array(null, $actualValue);
713 558
            }
714 558
            $this->documentChangeSets[$oid] = $changeSet;
715 558
        } else {
716
            // Document is "fully" MANAGED: it was already fully persisted before
717
            // and we have a copy of the original data
718 276
            $originalData = $this->originalDocumentData[$oid];
719 276
            $isChangeTrackingNotify = $class->isChangeTrackingNotify();
720 276
            if ($isChangeTrackingNotify && ! $recompute && isset($this->documentChangeSets[$oid])) {
721 2
                $changeSet = $this->documentChangeSets[$oid];
722 2
            } else {
723 276
                $changeSet = array();
724
            }
725
726 276
            foreach ($actualData as $propName => $actualValue) {
727
                // skip not saved fields
728 276
                if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
729
                    continue;
730
                }
731
732 276
                $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
733
734
                // skip if value has not changed
735 276
                if ($orgValue === $actualValue) {
736
                    // but consider dirty GridFSFile instances as changed
737 275
                    if ( ! (isset($class->fieldMappings[$propName]['file']) && $actualValue->isDirty())) {
738 275
                        continue;
739
                    }
740 1
                }
741
742
                // if relationship is a embed-one, schedule orphan removal to trigger cascade remove operations
743 177
                if (isset($class->fieldMappings[$propName]['embedded']) && $class->fieldMappings[$propName]['type'] === 'one') {
744 10
                    if ($orgValue !== null) {
745 5
                        $this->scheduleOrphanRemoval($orgValue);
746 5
                    }
747
748 10
                    $changeSet[$propName] = array($orgValue, $actualValue);
749 10
                    continue;
750
                }
751
752
                // if owning side of reference-one relationship
753 170
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $class->fieldMappings[$propName]['isOwningSide']) {
754 10
                    if ($orgValue !== null && $class->fieldMappings[$propName]['orphanRemoval']) {
755 1
                        $this->scheduleOrphanRemoval($orgValue);
756 1
                    }
757
758 10
                    $changeSet[$propName] = array($orgValue, $actualValue);
759 10
                    continue;
760
                }
761
762 162
                if ($isChangeTrackingNotify) {
763 2
                    continue;
764
                }
765
766
                // ignore inverse side of reference-many relationship
767 161
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'many' && $class->fieldMappings[$propName]['isInverseSide']) {
768
                    continue;
769
                }
770
771
                // Persistent collection was exchanged with the "originally"
772
                // created one. This can only mean it was cloned and replaced
773
                // on another document.
774 161
                if ($actualValue instanceof PersistentCollectionInterface && $actualValue->getOwner() !== $document) {
775 6
                    $actualValue = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
776 6
                }
777
778
                // if embed-many or reference-many relationship
779 161
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'many') {
780 25
                    $changeSet[$propName] = array($orgValue, $actualValue);
781
                    /* If original collection was exchanged with a non-empty value
782
                     * and $set will be issued, there is no need to $unset it first
783
                     */
784 25
                    if ($actualValue && $actualValue->isDirty() && CollectionHelper::usesSet($class->fieldMappings[$propName]['strategy'])) {
785 7
                        continue;
786
                    }
787 19
                    if ($orgValue instanceof PersistentCollectionInterface) {
788 17
                        $this->scheduleCollectionDeletion($orgValue);
789 17
                    }
790 19
                    continue;
791
                }
792
793
                // skip equivalent date values
794 147
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'date') {
795 35
                    $dateType = Type::getType('date');
796 35
                    $dbOrgValue = $dateType->convertToDatabaseValue($orgValue);
797 35
                    $dbActualValue = $dateType->convertToDatabaseValue($actualValue);
798
799 35
                    if ($dbOrgValue instanceof \MongoDate && $dbActualValue instanceof \MongoDate && $dbOrgValue == $dbActualValue) {
800 29
                        continue;
801
                    }
802 9
                }
803
804
                // regular field
805 131
                $changeSet[$propName] = array($orgValue, $actualValue);
806 276
            }
807 276
            if ($changeSet) {
808 163
                $this->documentChangeSets[$oid] = (isset($this->documentChangeSets[$oid]))
809 163
                    ? $changeSet + $this->documentChangeSets[$oid]
810 16
                    : $changeSet;
811
812 163
                $this->originalDocumentData[$oid] = $actualData;
813 163
                $this->scheduleForUpdate($document);
814 163
            }
815
        }
816
817
        // Look for changes in associations of the document
818 558
        $associationMappings = array_filter(
819 558
            $class->associationMappings,
820
            function ($assoc) { return empty($assoc['notSaved']); }
821 558
        );
822
823 558
        foreach ($associationMappings as $mapping) {
824 430
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
825
826 430
            if ($value === null) {
827 288
                continue;
828
            }
829
830 421
            $this->computeAssociationChanges($document, $mapping, $value);
831
832 420
            if (isset($mapping['reference'])) {
833 317
                continue;
834
            }
835
836 329
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value->unwrap();
837
838 329
            foreach ($values as $obj) {
839 171
                $oid2 = spl_object_hash($obj);
840
841 171
                if (isset($this->documentChangeSets[$oid2])) {
842 169
                    $this->documentChangeSets[$oid][$mapping['fieldName']] = array($value, $value);
843
844 169
                    if ( ! $isNewDocument) {
845 71
                        $this->scheduleForUpdate($document);
846 71
                    }
847
848 169
                    break;
849
                }
850 329
            }
851 557
        }
852 557
    }
853
854
    /**
855
     * Computes all the changes that have been done to documents and collections
856
     * since the last commit and stores these changes in the _documentChangeSet map
857
     * temporarily for access by the persisters, until the UoW commit is finished.
858
     */
859 556
    public function computeChangeSets()
860
    {
861 556
        $this->computeScheduleInsertsChangeSets();
862 555
        $this->computeScheduleUpsertsChangeSets();
863
864
        // Compute changes for other MANAGED documents. Change tracking policies take effect here.
865 555
        foreach ($this->identityMap as $className => $documents) {
866 555
            $class = $this->dm->getClassMetadata($className);
867 555
            if ($class->isEmbeddedDocument) {
868
                /* we do not want to compute changes to embedded documents up front
869
                 * in case embedded document was replaced and its changeset
870
                 * would corrupt data. Embedded documents' change set will
871
                 * be calculated by reachability from owning document.
872
                 */
873 160
                continue;
874
            }
875
876
            // If change tracking is explicit or happens through notification, then only compute
877
            // changes on document of that type that are explicitly marked for synchronization.
878 555
            switch (true) {
879 555
                case ($class->isChangeTrackingDeferredImplicit()):
880 554
                    $documentsToProcess = $documents;
881 554
                    break;
882
883 3
                case (isset($this->scheduledForDirtyCheck[$className])):
884 2
                    $documentsToProcess = $this->scheduledForDirtyCheck[$className];
885 2
                    break;
886
887 3
                default:
888 3
                    $documentsToProcess = array();
889
890 3
            }
891
892 555
            foreach ($documentsToProcess as $document) {
893
                // Ignore uninitialized proxy objects
894 551
                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...
895 10
                    continue;
896
                }
897
                // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here.
898 551
                $oid = spl_object_hash($document);
899 551 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...
900 551
                    && ! isset($this->documentUpserts[$oid])
901 551
                    && ! isset($this->documentDeletions[$oid])
902 551
                    && isset($this->documentStates[$oid])
903 551
                ) {
904 261
                    $this->computeChangeSet($class, $document);
905 261
                }
906 555
            }
907 555
        }
908 555
    }
909
910
    /**
911
     * Computes the changes of an association.
912
     *
913
     * @param object $parentDocument
914
     * @param array $assoc
915
     * @param mixed $value The value of the association.
916
     * @throws \InvalidArgumentException
917
     */
918 421
    private function computeAssociationChanges($parentDocument, array $assoc, $value)
919
    {
920 421
        $isNewParentDocument = isset($this->documentInsertions[spl_object_hash($parentDocument)]);
921 421
        $class = $this->dm->getClassMetadata(get_class($parentDocument));
922 421
        $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument);
923
924 421
        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...
925 8
            return;
926
        }
927
928 420
        if ($value instanceof PersistentCollectionInterface && $value->isDirty() && ($assoc['isOwningSide'] || isset($assoc['embedded']))) {
929 228
            if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) {
930 224
                $this->scheduleCollectionUpdate($value);
931 224
            }
932 228
            $topmostOwner = $this->getOwningDocument($value->getOwner());
933 228
            $this->visitedCollections[spl_object_hash($topmostOwner)][] = $value;
934 228
            if ( ! empty($assoc['orphanRemoval']) || isset($assoc['embedded'])) {
935 134
                $value->initialize();
936 134
                foreach ($value->getDeletedDocuments() as $orphan) {
937 21
                    $this->scheduleOrphanRemoval($orphan);
938 134
                }
939 134
            }
940 228
        }
941
942
        // Look through the documents, and in any of their associations,
943
        // for transient (new) documents, recursively. ("Persistence by reachability")
944
        // Unwrap. Uninitialized collections will simply be empty.
945 420
        $unwrappedValue = ($assoc['type'] === ClassMetadata::ONE) ? array($value) : $value->unwrap();
946
947 420
        $count = 0;
948 420
        foreach ($unwrappedValue as $key => $entry) {
949 325
            if ( ! is_object($entry)) {
950 1
                throw new \InvalidArgumentException(
951 1
                        sprintf('Expected object, found "%s" in %s::%s', $entry, get_class($parentDocument), $assoc['name'])
952 1
                );
953
            }
954
955 324
            $targetClass = $this->dm->getClassMetadata(get_class($entry));
956
957 324
            $state = $this->getDocumentState($entry, self::STATE_NEW);
958
959
            // Handle "set" strategy for multi-level hierarchy
960 324
            $pathKey = ! isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key;
961 324
            $path = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name'];
962
963 324
            $count++;
964
965
            switch ($state) {
966 324
                case self::STATE_NEW:
967 56
                    if ( ! $assoc['isCascadePersist']) {
968
                        throw new \InvalidArgumentException("A new document was found through a relationship that was not"
969
                            . " configured to cascade persist operations: " . $this->objToStr($entry) . "."
970
                            . " Explicitly persist the new document or configure cascading persist operations"
971
                            . " on the relationship.");
972
                    }
973
974 56
                    $this->persistNew($targetClass, $entry);
975 56
                    $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
976 56
                    $this->computeChangeSet($targetClass, $entry);
977 56
                    break;
978
979 320
                case self::STATE_MANAGED:
980 320
                    if ($targetClass->isEmbeddedDocument) {
981 163
                        list(, $knownParent, ) = $this->getParentAssociation($entry);
982 163
                        if ($knownParent && $knownParent !== $parentDocument) {
983 6
                            $entry = clone $entry;
984 6
                            if ($assoc['type'] === ClassMetadata::ONE) {
985 3
                                $class->setFieldValue($parentDocument, $assoc['name'], $entry);
986 3
                                $this->setOriginalDocumentProperty(spl_object_hash($parentDocument), $assoc['name'], $entry);
987 3
                            } else {
988
                                // must use unwrapped value to not trigger orphan removal
989 6
                                $unwrappedValue[$key] = $entry;
990
                            }
991 6
                            $this->persistNew($targetClass, $entry);
992 6
                        }
993 163
                        $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
994 163
                        $this->computeChangeSet($targetClass, $entry);
995 163
                    }
996 320
                    break;
997
998 1
                case self::STATE_REMOVED:
999
                    // Consume the $value as array (it's either an array or an ArrayAccess)
1000
                    // and remove the element from Collection.
1001 1
                    if ($assoc['type'] === ClassMetadata::MANY) {
1002
                        unset($value[$key]);
1003
                    }
1004 1
                    break;
1005
1006
                case self::STATE_DETACHED:
1007
                    // Can actually not happen right now as we assume STATE_NEW,
1008
                    // so the exception will be raised from the DBAL layer (constraint violation).
1009
                    throw new \InvalidArgumentException("A detached document was found through a "
1010
                        . "relationship during cascading a persist operation.");
1011
1012
                default:
1013
                    // MANAGED associated documents are already taken into account
1014
                    // during changeset calculation anyway, since they are in the identity map.
1015
1016
            }
1017 419
        }
1018 419
    }
1019
1020
    /**
1021
     * INTERNAL:
1022
     * Computes the changeset of an individual document, independently of the
1023
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
1024
     *
1025
     * The passed document must be a managed document. If the document already has a change set
1026
     * because this method is invoked during a commit cycle then the change sets are added.
1027
     * whereby changes detected in this method prevail.
1028
     *
1029
     * @ignore
1030
     * @param ClassMetadata $class The class descriptor of the document.
1031
     * @param object $document The document for which to (re)calculate the change set.
1032
     * @throws \InvalidArgumentException If the passed document is not MANAGED.
1033
     */
1034 19
    public function recomputeSingleDocumentChangeSet(ClassMetadata $class, $document)
1035
    {
1036
        // Ignore uninitialized proxy objects
1037 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...
1038 1
            return;
1039
        }
1040
1041 18
        $oid = spl_object_hash($document);
1042
1043 18
        if ( ! isset($this->documentStates[$oid]) || $this->documentStates[$oid] != self::STATE_MANAGED) {
1044
            throw new \InvalidArgumentException('Document must be managed.');
1045
        }
1046
1047 18
        if ( ! $class->isInheritanceTypeNone()) {
1048 2
            $class = $this->dm->getClassMetadata(get_class($document));
1049 2
        }
1050
1051 18
        $this->computeOrRecomputeChangeSet($class, $document, true);
1052 18
    }
1053
1054
    /**
1055
     * @param ClassMetadata $class
1056
     * @param object $document
1057
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1058
     */
1059 576
    private function persistNew(ClassMetadata $class, $document)
1060
    {
1061 576
        $oid = spl_object_hash($document);
1062 576 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...
1063 156
            $class->invokeLifecycleCallbacks(Events::prePersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1064 156
        }
1065 576 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...
1066 6
            $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($document, $this->dm));
1067 6
        }
1068
1069 576
        $upsert = false;
1070 576
        if ($class->identifier) {
1071 576
            $idValue = $class->getIdentifierValue($document);
1072 576
            $upsert = ! $class->isEmbeddedDocument && $idValue !== null;
1073
1074 576
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1075 3
                throw new \InvalidArgumentException(sprintf(
1076 3
                    "%s uses NONE identifier generation strategy but no identifier was provided when persisting.",
1077 3
                    get_class($document)
1078 3
                ));
1079
            }
1080
1081 575
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_AUTO && $idValue !== null && ! \MongoId::isValid($idValue)) {
1082 1
                throw new \InvalidArgumentException(sprintf(
1083 1
                    "%s uses AUTO identifier generation strategy but provided identifier is not valid MongoId.",
1084 1
                    get_class($document)
1085 1
                ));
1086
            }
1087
1088 574
            if ($class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1089 503
                $idValue = $class->idGenerator->generate($this->dm, $document);
1090 503
                $idValue = $class->getPHPIdentifierValue($class->getDatabaseIdentifierValue($idValue));
1091 503
                $class->setIdentifierValue($document, $idValue);
1092 503
            }
1093
1094 574
            $this->documentIdentifiers[$oid] = $idValue;
1095 574
        } else {
1096
            // this is for embedded documents without identifiers
1097 144
            $this->documentIdentifiers[$oid] = $oid;
1098
        }
1099
1100 574
        $this->documentStates[$oid] = self::STATE_MANAGED;
1101
1102 574
        if ($upsert) {
1103 81
            $this->scheduleForUpsert($class, $document);
1104 81
        } else {
1105 508
            $this->scheduleForInsert($class, $document);
1106
        }
1107 574
    }
1108
1109
    /**
1110
     * Cascades the postPersist events to embedded documents.
1111
     *
1112
     * @param ClassMetadata $class
1113
     * @param object $document
1114
     */
1115 552
    private function cascadePostPersist(ClassMetadata $class, $document)
1116
    {
1117 552
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1118
1119 552
        $embeddedMappings = array_filter(
1120 552
            $class->associationMappings,
1121
            function($assoc) { return ! empty($assoc['embedded']); }
1122 552
        );
1123
1124 552
        foreach ($embeddedMappings as $mapping) {
1125 337
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1126
1127 337
            if ($value === null) {
1128 214
                continue;
1129
            }
1130
1131 319
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1132
1133 319
            if (isset($mapping['targetDocument'])) {
1134 308
                $embeddedClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1135 308
            }
1136
1137 319
            foreach ($values as $embeddedDocument) {
1138 161
                if ( ! isset($mapping['targetDocument'])) {
1139 13
                    $embeddedClass = $this->dm->getClassMetadata(get_class($embeddedDocument));
1140 13
                }
1141
1142 161 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...
1143 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...
1144 9
                }
1145 161
                if ($hasPostPersistListeners) {
1146 4
                    $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($embeddedDocument, $this->dm));
1147 4
                }
1148 161
                $this->cascadePostPersist($embeddedClass, $embeddedDocument);
1149 319
            }
1150 552
         }
1151 552
     }
1152
1153
    /**
1154
     * Executes all document insertions for documents of the specified type.
1155
     *
1156
     * @param ClassMetadata $class
1157
     * @param array $documents Array of documents to insert
1158
     * @param array $options Array of options to be used with batchInsert()
1159
     */
1160 487 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...
1161
    {
1162 487
        $persister = $this->getDocumentPersister($class->name);
1163
1164 487
        foreach ($documents as $oid => $document) {
1165 487
            $persister->addInsert($document);
1166 487
            unset($this->documentInsertions[$oid]);
1167 487
        }
1168
1169 487
        $persister->executeInserts($options);
1170
1171 486
        $hasPostPersistLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postPersist]);
1172 486
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1173
1174 486
        foreach ($documents as $document) {
1175 486
            if ($hasPostPersistLifecycleCallbacks) {
1176 9
                $class->invokeLifecycleCallbacks(Events::postPersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1177 9
            }
1178 486
            if ($hasPostPersistListeners) {
1179 5
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1180 5
            }
1181 486
            $this->cascadePostPersist($class, $document);
1182 486
        }
1183 486
    }
1184
1185
    /**
1186
     * Executes all document upserts for documents of the specified type.
1187
     *
1188
     * @param ClassMetadata $class
1189
     * @param array $documents Array of documents to upsert
1190
     * @param array $options Array of options to be used with batchInsert()
1191
     */
1192 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...
1193
    {
1194 78
        $persister = $this->getDocumentPersister($class->name);
1195
1196
1197 78
        foreach ($documents as $oid => $document) {
1198 78
            $persister->addUpsert($document);
1199 78
            unset($this->documentUpserts[$oid]);
1200 78
        }
1201
1202 78
        $persister->executeUpserts($options);
1203
1204 78
        $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]);
1205 78
        $hasListeners = $this->evm->hasListeners(Events::postPersist);
1206
1207 78
        foreach ($documents as $document) {
1208 78
            if ($hasLifecycleCallbacks) {
1209
                $class->invokeLifecycleCallbacks(Events::postPersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1210
            }
1211 78
            if ($hasListeners) {
1212 2
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1213 2
            }
1214 78
            $this->cascadePostPersist($class, $document);
1215 78
        }
1216 78
    }
1217
1218
    /**
1219
     * Executes all document updates for documents of the specified type.
1220
     *
1221
     * @param Mapping\ClassMetadata $class
1222
     * @param array $documents Array of documents to update
1223
     * @param array $options Array of options to be used with update()
1224
     */
1225 216
    private function executeUpdates(ClassMetadata $class, array $documents, array $options = array())
1226
    {
1227 216
        $className = $class->name;
1228 216
        $persister = $this->getDocumentPersister($className);
1229
1230 216
        $hasPreUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::preUpdate]);
1231 216
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1232 216
        $hasPostUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postUpdate]);
1233 216
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1234
1235 216
        foreach ($documents as $oid => $document) {
1236 216
            if ( ! isset($this->documentChangeSets[$oid])) {
1237
                // only ReferenceMany collection is scheduled for update
1238 60
                $this->documentChangeSets[$oid] = array();
1239 60
            }
1240 216 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...
1241 11
                $class->invokeLifecycleCallbacks(Events::preUpdate, $document, array(
1242 11
                    new Event\PreUpdateEventArgs($document, $this->dm, $this->documentChangeSets[$oid])
1243 11
                ));
1244 11
                $this->recomputeSingleDocumentChangeSet($class, $document);
1245 11
            }
1246
1247 216 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...
1248 8
                $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1249 8
                    $document, $this->dm, $this->documentChangeSets[$oid])
1250 8
                );
1251 8
            }
1252 216
            $this->cascadePreUpdate($class, $document);
1253
1254 216
            if ( ! empty($this->documentChangeSets[$oid]) || $this->hasScheduledCollections($document)) {
1255 214
                $persister->update($document, $options);
1256 210
            }
1257
1258 212
            unset($this->documentUpdates[$oid]);
1259
1260 212
            if ($hasPostUpdateLifecycleCallbacks) {
1261 6
                $class->invokeLifecycleCallbacks(Events::postUpdate, $document, array(new LifecycleEventArgs($document, $this->dm)));
1262 6
            }
1263 212
            if ($hasPostUpdateListeners) {
1264 8
                $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($document, $this->dm));
1265 8
            }
1266 212
            $this->cascadePostUpdate($class, $document);
1267 212
        }
1268 211
    }
1269
1270
    /**
1271
     * Cascades the preUpdate event to embedded documents.
1272
     *
1273
     * @param ClassMetadata $class
1274
     * @param object $document
1275
     */
1276 216
    private function cascadePreUpdate(ClassMetadata $class, $document)
1277
    {
1278 216
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1279
1280 216
        $embeddedMappings = array_filter(
1281 216
            $class->associationMappings,
1282
            function ($assoc) { return ! empty($assoc['embedded']); }
1283 216
        );
1284
1285 216
        foreach ($embeddedMappings as $mapping) {
1286 133
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1287
1288 133
            if ($value === null) {
1289 49
                continue;
1290
            }
1291
1292 131
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1293
1294 131
            foreach ($values as $entry) {
1295 84
                $entryOid = spl_object_hash($entry);
1296 84
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1297
1298 84
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1299 47
                    continue;
1300
                }
1301
1302 67
                if (isset($this->documentInsertions[$entryOid])) {
1303 52
                    continue;
1304
                }
1305
1306 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...
1307 5
                    $entryClass->invokeLifecycleCallbacks(Events::preUpdate, $entry, array(
1308 5
                        new Event\PreUpdateEventArgs($entry, $this->dm, $this->documentChangeSets[$entryOid])
1309 5
                    ));
1310 5
                    $this->recomputeSingleDocumentChangeSet($entryClass, $entry);
1311 5
                }
1312 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...
1313 3
                    $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1314 3
                        $entry, $this->dm, $this->documentChangeSets[$entryOid])
1315 3
                    );
1316 3
                }
1317
1318 45
                $this->cascadePreUpdate($entryClass, $entry);
1319 131
            }
1320 216
        }
1321 216
    }
1322
1323
    /**
1324
     * Cascades the postUpdate and postPersist events to embedded documents.
1325
     *
1326
     * @param ClassMetadata $class
1327
     * @param object $document
1328
     */
1329 212
    private function cascadePostUpdate(ClassMetadata $class, $document)
1330
    {
1331 212
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1332 212
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1333
1334 212
        $embeddedMappings = array_filter(
1335 212
            $class->associationMappings,
1336
            function($assoc) { return ! empty($assoc['embedded']); }
1337 212
        );
1338
1339 212
        foreach ($embeddedMappings as $mapping) {
1340 129
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1341
1342 129
            if ($value === null) {
1343 52
                continue;
1344
            }
1345
1346 127
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1347
1348 127
            foreach ($values as $entry) {
1349 84
                $entryOid = spl_object_hash($entry);
1350 84
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1351
1352 84
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1353 47
                    continue;
1354
                }
1355
1356 67
                if (isset($this->documentInsertions[$entryOid])) {
1357 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...
1358 1
                        $entryClass->invokeLifecycleCallbacks(Events::postPersist, $entry, array(
1359 1
                            new LifecycleEventArgs($entry, $this->dm)
1360 1
                        ));
1361 1
                    }
1362 52
                    if ($hasPostPersistListeners) {
1363 3
                        $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entry, $this->dm));
1364 3
                    }
1365 52
                } else {
1366 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...
1367 9
                        $entryClass->invokeLifecycleCallbacks(Events::postUpdate, $entry, array(
1368 9
                            new LifecycleEventArgs($entry, $this->dm)
1369 9
                        ));
1370 9
                    }
1371 45
                    if ($hasPostUpdateListeners) {
1372 3
                        $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entry, $this->dm));
1373 3
                    }
1374
                }
1375
1376 67
                $this->cascadePostUpdate($entryClass, $entry);
1377 127
            }
1378 212
        }
1379 212
    }
1380
1381
    /**
1382
     * Executes all document deletions for documents of the specified type.
1383
     *
1384
     * @param ClassMetadata $class
1385
     * @param array $documents Array of documents to delete
1386
     * @param array $options Array of options to be used with remove()
1387
     */
1388 62
    private function executeDeletions(ClassMetadata $class, array $documents, array $options = array())
1389
    {
1390 62
        $hasPostRemoveLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postRemove]);
1391 62
        $hasPostRemoveListeners = $this->evm->hasListeners(Events::postRemove);
1392
1393 62
        $persister = $this->getDocumentPersister($class->name);
1394
1395 62
        foreach ($documents as $oid => $document) {
1396 62
            if ( ! $class->isEmbeddedDocument) {
1397 28
                $persister->delete($document, $options);
1398 26
            }
1399
            unset(
1400 60
                $this->documentDeletions[$oid],
1401 60
                $this->documentIdentifiers[$oid],
1402 60
                $this->originalDocumentData[$oid]
1403
            );
1404
1405
            // Clear snapshot information for any referenced PersistentCollection
1406
            // http://www.doctrine-project.org/jira/browse/MODM-95
1407 60
            foreach ($class->associationMappings as $fieldMapping) {
1408 41
                if (isset($fieldMapping['type']) && $fieldMapping['type'] === ClassMetadata::MANY) {
1409 26
                    $value = $class->reflFields[$fieldMapping['fieldName']]->getValue($document);
1410 26
                    if ($value instanceof PersistentCollectionInterface) {
1411 22
                        $value->clearSnapshot();
1412 22
                    }
1413 26
                }
1414 60
            }
1415
1416
            // Document with this $oid after deletion treated as NEW, even if the $oid
1417
            // is obtained by a new document because the old one went out of scope.
1418 60
            $this->documentStates[$oid] = self::STATE_NEW;
1419
1420 60
            if ($hasPostRemoveLifecycleCallbacks) {
1421 8
                $class->invokeLifecycleCallbacks(Events::postRemove, $document, array(new LifecycleEventArgs($document, $this->dm)));
1422 8
            }
1423 60
            if ($hasPostRemoveListeners) {
1424 2
                $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($document, $this->dm));
1425 2
            }
1426 60
        }
1427 60
    }
1428
1429
    /**
1430
     * Schedules a document for insertion into the database.
1431
     * If the document already has an identifier, it will be added to the
1432
     * identity map.
1433
     *
1434
     * @param ClassMetadata $class
1435
     * @param object $document The document to schedule for insertion.
1436
     * @throws \InvalidArgumentException
1437
     */
1438 511
    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...
1439
    {
1440 511
        $oid = spl_object_hash($document);
1441
1442 511
        if (isset($this->documentUpdates[$oid])) {
1443
            throw new \InvalidArgumentException("Dirty document can not be scheduled for insertion.");
1444
        }
1445 511
        if (isset($this->documentDeletions[$oid])) {
1446
            throw new \InvalidArgumentException("Removed document can not be scheduled for insertion.");
1447
        }
1448 511
        if (isset($this->documentInsertions[$oid])) {
1449
            throw new \InvalidArgumentException("Document can not be scheduled for insertion twice.");
1450
        }
1451
1452 511
        $this->documentInsertions[$oid] = $document;
1453
1454 511
        if (isset($this->documentIdentifiers[$oid])) {
1455 508
            $this->addToIdentityMap($document);
1456 508
        }
1457 511
    }
1458
1459
    /**
1460
     * Schedules a document for upsert into the database and adds it to the
1461
     * identity map
1462
     *
1463
     * @param ClassMetadata $class
1464
     * @param object $document The document to schedule for upsert.
1465
     * @throws \InvalidArgumentException
1466
     */
1467 84
    public function scheduleForUpsert(ClassMetadata $class, $document)
1468
    {
1469 84
        $oid = spl_object_hash($document);
1470
1471 84
        if ($class->isEmbeddedDocument) {
1472
            throw new \InvalidArgumentException("Embedded document can not be scheduled for upsert.");
1473
        }
1474 84
        if (isset($this->documentUpdates[$oid])) {
1475
            throw new \InvalidArgumentException("Dirty document can not be scheduled for upsert.");
1476
        }
1477 84
        if (isset($this->documentDeletions[$oid])) {
1478
            throw new \InvalidArgumentException("Removed document can not be scheduled for upsert.");
1479
        }
1480 84
        if (isset($this->documentUpserts[$oid])) {
1481
            throw new \InvalidArgumentException("Document can not be scheduled for upsert twice.");
1482
        }
1483
1484 84
        $this->documentUpserts[$oid] = $document;
1485 84
        $this->documentIdentifiers[$oid] = $class->getIdentifierValue($document);
1486 84
        $this->addToIdentityMap($document);
1487 84
    }
1488
1489
    /**
1490
     * Checks whether a document is scheduled for insertion.
1491
     *
1492
     * @param object $document
1493
     * @return boolean
1494
     */
1495 70
    public function isScheduledForInsert($document)
1496
    {
1497 70
        return isset($this->documentInsertions[spl_object_hash($document)]);
1498
    }
1499
1500
    /**
1501
     * Checks whether a document is scheduled for upsert.
1502
     *
1503
     * @param object $document
1504
     * @return boolean
1505
     */
1506 5
    public function isScheduledForUpsert($document)
1507
    {
1508 5
        return isset($this->documentUpserts[spl_object_hash($document)]);
1509
    }
1510
1511
    /**
1512
     * Schedules a document for being updated.
1513
     *
1514
     * @param object $document The document to schedule for being updated.
1515
     * @throws \InvalidArgumentException
1516
     */
1517 225
    public function scheduleForUpdate($document)
1518
    {
1519 225
        $oid = spl_object_hash($document);
1520 225
        if ( ! isset($this->documentIdentifiers[$oid])) {
1521
            throw new \InvalidArgumentException("Document has no identity.");
1522
        }
1523
1524 225
        if (isset($this->documentDeletions[$oid])) {
1525
            throw new \InvalidArgumentException("Document is removed.");
1526
        }
1527
1528 225
        if ( ! isset($this->documentUpdates[$oid])
1529 225
            && ! isset($this->documentInsertions[$oid])
1530 225
            && ! isset($this->documentUpserts[$oid])) {
1531 221
            $this->documentUpdates[$oid] = $document;
1532 221
        }
1533 225
    }
1534
1535
    /**
1536
     * Checks whether a document is registered as dirty in the unit of work.
1537
     * Note: Is not very useful currently as dirty documents are only registered
1538
     * at commit time.
1539
     *
1540
     * @param object $document
1541
     * @return boolean
1542
     */
1543 13
    public function isScheduledForUpdate($document)
1544
    {
1545 13
        return isset($this->documentUpdates[spl_object_hash($document)]);
1546
    }
1547
1548 1
    public function isScheduledForDirtyCheck($document)
1549
    {
1550 1
        $class = $this->dm->getClassMetadata(get_class($document));
1551 1
        return isset($this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)]);
1552
    }
1553
1554
    /**
1555
     * INTERNAL:
1556
     * Schedules a document for deletion.
1557
     *
1558
     * @param object $document
1559
     */
1560 67
    public function scheduleForDelete($document)
1561
    {
1562 67
        $oid = spl_object_hash($document);
1563
1564 67
        if (isset($this->documentInsertions[$oid])) {
1565 2
            if ($this->isInIdentityMap($document)) {
1566 2
                $this->removeFromIdentityMap($document);
1567 2
            }
1568 2
            unset($this->documentInsertions[$oid]);
1569 2
            return; // document has not been persisted yet, so nothing more to do.
1570
        }
1571
1572 66
        if ( ! $this->isInIdentityMap($document)) {
1573 1
            return; // ignore
1574
        }
1575
1576 65
        $this->removeFromIdentityMap($document);
1577 65
        $this->documentStates[$oid] = self::STATE_REMOVED;
1578
1579 65
        if (isset($this->documentUpdates[$oid])) {
1580
            unset($this->documentUpdates[$oid]);
1581
        }
1582 65
        if ( ! isset($this->documentDeletions[$oid])) {
1583 65
            $this->documentDeletions[$oid] = $document;
1584 65
        }
1585 65
    }
1586
1587
    /**
1588
     * Checks whether a document is registered as removed/deleted with the unit
1589
     * of work.
1590
     *
1591
     * @param object $document
1592
     * @return boolean
1593
     */
1594 8
    public function isScheduledForDelete($document)
1595
    {
1596 8
        return isset($this->documentDeletions[spl_object_hash($document)]);
1597
    }
1598
1599
    /**
1600
     * Checks whether a document is scheduled for insertion, update or deletion.
1601
     *
1602
     * @param $document
1603
     * @return boolean
1604
     */
1605 227
    public function isDocumentScheduled($document)
1606
    {
1607 227
        $oid = spl_object_hash($document);
1608 227
        return isset($this->documentInsertions[$oid]) ||
1609 121
            isset($this->documentUpserts[$oid]) ||
1610 112
            isset($this->documentUpdates[$oid]) ||
1611 227
            isset($this->documentDeletions[$oid]);
1612
    }
1613
1614
    /**
1615
     * INTERNAL:
1616
     * Registers a document in the identity map.
1617
     *
1618
     * Note that documents in a hierarchy are registered with the class name of
1619
     * the root document. Identifiers are serialized before being used as array
1620
     * keys to allow differentiation of equal, but not identical, values.
1621
     *
1622
     * @ignore
1623
     * @param object $document  The document to register.
1624
     * @return boolean  TRUE if the registration was successful, FALSE if the identity of
1625
     *                  the document in question is already managed.
1626
     */
1627 603
    public function addToIdentityMap($document)
1628
    {
1629 603
        $class = $this->dm->getClassMetadata(get_class($document));
1630 603
        $id = $this->getIdForIdentityMap($document);
1631
1632 603
        if (isset($this->identityMap[$class->name][$id])) {
1633 54
            return false;
1634
        }
1635
1636 603
        $this->identityMap[$class->name][$id] = $document;
1637
1638 603
        if ($document instanceof NotifyPropertyChanged &&
1639 603
            ( ! $document instanceof Proxy || $document->__isInitialized())) {
1640 3
            $document->addPropertyChangedListener($this);
1641 3
        }
1642
1643 603
        return true;
1644
    }
1645
1646
    /**
1647
     * Gets the state of a document with regard to the current unit of work.
1648
     *
1649
     * @param object   $document
1650
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1651
     *                         This parameter can be set to improve performance of document state detection
1652
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1653
     *                         is either known or does not matter for the caller of the method.
1654
     * @return int The document state.
1655
     */
1656 579
    public function getDocumentState($document, $assume = null)
1657
    {
1658 579
        $oid = spl_object_hash($document);
1659
1660 579
        if (isset($this->documentStates[$oid])) {
1661 355
            return $this->documentStates[$oid];
1662
        }
1663
1664 579
        $class = $this->dm->getClassMetadata(get_class($document));
1665
1666 579
        if ($class->isEmbeddedDocument) {
1667 177
            return self::STATE_NEW;
1668
        }
1669
1670 576
        if ($assume !== null) {
1671 573
            return $assume;
1672
        }
1673
1674
        /* State can only be NEW or DETACHED, because MANAGED/REMOVED states are
1675
         * known. Note that you cannot remember the NEW or DETACHED state in
1676
         * _documentStates since the UoW does not hold references to such
1677
         * objects and the object hash can be reused. More generally, because
1678
         * the state may "change" between NEW/DETACHED without the UoW being
1679
         * aware of it.
1680
         */
1681 4
        $id = $class->getIdentifierObject($document);
1682
1683 4
        if ($id === null) {
1684 2
            return self::STATE_NEW;
1685
        }
1686
1687
        // Check for a version field, if available, to avoid a DB lookup.
1688 2
        if ($class->isVersioned) {
1689
            return ($class->getFieldValue($document, $class->versionField))
1690
                ? self::STATE_DETACHED
1691
                : self::STATE_NEW;
1692
        }
1693
1694
        // Last try before DB lookup: check the identity map.
1695 2
        if ($this->tryGetById($id, $class)) {
1696 1
            return self::STATE_DETACHED;
1697
        }
1698
1699
        // DB lookup
1700 2
        if ($this->getDocumentPersister($class->name)->exists($document)) {
1701 1
            return self::STATE_DETACHED;
1702
        }
1703
1704 1
        return self::STATE_NEW;
1705
    }
1706
1707
    /**
1708
     * INTERNAL:
1709
     * Removes a document from the identity map. This effectively detaches the
1710
     * document from the persistence management of Doctrine.
1711
     *
1712
     * @ignore
1713
     * @param object $document
1714
     * @throws \InvalidArgumentException
1715
     * @return boolean
1716
     */
1717 76
    public function removeFromIdentityMap($document)
1718
    {
1719 76
        $oid = spl_object_hash($document);
1720
1721
        // Check if id is registered first
1722 76
        if ( ! isset($this->documentIdentifiers[$oid])) {
1723
            return false;
1724
        }
1725
1726 76
        $class = $this->dm->getClassMetadata(get_class($document));
1727 76
        $id = $this->getIdForIdentityMap($document);
1728
1729 76
        if (isset($this->identityMap[$class->name][$id])) {
1730 76
            unset($this->identityMap[$class->name][$id]);
1731 76
            $this->documentStates[$oid] = self::STATE_DETACHED;
1732 76
            return true;
1733
        }
1734
1735
        return false;
1736
    }
1737
1738
    /**
1739
     * INTERNAL:
1740
     * Gets a document in the identity map by its identifier hash.
1741
     *
1742
     * @ignore
1743
     * @param mixed         $id    Document identifier
1744
     * @param ClassMetadata $class Document class
1745
     * @return object
1746
     * @throws InvalidArgumentException if the class does not have an identifier
1747
     */
1748 32
    public function getById($id, ClassMetadata $class)
1749
    {
1750 32
        if ( ! $class->identifier) {
1751
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1752
        }
1753
1754 32
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1755
1756 32
        return $this->identityMap[$class->name][$serializedId];
1757
    }
1758
1759
    /**
1760
     * INTERNAL:
1761
     * Tries to get a document by its identifier hash. If no document is found
1762
     * for the given hash, FALSE is returned.
1763
     *
1764
     * @ignore
1765
     * @param mixed         $id    Document identifier
1766
     * @param ClassMetadata $class Document class
1767
     * @return mixed The found document or FALSE.
1768
     * @throws InvalidArgumentException if the class does not have an identifier
1769
     */
1770 292
    public function tryGetById($id, ClassMetadata $class)
1771
    {
1772 292
        if ( ! $class->identifier) {
1773
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1774
        }
1775
1776 292
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1777
1778 292
        return isset($this->identityMap[$class->name][$serializedId]) ?
1779 292
            $this->identityMap[$class->name][$serializedId] : false;
1780
    }
1781
1782
    /**
1783
     * Schedules a document for dirty-checking at commit-time.
1784
     *
1785
     * @param object $document The document to schedule for dirty-checking.
1786
     * @todo Rename: scheduleForSynchronization
1787
     */
1788 2
    public function scheduleForDirtyCheck($document)
1789
    {
1790 2
        $class = $this->dm->getClassMetadata(get_class($document));
1791 2
        $this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)] = $document;
1792 2
    }
1793
1794
    /**
1795
     * Checks whether a document is registered in the identity map.
1796
     *
1797
     * @param object $document
1798
     * @return boolean
1799
     */
1800 76
    public function isInIdentityMap($document)
1801
    {
1802 76
        $oid = spl_object_hash($document);
1803
1804 76
        if ( ! isset($this->documentIdentifiers[$oid])) {
1805 4
            return false;
1806
        }
1807
1808 75
        $class = $this->dm->getClassMetadata(get_class($document));
1809 75
        $id = $this->getIdForIdentityMap($document);
1810
1811 75
        return isset($this->identityMap[$class->name][$id]);
1812
    }
1813
1814
    /**
1815
     * @param object $document
1816
     * @return string
1817
     */
1818 603
    private function getIdForIdentityMap($document)
1819
    {
1820 603
        $class = $this->dm->getClassMetadata(get_class($document));
1821
1822 603
        if ( ! $class->identifier) {
1823 147
            $id = spl_object_hash($document);
1824 147
        } else {
1825 602
            $id = $this->documentIdentifiers[spl_object_hash($document)];
1826 602
            $id = serialize($class->getDatabaseIdentifierValue($id));
1827
        }
1828
1829 603
        return $id;
1830
    }
1831
1832
    /**
1833
     * INTERNAL:
1834
     * Checks whether an identifier exists in the identity map.
1835
     *
1836
     * @ignore
1837
     * @param string $id
1838
     * @param string $rootClassName
1839
     * @return boolean
1840
     */
1841
    public function containsId($id, $rootClassName)
1842
    {
1843
        return isset($this->identityMap[$rootClassName][serialize($id)]);
1844
    }
1845
1846
    /**
1847
     * Persists a document as part of the current unit of work.
1848
     *
1849
     * @param object $document The document to persist.
1850
     * @throws MongoDBException If trying to persist MappedSuperclass.
1851
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1852
     */
1853 574
    public function persist($document)
1854
    {
1855 574
        $class = $this->dm->getClassMetadata(get_class($document));
1856 574
        if ($class->isMappedSuperclass) {
1857 1
            throw MongoDBException::cannotPersistMappedSuperclass($class->name);
1858
        }
1859 573
        $visited = array();
1860 573
        $this->doPersist($document, $visited);
1861 569
    }
1862
1863
    /**
1864
     * Saves a document as part of the current unit of work.
1865
     * This method is internally called during save() cascades as it tracks
1866
     * the already visited documents to prevent infinite recursions.
1867
     *
1868
     * NOTE: This method always considers documents that are not yet known to
1869
     * this UnitOfWork as NEW.
1870
     *
1871
     * @param object $document The document to persist.
1872
     * @param array $visited The already visited documents.
1873
     * @throws \InvalidArgumentException
1874
     * @throws MongoDBException
1875
     */
1876 573
    private function doPersist($document, array &$visited)
1877
    {
1878 573
        $oid = spl_object_hash($document);
1879 573
        if (isset($visited[$oid])) {
1880 24
            return; // Prevent infinite recursion
1881
        }
1882
1883 573
        $visited[$oid] = $document; // Mark visited
1884
1885 573
        $class = $this->dm->getClassMetadata(get_class($document));
1886
1887 573
        $documentState = $this->getDocumentState($document, self::STATE_NEW);
1888
        switch ($documentState) {
1889 573
            case self::STATE_MANAGED:
1890
                // Nothing to do, except if policy is "deferred explicit"
1891 44
                if ($class->isChangeTrackingDeferredExplicit()) {
1892
                    $this->scheduleForDirtyCheck($document);
1893
                }
1894 44
                break;
1895 573
            case self::STATE_NEW:
1896 573
                $this->persistNew($class, $document);
1897 571
                break;
1898
1899 2
            case self::STATE_REMOVED:
1900
                // Document becomes managed again
1901 2
                unset($this->documentDeletions[$oid]);
1902
1903 2
                $this->documentStates[$oid] = self::STATE_MANAGED;
1904 2
                break;
1905
1906
            case self::STATE_DETACHED:
1907
                throw new \InvalidArgumentException(
1908
                    "Behavior of persist() for a detached document is not yet defined.");
1909
1910
            default:
1911
                throw MongoDBException::invalidDocumentState($documentState);
1912
        }
1913
1914 571
        $this->cascadePersist($document, $visited);
1915 569
    }
1916
1917
    /**
1918
     * Deletes a document as part of the current unit of work.
1919
     *
1920
     * @param object $document The document to remove.
1921
     */
1922 66
    public function remove($document)
1923
    {
1924 66
        $visited = array();
1925 66
        $this->doRemove($document, $visited);
1926 66
    }
1927
1928
    /**
1929
     * Deletes a document as part of the current unit of work.
1930
     *
1931
     * This method is internally called during delete() cascades as it tracks
1932
     * the already visited documents to prevent infinite recursions.
1933
     *
1934
     * @param object $document The document to delete.
1935
     * @param array $visited The map of the already visited documents.
1936
     * @throws MongoDBException
1937
     */
1938 66
    private function doRemove($document, array &$visited)
1939
    {
1940 66
        $oid = spl_object_hash($document);
1941 66
        if (isset($visited[$oid])) {
1942 1
            return; // Prevent infinite recursion
1943
        }
1944
1945 66
        $visited[$oid] = $document; // mark visited
1946
1947
        /* Cascade first, because scheduleForDelete() removes the entity from
1948
         * the identity map, which can cause problems when a lazy Proxy has to
1949
         * be initialized for the cascade operation.
1950
         */
1951 66
        $this->cascadeRemove($document, $visited);
1952
1953 66
        $class = $this->dm->getClassMetadata(get_class($document));
1954 66
        $documentState = $this->getDocumentState($document);
1955
        switch ($documentState) {
1956 66
            case self::STATE_NEW:
1957 66
            case self::STATE_REMOVED:
1958
                // nothing to do
1959 1
                break;
1960 66
            case self::STATE_MANAGED:
1961 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...
1962 8
                    $class->invokeLifecycleCallbacks(Events::preRemove, $document, array(new LifecycleEventArgs($document, $this->dm)));
1963 8
                }
1964 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...
1965 1
                    $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($document, $this->dm));
1966 1
                }
1967 66
                $this->scheduleForDelete($document);
1968 66
                break;
1969
            case self::STATE_DETACHED:
1970
                throw MongoDBException::detachedDocumentCannotBeRemoved();
1971
            default:
1972
                throw MongoDBException::invalidDocumentState($documentState);
1973
        }
1974 66
    }
1975
1976
    /**
1977
     * Merges the state of the given detached document into this UnitOfWork.
1978
     *
1979
     * @param object $document
1980
     * @return object The managed copy of the document.
1981
     */
1982 13
    public function merge($document)
1983
    {
1984 13
        $visited = array();
1985
1986 13
        return $this->doMerge($document, $visited);
1987
    }
1988
1989
    /**
1990
     * Executes a merge operation on a document.
1991
     *
1992
     * @param object      $document
1993
     * @param array       $visited
1994
     * @param object|null $prevManagedCopy
1995
     * @param array|null  $assoc
1996
     *
1997
     * @return object The managed copy of the document.
1998
     *
1999
     * @throws InvalidArgumentException If the entity instance is NEW.
2000
     * @throws LockException If the document uses optimistic locking through a
2001
     *                       version attribute and the version check against the
2002
     *                       managed copy fails.
2003
     */
2004 13
    private function doMerge($document, array &$visited, $prevManagedCopy = null, $assoc = null)
2005
    {
2006 13
        $oid = spl_object_hash($document);
2007
2008 13
        if (isset($visited[$oid])) {
2009 1
            return $visited[$oid]; // Prevent infinite recursion
2010
        }
2011
2012 13
        $visited[$oid] = $document; // mark visited
2013
2014 13
        $class = $this->dm->getClassMetadata(get_class($document));
2015
2016
        /* First we assume DETACHED, although it can still be NEW but we can
2017
         * avoid an extra DB round trip this way. If it is not MANAGED but has
2018
         * an identity, we need to fetch it from the DB anyway in order to
2019
         * merge. MANAGED documents are ignored by the merge operation.
2020
         */
2021 13
        $managedCopy = $document;
2022
2023 13
        if ($this->getDocumentState($document, self::STATE_DETACHED) !== self::STATE_MANAGED) {
2024 13
            if ($document instanceof Proxy && ! $document->__isInitialized()) {
2025
                $document->__load();
2026
            }
2027
2028
            // Try to look the document up in the identity map.
2029 13
            $id = $class->isEmbeddedDocument ? null : $class->getIdentifierObject($document);
2030
2031 13
            if ($id === null) {
2032
                // If there is no identifier, it is actually NEW.
2033 5
                $managedCopy = $class->newInstance();
2034 5
                $this->persistNew($class, $managedCopy);
2035 5
            } else {
2036 10
                $managedCopy = $this->tryGetById($id, $class);
2037
2038 10
                if ($managedCopy) {
2039
                    // We have the document in memory already, just make sure it is not removed.
2040 5
                    if ($this->getDocumentState($managedCopy) === self::STATE_REMOVED) {
2041
                        throw new \InvalidArgumentException('Removed entity detected during merge. Cannot merge with a removed entity.');
2042
                    }
2043 5
                } else {
2044
                    // We need to fetch the managed copy in order to merge.
2045 7
                    $managedCopy = $this->dm->find($class->name, $id);
2046
                }
2047
2048 10
                if ($managedCopy === null) {
2049
                    // If the identifier is ASSIGNED, it is NEW
2050
                    $managedCopy = $class->newInstance();
2051
                    $class->setIdentifierValue($managedCopy, $id);
2052
                    $this->persistNew($class, $managedCopy);
2053
                } else {
2054 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...
2055
                        $managedCopy->__load();
2056
                    }
2057
                }
2058
            }
2059
2060 13
            if ($class->isVersioned) {
2061
                $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
2062
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2063
2064
                // Throw exception if versions don't match
2065
                if ($managedCopyVersion != $documentVersion) {
2066
                    throw LockException::lockFailedVersionMissmatch($document, $documentVersion, $managedCopyVersion);
2067
                }
2068
            }
2069
2070
            // Merge state of $document into existing (managed) document
2071 13
            foreach ($class->reflClass->getProperties() as $prop) {
2072 13
                $name = $prop->name;
2073 13
                $prop->setAccessible(true);
2074 13
                if ( ! isset($class->associationMappings[$name])) {
2075 13
                    if ( ! $class->isIdentifier($name)) {
2076 13
                        $prop->setValue($managedCopy, $prop->getValue($document));
2077 13
                    }
2078 13
                } else {
2079 13
                    $assoc2 = $class->associationMappings[$name];
2080
2081 13
                    if ($assoc2['type'] === 'one') {
2082 5
                        $other = $prop->getValue($document);
2083
2084 5
                        if ($other === null) {
2085 2
                            $prop->setValue($managedCopy, null);
2086 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...
2087
                            // Do not merge fields marked lazy that have not been fetched
2088 1
                            continue;
2089 3
                        } elseif ( ! $assoc2['isCascadeMerge']) {
2090
                            if ($this->getDocumentState($other) === self::STATE_DETACHED) {
2091
                                $targetDocument = isset($assoc2['targetDocument']) ? $assoc2['targetDocument'] : get_class($other);
2092
                                /* @var $targetClass \Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo */
2093
                                $targetClass = $this->dm->getClassMetadata($targetDocument);
2094
                                $relatedId = $targetClass->getIdentifierObject($other);
2095
2096
                                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...
2097
                                    $other = $this->dm->find($targetClass->name, $relatedId);
2098
                                } else {
2099
                                    $other = $this
2100
                                        ->dm
2101
                                        ->getProxyFactory()
2102
                                        ->getProxy($assoc2['targetDocument'], array($targetClass->identifier => $relatedId));
2103
                                    $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...
2104
                                }
2105
                            }
2106
2107
                            $prop->setValue($managedCopy, $other);
2108
                        }
2109 4
                    } else {
2110 10
                        $mergeCol = $prop->getValue($document);
2111
2112 10
                        if ($mergeCol instanceof PersistentCollectionInterface && ! $mergeCol->isInitialized()) {
0 ignored issues
show
Bug introduced by
The method isInitialized() does not exist on Doctrine\ODM\MongoDB\Per...tentCollectionInterface. Did you maybe mean initialize()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
2113
                            /* Do not merge fields marked lazy that have not
2114
                             * been fetched. Keep the lazy persistent collection
2115
                             * of the managed copy.
2116
                             */
2117 3
                            continue;
2118
                        }
2119
2120 7
                        $managedCol = $prop->getValue($managedCopy);
2121
2122 7
                        if ( ! $managedCol) {
2123 2
                            $managedCol = $this->persistentCollectionFactory->create($assoc2, null);
2124 2
                            $managedCol->setOwner($managedCopy, $assoc2);
2125 2
                            $prop->setValue($managedCopy, $managedCol);
2126 2
                            $this->originalDocumentData[$oid][$name] = $managedCol;
2127 2
                        }
2128
2129
                        /* Note: do not process association's target documents.
2130
                         * They will be handled during the cascade. Initialize
2131
                         * and, if necessary, clear $managedCol for now.
2132
                         */
2133 7
                        if ($assoc2['isCascadeMerge']) {
2134 7
                            $managedCol->initialize();
2135
2136
                            // If $managedCol differs from the merged collection, clear and set dirty
2137 7
                            if ( ! $managedCol->isEmpty() && $managedCol !== $mergeCol) {
2138 2
                                $managedCol->unwrap()->clear();
2139 2
                                $managedCol->setDirty(true);
2140
2141 2
                                if ($assoc2['isOwningSide'] && $class->isChangeTrackingNotify()) {
2142
                                    $this->scheduleForDirtyCheck($managedCopy);
2143
                                }
2144 2
                            }
2145 7
                        }
2146
                    }
2147
                }
2148
2149 13
                if ($class->isChangeTrackingNotify()) {
2150
                    // Just treat all properties as changed, there is no other choice.
2151
                    $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy));
2152
                }
2153 13
            }
2154
2155 13
            if ($class->isChangeTrackingDeferredExplicit()) {
2156
                $this->scheduleForDirtyCheck($document);
2157
            }
2158 13
        }
2159
2160 13
        if ($prevManagedCopy !== null) {
2161 6
            $assocField = $assoc['fieldName'];
2162 6
            $prevClass = $this->dm->getClassMetadata(get_class($prevManagedCopy));
2163
2164 6
            if ($assoc['type'] === 'one') {
2165 2
                $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
2166 2
            } else {
2167 4
                $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
2168
2169 4
                if ($assoc['type'] === 'many' && isset($assoc['mappedBy'])) {
2170 1
                    $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy);
2171 1
                }
2172
            }
2173 6
        }
2174
2175
        // Mark the managed copy visited as well
2176 13
        $visited[spl_object_hash($managedCopy)] = true;
2177
2178 13
        $this->cascadeMerge($document, $managedCopy, $visited);
2179
2180 13
        return $managedCopy;
2181
    }
2182
2183
    /**
2184
     * Detaches a document from the persistence management. It's persistence will
2185
     * no longer be managed by Doctrine.
2186
     *
2187
     * @param object $document The document to detach.
2188
     */
2189 9
    public function detach($document)
2190
    {
2191 9
        $visited = array();
2192 9
        $this->doDetach($document, $visited);
2193 9
    }
2194
2195
    /**
2196
     * Executes a detach operation on the given document.
2197
     *
2198
     * @param object $document
2199
     * @param array $visited
2200
     * @internal This method always considers documents with an assigned identifier as DETACHED.
2201
     */
2202 12
    private function doDetach($document, array &$visited)
2203
    {
2204 12
        $oid = spl_object_hash($document);
2205 12
        if (isset($visited[$oid])) {
2206 4
            return; // Prevent infinite recursion
2207
        }
2208
2209 12
        $visited[$oid] = $document; // mark visited
2210
2211 12
        switch ($this->getDocumentState($document, self::STATE_DETACHED)) {
2212 12
            case self::STATE_MANAGED:
2213 12
                $this->removeFromIdentityMap($document);
2214 12
                unset($this->documentInsertions[$oid], $this->documentUpdates[$oid],
2215 12
                    $this->documentDeletions[$oid], $this->documentIdentifiers[$oid],
2216 12
                    $this->documentStates[$oid], $this->originalDocumentData[$oid],
2217 12
                    $this->parentAssociations[$oid], $this->documentUpserts[$oid],
2218 12
                    $this->hasScheduledCollections[$oid]);
2219 12
                break;
2220 4
            case self::STATE_NEW:
2221 4
            case self::STATE_DETACHED:
2222 4
                return;
2223 12
        }
2224
2225 12
        $this->cascadeDetach($document, $visited);
2226 12
    }
2227
2228
    /**
2229
     * Refreshes the state of the given document from the database, overwriting
2230
     * any local, unpersisted changes.
2231
     *
2232
     * @param object $document The document to refresh.
2233
     * @throws \InvalidArgumentException If the document is not MANAGED.
2234
     */
2235 21
    public function refresh($document)
2236
    {
2237 21
        $visited = array();
2238 21
        $this->doRefresh($document, $visited);
2239 20
    }
2240
2241
    /**
2242
     * Executes a refresh operation on a document.
2243
     *
2244
     * @param object $document The document to refresh.
2245
     * @param array $visited The already visited documents during cascades.
2246
     * @throws \InvalidArgumentException If the document is not MANAGED.
2247
     */
2248 21
    private function doRefresh($document, array &$visited)
2249
    {
2250 21
        $oid = spl_object_hash($document);
2251 21
        if (isset($visited[$oid])) {
2252
            return; // Prevent infinite recursion
2253
        }
2254
2255 21
        $visited[$oid] = $document; // mark visited
2256
2257 21
        $class = $this->dm->getClassMetadata(get_class($document));
2258
2259 21
        if ( ! $class->isEmbeddedDocument) {
2260 21
            if ($this->getDocumentState($document) == self::STATE_MANAGED) {
2261 20
                $id = $class->getDatabaseIdentifierValue($this->documentIdentifiers[$oid]);
2262 20
                $this->getDocumentPersister($class->name)->refresh($id, $document);
2263 20
            } else {
2264 1
                throw new \InvalidArgumentException("Document is not MANAGED.");
2265
            }
2266 20
        }
2267
2268 20
        $this->cascadeRefresh($document, $visited);
2269 20
    }
2270
2271
    /**
2272
     * Cascades a refresh operation to associated documents.
2273
     *
2274
     * @param object $document
2275
     * @param array $visited
2276
     */
2277 20
    private function cascadeRefresh($document, array &$visited)
2278
    {
2279 20
        $class = $this->dm->getClassMetadata(get_class($document));
2280
2281 20
        $associationMappings = array_filter(
2282 20
            $class->associationMappings,
2283
            function ($assoc) { return $assoc['isCascadeRefresh']; }
2284 20
        );
2285
2286 20
        foreach ($associationMappings as $mapping) {
2287 15
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2288 15
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2289 15
                if ($relatedDocuments instanceof PersistentCollectionInterface) {
2290
                    // Unwrap so that foreach() does not initialize
2291 15
                    $relatedDocuments = $relatedDocuments->unwrap();
2292 15
                }
2293 15
                foreach ($relatedDocuments as $relatedDocument) {
2294
                    $this->doRefresh($relatedDocument, $visited);
2295 15
                }
2296 15
            } elseif ($relatedDocuments !== null) {
2297 2
                $this->doRefresh($relatedDocuments, $visited);
2298 2
            }
2299 20
        }
2300 20
    }
2301
2302
    /**
2303
     * Cascades a detach operation to associated documents.
2304
     *
2305
     * @param object $document
2306
     * @param array $visited
2307
     */
2308 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...
2309
    {
2310 12
        $class = $this->dm->getClassMetadata(get_class($document));
2311 12
        foreach ($class->fieldMappings as $mapping) {
2312 12
            if ( ! $mapping['isCascadeDetach']) {
2313 12
                continue;
2314
            }
2315 7
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2316 7
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2317 7
                if ($relatedDocuments instanceof PersistentCollectionInterface) {
2318
                    // Unwrap so that foreach() does not initialize
2319 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2320 6
                }
2321 7
                foreach ($relatedDocuments as $relatedDocument) {
2322 5
                    $this->doDetach($relatedDocument, $visited);
2323 7
                }
2324 7
            } elseif ($relatedDocuments !== null) {
2325 5
                $this->doDetach($relatedDocuments, $visited);
2326 5
            }
2327 12
        }
2328 12
    }
2329
    /**
2330
     * Cascades a merge operation to associated documents.
2331
     *
2332
     * @param object $document
2333
     * @param object $managedCopy
2334
     * @param array $visited
2335
     */
2336 13
    private function cascadeMerge($document, $managedCopy, array &$visited)
2337
    {
2338 13
        $class = $this->dm->getClassMetadata(get_class($document));
2339
2340 13
        $associationMappings = array_filter(
2341 13
            $class->associationMappings,
2342
            function ($assoc) { return $assoc['isCascadeMerge']; }
2343 13
        );
2344
2345 13
        foreach ($associationMappings as $assoc) {
2346 12
            $relatedDocuments = $class->reflFields[$assoc['fieldName']]->getValue($document);
2347
2348 12
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2349 8
                if ($relatedDocuments === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
2350
                    // Collections are the same, so there is nothing to do
2351
                    continue;
2352
                }
2353
2354 8
                if ($relatedDocuments instanceof PersistentCollectionInterface) {
2355
                    // Unwrap so that foreach() does not initialize
2356 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2357 6
                }
2358
2359 8
                foreach ($relatedDocuments as $relatedDocument) {
2360 4
                    $this->doMerge($relatedDocument, $visited, $managedCopy, $assoc);
2361 8
                }
2362 12
            } elseif ($relatedDocuments !== null) {
2363 3
                $this->doMerge($relatedDocuments, $visited, $managedCopy, $assoc);
2364 3
            }
2365 13
        }
2366 13
    }
2367
2368
    /**
2369
     * Cascades the save operation to associated documents.
2370
     *
2371
     * @param object $document
2372
     * @param array $visited
2373
     */
2374 571
    private function cascadePersist($document, array &$visited)
2375
    {
2376 571
        $class = $this->dm->getClassMetadata(get_class($document));
2377
2378 571
        $associationMappings = array_filter(
2379 571
            $class->associationMappings,
2380
            function ($assoc) { return $assoc['isCascadePersist']; }
2381 571
        );
2382
2383 571
        foreach ($associationMappings as $fieldName => $mapping) {
2384 392
            $relatedDocuments = $class->reflFields[$fieldName]->getValue($document);
2385
2386 392
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2387 342
                if ($relatedDocuments instanceof PersistentCollectionInterface) {
2388 17
                    if ($relatedDocuments->getOwner() !== $document) {
2389 2
                        $relatedDocuments = $this->fixPersistentCollectionOwnership($relatedDocuments, $document, $class, $mapping['fieldName']);
2390 2
                    }
2391
                    // Unwrap so that foreach() does not initialize
2392 17
                    $relatedDocuments = $relatedDocuments->unwrap();
2393 17
                }
2394
2395 342
                $count = 0;
2396 342
                foreach ($relatedDocuments as $relatedKey => $relatedDocument) {
2397 189
                    if ( ! empty($mapping['embedded'])) {
2398 115
                        list(, $knownParent, ) = $this->getParentAssociation($relatedDocument);
2399 115
                        if ($knownParent && $knownParent !== $document) {
2400 4
                            $relatedDocument = clone $relatedDocument;
2401 4
                            $relatedDocuments[$relatedKey] = $relatedDocument;
2402 4
                        }
2403 115
                        $pathKey = ! isset($mapping['strategy']) || CollectionHelper::isList($mapping['strategy']) ? $count++ : $relatedKey;
2404 115
                        $this->setParentAssociation($relatedDocument, $mapping, $document, $mapping['fieldName'] . '.' . $pathKey);
2405 115
                    }
2406 189
                    $this->doPersist($relatedDocument, $visited);
2407 341
                }
2408 392
            } elseif ($relatedDocuments !== null) {
2409 120
                if ( ! empty($mapping['embedded'])) {
2410 66
                    list(, $knownParent, ) = $this->getParentAssociation($relatedDocuments);
2411 66
                    if ($knownParent && $knownParent !== $document) {
2412 5
                        $relatedDocuments = clone $relatedDocuments;
2413 5
                        $class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments);
2414 5
                    }
2415 66
                    $this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']);
2416 66
                }
2417 120
                $this->doPersist($relatedDocuments, $visited);
2418 119
            }
2419 570
        }
2420 569
    }
2421
2422
    /**
2423
     * Cascades the delete operation to associated documents.
2424
     *
2425
     * @param object $document
2426
     * @param array $visited
2427
     */
2428 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...
2429
    {
2430 66
        $class = $this->dm->getClassMetadata(get_class($document));
2431 66
        foreach ($class->fieldMappings as $mapping) {
2432 66
            if ( ! $mapping['isCascadeRemove']) {
2433 66
                continue;
2434
            }
2435 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...
2436 2
                $document->__load();
2437 2
            }
2438
2439 33
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2440 33
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2441
                // If its a PersistentCollection initialization is intended! No unwrap!
2442 24
                foreach ($relatedDocuments as $relatedDocument) {
2443 13
                    $this->doRemove($relatedDocument, $visited);
2444 24
                }
2445 33
            } elseif ($relatedDocuments !== null) {
2446 12
                $this->doRemove($relatedDocuments, $visited);
2447 12
            }
2448 66
        }
2449 66
    }
2450
2451
    /**
2452
     * Acquire a lock on the given document.
2453
     *
2454
     * @param object $document
2455
     * @param int $lockMode
2456
     * @param int $lockVersion
2457
     * @throws LockException
2458
     * @throws \InvalidArgumentException
2459
     */
2460 9
    public function lock($document, $lockMode, $lockVersion = null)
2461
    {
2462 9
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2463 1
            throw new \InvalidArgumentException("Document is not MANAGED.");
2464
        }
2465
2466 8
        $documentName = get_class($document);
2467 8
        $class = $this->dm->getClassMetadata($documentName);
2468
2469 8
        if ($lockMode == LockMode::OPTIMISTIC) {
2470 3
            if ( ! $class->isVersioned) {
2471 1
                throw LockException::notVersioned($documentName);
2472
            }
2473
2474 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...
2475 2
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2476 2
                if ($documentVersion != $lockVersion) {
2477 1
                    throw LockException::lockFailedVersionMissmatch($document, $lockVersion, $documentVersion);
2478
                }
2479 1
            }
2480 6
        } elseif (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) {
2481 5
            $this->getDocumentPersister($class->name)->lock($document, $lockMode);
2482 5
        }
2483 6
    }
2484
2485
    /**
2486
     * Releases a lock on the given document.
2487
     *
2488
     * @param object $document
2489
     * @throws \InvalidArgumentException
2490
     */
2491 1
    public function unlock($document)
2492
    {
2493 1
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2494
            throw new \InvalidArgumentException("Document is not MANAGED.");
2495
        }
2496 1
        $documentName = get_class($document);
2497 1
        $this->getDocumentPersister($documentName)->unlock($document);
2498 1
    }
2499
2500
    /**
2501
     * Clears the UnitOfWork.
2502
     *
2503
     * @param string|null $documentName if given, only documents of this type will get detached.
2504
     */
2505 391
    public function clear($documentName = null)
2506
    {
2507 391
        if ($documentName === null) {
2508 385
            $this->identityMap =
2509 385
            $this->documentIdentifiers =
2510 385
            $this->originalDocumentData =
2511 385
            $this->documentChangeSets =
2512 385
            $this->documentStates =
2513 385
            $this->scheduledForDirtyCheck =
2514 385
            $this->documentInsertions =
2515 385
            $this->documentUpserts =
2516 385
            $this->documentUpdates =
2517 385
            $this->documentDeletions =
2518 385
            $this->collectionUpdates =
2519 385
            $this->collectionDeletions =
2520 385
            $this->parentAssociations =
2521 385
            $this->orphanRemovals = 
2522 385
            $this->hasScheduledCollections = array();
2523 385
        } else {
2524 6
            $visited = array();
2525 6
            foreach ($this->identityMap as $className => $documents) {
2526 6
                if ($className === $documentName) {
2527 3
                    foreach ($documents as $document) {
2528 3
                        $this->doDetach($document, $visited);
2529 3
                    }
2530 3
                }
2531 6
            }
2532
        }
2533
2534 391 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...
2535
            $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->dm, $documentName));
2536
        }
2537 391
    }
2538
2539
    /**
2540
     * INTERNAL:
2541
     * Schedules an embedded document for removal. The remove() operation will be
2542
     * invoked on that document at the beginning of the next commit of this
2543
     * UnitOfWork.
2544
     *
2545
     * @ignore
2546
     * @param object $document
2547
     */
2548 47
    public function scheduleOrphanRemoval($document)
2549
    {
2550 47
        $this->orphanRemovals[spl_object_hash($document)] = $document;
2551 47
    }
2552
2553
    /**
2554
     * INTERNAL:
2555
     * Unschedules an embedded or referenced object for removal.
2556
     *
2557
     * @ignore
2558
     * @param object $document
2559
     */
2560 104
    public function unscheduleOrphanRemoval($document)
2561
    {
2562 104
        $oid = spl_object_hash($document);
2563 104
        if (isset($this->orphanRemovals[$oid])) {
2564 1
            unset($this->orphanRemovals[$oid]);
2565 1
        }
2566 104
    }
2567
2568
    /**
2569
     * Fixes PersistentCollection state if it wasn't used exactly as we had in mind:
2570
     *  1) sets owner if it was cloned
2571
     *  2) clones collection, sets owner, updates document's property and, if necessary, updates originalData
2572
     *  3) NOP if state is OK
2573
     * Returned collection should be used from now on (only important with 2nd point)
2574
     *
2575
     * @param PersistentCollection $coll
2576
     * @param object $document
2577
     * @param ClassMetadata $class
2578
     * @param string $propName
2579
     * @return PersistentCollection
2580
     */
2581 8
    private function fixPersistentCollectionOwnership(PersistentCollectionInterface $coll, $document, ClassMetadata $class, $propName)
2582
    {
2583 8
        $owner = $coll->getOwner();
2584 8
        if ($owner === null) { // cloned
2585 6
            $coll->setOwner($document, $class->fieldMappings[$propName]);
2586 8
        } elseif ($owner !== $document) { // no clone, we have to fix
2587 2
            if ( ! $coll->isInitialized()) {
0 ignored issues
show
Bug introduced by
The method isInitialized() does not exist on Doctrine\ODM\MongoDB\Per...tentCollectionInterface. Did you maybe mean initialize()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
2588 1
                $coll->initialize(); // we have to do this otherwise the cols share state
2589 1
            }
2590 2
            $newValue = clone $coll;
2591 2
            $newValue->setOwner($document, $class->fieldMappings[$propName]);
2592 2
            $class->reflFields[$propName]->setValue($document, $newValue);
2593 2
            if ($this->isScheduledForUpdate($document)) {
2594
                // @todo following line should be superfluous once collections are stored in change sets
2595
                $this->setOriginalDocumentProperty(spl_object_hash($document), $propName, $newValue);
2596
            }
2597 2
            return $newValue;
2598
        }
2599 6
        return $coll;
2600
    }
2601
2602
    /**
2603
     * INTERNAL:
2604
     * Schedules a complete collection for removal when this UnitOfWork commits.
2605
     *
2606
     * @param PersistentCollectionInterface $coll
2607
     */
2608 41
    public function scheduleCollectionDeletion(PersistentCollectionInterface $coll)
2609
    {
2610 41
        $oid = spl_object_hash($coll);
2611 41
        unset($this->collectionUpdates[$oid]);
2612 41
        if ( ! isset($this->collectionDeletions[$oid])) {
2613 41
            $this->collectionDeletions[$oid] = $coll;
2614 41
            $this->scheduleCollectionOwner($coll);
2615 41
        }
2616 41
    }
2617
2618
    /**
2619
     * Checks whether a PersistentCollection is scheduled for deletion.
2620
     *
2621
     * @param PersistentCollectionInterface $coll
2622
     * @return boolean
2623
     */
2624 106
    public function isCollectionScheduledForDeletion(PersistentCollectionInterface $coll)
2625
    {
2626 106
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2627
    }
2628
    
2629
    /**
2630
     * INTERNAL:
2631
     * Unschedules a collection from being deleted when this UnitOfWork commits.
2632
     * 
2633
     * @param \Doctrine\ODM\MongoDB\PersistentCollectionInterface $coll
2634
     */
2635 208 View Code Duplication
    public function unscheduleCollectionDeletion(PersistentCollectionInterface $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...
2636
    {
2637 208
        $oid = spl_object_hash($coll);
2638 208
        if (isset($this->collectionDeletions[$oid])) {
2639 11
            $topmostOwner = $this->getOwningDocument($coll->getOwner());
2640 11
            unset($this->collectionDeletions[$oid]);
2641 11
            unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2642 11
        }
2643 208
    }
2644
2645
    /**
2646
     * INTERNAL:
2647
     * Schedules a collection for update when this UnitOfWork commits.
2648
     *
2649
     * @param PersistentCollectionInterface $coll
2650
     */
2651 224
    public function scheduleCollectionUpdate(PersistentCollectionInterface $coll)
2652
    {
2653 224
        $mapping = $coll->getMapping();
2654 224
        if (CollectionHelper::usesSet($mapping['strategy'])) {
2655
            /* There is no need to $unset collection if it will be $set later
2656
             * This is NOP if collection is not scheduled for deletion
2657
             */
2658 40
            $this->unscheduleCollectionDeletion($coll);
2659 40
        }
2660 224
        $oid = spl_object_hash($coll);
2661 224
        if ( ! isset($this->collectionUpdates[$oid])) {
2662 224
            $this->collectionUpdates[$oid] = $coll;
2663 224
            $this->scheduleCollectionOwner($coll);
2664 224
        }
2665 224
    }
2666
    
2667
    /**
2668
     * INTERNAL:
2669
     * Unschedules a collection from being updated when this UnitOfWork commits.
2670
     * 
2671
     * @param \Doctrine\ODM\MongoDB\PersistentCollectionInterface $coll
2672
     */
2673 208 View Code Duplication
    public function unscheduleCollectionUpdate(PersistentCollectionInterface $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...
2674
    {
2675 208
        $oid = spl_object_hash($coll);
2676 208
        if (isset($this->collectionUpdates[$oid])) {
2677 198
            $topmostOwner = $this->getOwningDocument($coll->getOwner());
2678 198
            unset($this->collectionUpdates[$oid]);
2679 198
            unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2680 198
        }
2681 208
    }
2682
    
2683
    /**
2684
     * Checks whether a PersistentCollection is scheduled for update.
2685
     *
2686
     * @param PersistentCollectionInterface $coll
2687
     * @return boolean
2688
     */
2689 122
    public function isCollectionScheduledForUpdate(PersistentCollectionInterface $coll)
2690
    {
2691 122
        return isset($this->collectionUpdates[spl_object_hash($coll)]);
2692
    }
2693
2694
    /**
2695
     * INTERNAL:
2696
     * Gets PersistentCollections that have been visited during computing change
2697
     * set of $document
2698
     *
2699
     * @param object $document
2700
     * @return PersistentCollectionInterface[]
2701
     */
2702 539
    public function getVisitedCollections($document)
2703
    {
2704 539
        $oid = spl_object_hash($document);
2705 539
        return isset($this->visitedCollections[$oid])
2706 539
                ? $this->visitedCollections[$oid]
2707 539
                : array();
2708
    }
2709
    
2710
    /**
2711
     * INTERNAL:
2712
     * Gets PersistentCollections that are scheduled to update and related to $document
2713
     * 
2714
     * @param object $document
2715
     * @return array
2716
     */
2717 539
    public function getScheduledCollections($document)
2718
    {
2719 539
        $oid = spl_object_hash($document);
2720 539
        return isset($this->hasScheduledCollections[$oid]) 
2721 539
                ? $this->hasScheduledCollections[$oid]
2722 539
                : array();
2723
    }
2724
    
2725
    /**
2726
     * Checks whether the document is related to a PersistentCollection
2727
     * scheduled for update or deletion.
2728
     *
2729
     * @param object $document
2730
     * @return boolean
2731
     */
2732 60
    public function hasScheduledCollections($document)
2733
    {
2734 60
        return isset($this->hasScheduledCollections[spl_object_hash($document)]);
2735
    }
2736
    
2737
    /**
2738
     * Marks the PersistentCollection's top-level owner as having a relation to
2739
     * a collection scheduled for update or deletion.
2740
     *
2741
     * If the owner is not scheduled for any lifecycle action, it will be
2742
     * scheduled for update to ensure that versioning takes place if necessary.
2743
     *
2744
     * If the collection is nested within atomic collection, it is immediately
2745
     * unscheduled and atomic one is scheduled for update instead. This makes
2746
     * calculating update data way easier.
2747
     * 
2748
     * @param PersistentCollectionInterface $coll
2749
     */
2750 226
    private function scheduleCollectionOwner(PersistentCollectionInterface $coll)
2751
    {
2752 226
        $document = $this->getOwningDocument($coll->getOwner());
2753 226
        $this->hasScheduledCollections[spl_object_hash($document)][spl_object_hash($coll)] = $coll;
2754
2755 226
        if ($document !== $coll->getOwner()) {
2756 24
            $parent = $coll->getOwner();
2757 24
            while (null !== ($parentAssoc = $this->getParentAssociation($parent))) {
2758 24
                list($mapping, $parent, ) = $parentAssoc;
2759 24
            }
2760 24
            if (isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
2761 7
                $class = $this->dm->getClassMetadata(get_class($document));
2762 7
                $atomicCollection = $class->getFieldValue($document, $mapping['fieldName']);
2763 7
                $this->scheduleCollectionUpdate($atomicCollection);
2764 7
                $this->unscheduleCollectionDeletion($coll);
2765 7
                $this->unscheduleCollectionUpdate($coll);
2766 7
            }
2767 24
        }
2768
2769 226
        if ( ! $this->isDocumentScheduled($document)) {
2770 92
            $this->scheduleForUpdate($document);
2771 92
        }
2772 226
    }
2773
2774
    /**
2775
     * Get the top-most owning document of a given document
2776
     *
2777
     * If a top-level document is provided, that same document will be returned.
2778
     * For an embedded document, we will walk through parent associations until
2779
     * we find a top-level document.
2780
     *
2781
     * @param object $document
2782
     * @throws \UnexpectedValueException when a top-level document could not be found
2783
     * @return object
2784
     */
2785 228
    public function getOwningDocument($document)
2786
    {
2787 228
        $class = $this->dm->getClassMetadata(get_class($document));
2788 228
        while ($class->isEmbeddedDocument) {
2789 38
            $parentAssociation = $this->getParentAssociation($document);
2790
2791 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...
2792
                throw new \UnexpectedValueException("Could not determine parent association for " . get_class($document));
2793
            }
2794
2795 38
            list(, $document, ) = $parentAssociation;
2796 38
            $class = $this->dm->getClassMetadata(get_class($document));
2797 38
        }
2798
2799 228
        return $document;
2800
    }
2801
2802
    /**
2803
     * Gets the class name for an association (embed or reference) with respect
2804
     * to any discriminator value.
2805
     *
2806
     * @param array      $mapping Field mapping for the association
2807
     * @param array|null $data    Data for the embedded document or reference
2808
     */
2809 209
    public function getClassNameForAssociation(array $mapping, $data)
2810
    {
2811 209
        $discriminatorField = isset($mapping['discriminatorField']) ? $mapping['discriminatorField'] : null;
2812
2813 209
        $discriminatorValue = null;
2814 209
        if (isset($discriminatorField, $data[$discriminatorField])) {
2815 21
            $discriminatorValue = $data[$discriminatorField];
2816 209
        } elseif (isset($mapping['defaultDiscriminatorValue'])) {
2817
            $discriminatorValue = $mapping['defaultDiscriminatorValue'];
2818
        }
2819
2820 209
        if ($discriminatorValue !== null) {
2821 21
            return isset($mapping['discriminatorMap'][$discriminatorValue])
2822 21
                ? $mapping['discriminatorMap'][$discriminatorValue]
2823 21
                : $discriminatorValue;
2824
        }
2825
2826 189
            $class = $this->dm->getClassMetadata($mapping['targetDocument']);
2827
2828 189 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...
2829 15
            $discriminatorValue = $data[$class->discriminatorField];
2830 189
        } elseif ($class->defaultDiscriminatorValue !== null) {
2831 1
            $discriminatorValue = $class->defaultDiscriminatorValue;
2832 1
        }
2833
2834 189
        if ($discriminatorValue !== null) {
2835 16
            return isset($class->discriminatorMap[$discriminatorValue])
2836 16
                ? $class->discriminatorMap[$discriminatorValue]
2837 16
                : $discriminatorValue;
2838
        }
2839
2840 173
        return $mapping['targetDocument'];
2841
    }
2842
2843
    /**
2844
     * INTERNAL:
2845
     * Creates a document. Used for reconstitution of documents during hydration.
2846
     *
2847
     * @ignore
2848
     * @param string $className The name of the document class.
2849
     * @param array $data The data for the document.
2850
     * @param array $hints Any hints to account for during reconstitution/lookup of the document.
2851
     * @param object The document to be hydrated into in case of creation
2852
     * @return object The document instance.
2853
     * @internal Highly performance-sensitive method.
2854
     */
2855 384
    public function getOrCreateDocument($className, $data, &$hints = array(), $document = null)
2856
    {
2857 384
        $class = $this->dm->getClassMetadata($className);
2858
2859
        // @TODO figure out how to remove this
2860 384
        $discriminatorValue = null;
2861 384 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...
2862 17
            $discriminatorValue = $data[$class->discriminatorField];
2863 384
        } elseif (isset($class->defaultDiscriminatorValue)) {
2864 2
            $discriminatorValue = $class->defaultDiscriminatorValue;
2865 2
        }
2866
2867 384
        if ($discriminatorValue !== null) {
2868 18
            $className = isset($class->discriminatorMap[$discriminatorValue])
2869 18
                ? $class->discriminatorMap[$discriminatorValue]
2870 18
                : $discriminatorValue;
2871
2872 18
            $class = $this->dm->getClassMetadata($className);
2873
2874 18
            unset($data[$class->discriminatorField]);
2875 18
        }
2876
2877 384
        $id = $class->getDatabaseIdentifierValue($data['_id']);
2878 384
        $serializedId = serialize($id);
2879
2880 384
        if (isset($this->identityMap[$class->name][$serializedId])) {
2881 90
            $document = $this->identityMap[$class->name][$serializedId];
2882 90
            $oid = spl_object_hash($document);
2883 90
            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...
2884 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...
2885 10
                $overrideLocalValues = true;
2886 10
                if ($document instanceof NotifyPropertyChanged) {
2887
                    $document->addPropertyChangedListener($this);
2888
                }
2889 10
            } else {
2890 86
                $overrideLocalValues = ! empty($hints[Query::HINT_REFRESH]);
2891
            }
2892 90
            if ($overrideLocalValues) {
2893 47
                $data = $this->hydratorFactory->hydrate($document, $data, $hints);
2894 47
                $this->originalDocumentData[$oid] = $data;
2895 47
            }
2896 90
        } else {
2897 356
            if ($document === null) {
2898 356
                $document = $class->newInstance();
2899 356
            }
2900 356
            $this->registerManaged($document, $id, $data);
2901 356
            $oid = spl_object_hash($document);
2902 356
            $this->documentStates[$oid] = self::STATE_MANAGED;
2903 356
            $this->identityMap[$class->name][$serializedId] = $document;
2904 356
            $data = $this->hydratorFactory->hydrate($document, $data, $hints);
2905 356
            $this->originalDocumentData[$oid] = $data;
2906
        }
2907 384
        return $document;
2908
    }
2909
2910
    /**
2911
     * Initializes (loads) an uninitialized persistent collection of a document.
2912
     *
2913
     * @param PersistentCollectionInterface $collection The collection to initialize.
2914
     */
2915 159
    public function loadCollection(PersistentCollectionInterface $collection)
2916
    {
2917 159
        $this->getDocumentPersister(get_class($collection->getOwner()))->loadCollection($collection);
2918 159
    }
2919
2920
    /**
2921
     * Gets the identity map of the UnitOfWork.
2922
     *
2923
     * @return array
2924
     */
2925
    public function getIdentityMap()
2926
    {
2927
        return $this->identityMap;
2928
    }
2929
2930
    /**
2931
     * Gets the original data of a document. The original data is the data that was
2932
     * present at the time the document was reconstituted from the database.
2933
     *
2934
     * @param object $document
2935
     * @return array
2936
     */
2937 1
    public function getOriginalDocumentData($document)
2938
    {
2939 1
        $oid = spl_object_hash($document);
2940 1
        if (isset($this->originalDocumentData[$oid])) {
2941 1
            return $this->originalDocumentData[$oid];
2942
        }
2943
        return array();
2944
    }
2945
2946
    /**
2947
     * @ignore
2948
     */
2949 52
    public function setOriginalDocumentData($document, array $data)
2950
    {
2951 52
        $oid = spl_object_hash($document);
2952 52
        $this->originalDocumentData[$oid] = $data;
2953 52
        unset($this->documentChangeSets[$oid]);
2954 52
    }
2955
2956
    /**
2957
     * INTERNAL:
2958
     * Sets a property value of the original data array of a document.
2959
     *
2960
     * @ignore
2961
     * @param string $oid
2962
     * @param string $property
2963
     * @param mixed $value
2964
     */
2965 3
    public function setOriginalDocumentProperty($oid, $property, $value)
2966
    {
2967 3
        $this->originalDocumentData[$oid][$property] = $value;
2968 3
    }
2969
2970
    /**
2971
     * Gets the identifier of a document.
2972
     *
2973
     * @param object $document
2974
     * @return mixed The identifier value
2975
     */
2976 356
    public function getDocumentIdentifier($document)
2977
    {
2978 356
        return isset($this->documentIdentifiers[spl_object_hash($document)]) ?
2979 356
            $this->documentIdentifiers[spl_object_hash($document)] : null;
2980
    }
2981
2982
    /**
2983
     * Checks whether the UnitOfWork has any pending insertions.
2984
     *
2985
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2986
     */
2987
    public function hasPendingInsertions()
2988
    {
2989
        return ! empty($this->documentInsertions);
2990
    }
2991
2992
    /**
2993
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2994
     * number of documents in the identity map.
2995
     *
2996
     * @return integer
2997
     */
2998 2
    public function size()
2999
    {
3000 2
        $count = 0;
3001 2
        foreach ($this->identityMap as $documentSet) {
3002 2
            $count += count($documentSet);
3003 2
        }
3004 2
        return $count;
3005
    }
3006
3007
    /**
3008
     * INTERNAL:
3009
     * Registers a document as managed.
3010
     *
3011
     * TODO: This method assumes that $id is a valid PHP identifier for the
3012
     * document class. If the class expects its database identifier to be a
3013
     * MongoId, and an incompatible $id is registered (e.g. an integer), the
3014
     * document identifiers map will become inconsistent with the identity map.
3015
     * In the future, we may want to round-trip $id through a PHP and database
3016
     * conversion and throw an exception if it's inconsistent.
3017
     *
3018
     * @param object $document The document.
3019
     * @param array $id The identifier values.
3020
     * @param array $data The original document data.
3021
     */
3022 378
    public function registerManaged($document, $id, array $data)
3023
    {
3024 378
        $oid = spl_object_hash($document);
3025 378
        $class = $this->dm->getClassMetadata(get_class($document));
3026
3027 378
        if ( ! $class->identifier || $id === null) {
3028 103
            $this->documentIdentifiers[$oid] = $oid;
3029 103
        } else {
3030 372
            $this->documentIdentifiers[$oid] = $class->getPHPIdentifierValue($id);
3031
        }
3032
3033 378
        $this->documentStates[$oid] = self::STATE_MANAGED;
3034 378
        $this->originalDocumentData[$oid] = $data;
3035 378
        $this->addToIdentityMap($document);
3036 378
    }
3037
3038
    /**
3039
     * INTERNAL:
3040
     * Clears the property changeset of the document with the given OID.
3041
     *
3042
     * @param string $oid The document's OID.
3043
     */
3044 1
    public function clearDocumentChangeSet($oid)
3045
    {
3046 1
        $this->documentChangeSets[$oid] = array();
3047 1
    }
3048
3049
    /* PropertyChangedListener implementation */
3050
3051
    /**
3052
     * Notifies this UnitOfWork of a property change in a document.
3053
     *
3054
     * @param object $document The document that owns the property.
3055
     * @param string $propertyName The name of the property that changed.
3056
     * @param mixed $oldValue The old value of the property.
3057
     * @param mixed $newValue The new value of the property.
3058
     */
3059 2
    public function propertyChanged($document, $propertyName, $oldValue, $newValue)
3060
    {
3061 2
        $oid = spl_object_hash($document);
3062 2
        $class = $this->dm->getClassMetadata(get_class($document));
3063
3064 2
        if ( ! isset($class->fieldMappings[$propertyName])) {
3065 1
            return; // ignore non-persistent fields
3066
        }
3067
3068
        // Update changeset and mark document for synchronization
3069 2
        $this->documentChangeSets[$oid][$propertyName] = array($oldValue, $newValue);
3070 2
        if ( ! isset($this->scheduledForDirtyCheck[$class->name][$oid])) {
3071 2
            $this->scheduleForDirtyCheck($document);
3072 2
        }
3073 2
    }
3074
3075
    /**
3076
     * Gets the currently scheduled document insertions in this UnitOfWork.
3077
     *
3078
     * @return array
3079
     */
3080 5
    public function getScheduledDocumentInsertions()
3081
    {
3082 5
        return $this->documentInsertions;
3083
    }
3084
3085
    /**
3086
     * Gets the currently scheduled document upserts in this UnitOfWork.
3087
     *
3088
     * @return array
3089
     */
3090 3
    public function getScheduledDocumentUpserts()
3091
    {
3092 3
        return $this->documentUpserts;
3093
    }
3094
3095
    /**
3096
     * Gets the currently scheduled document updates in this UnitOfWork.
3097
     *
3098
     * @return array
3099
     */
3100 3
    public function getScheduledDocumentUpdates()
3101
    {
3102 3
        return $this->documentUpdates;
3103
    }
3104
3105
    /**
3106
     * Gets the currently scheduled document deletions in this UnitOfWork.
3107
     *
3108
     * @return array
3109
     */
3110
    public function getScheduledDocumentDeletions()
3111
    {
3112
        return $this->documentDeletions;
3113
    }
3114
3115
    /**
3116
     * Get the currently scheduled complete collection deletions
3117
     *
3118
     * @return array
3119
     */
3120
    public function getScheduledCollectionDeletions()
3121
    {
3122
        return $this->collectionDeletions;
3123
    }
3124
3125
    /**
3126
     * Gets the currently scheduled collection inserts, updates and deletes.
3127
     *
3128
     * @return array
3129
     */
3130
    public function getScheduledCollectionUpdates()
3131
    {
3132
        return $this->collectionUpdates;
3133
    }
3134
3135
    /**
3136
     * Helper method to initialize a lazy loading proxy or persistent collection.
3137
     *
3138
     * @param object
3139
     * @return void
3140
     */
3141
    public function initializeObject($obj)
3142
    {
3143
        if ($obj instanceof Proxy) {
3144
            $obj->__load();
3145
        } elseif ($obj instanceof PersistentCollectionInterface) {
3146
            $obj->initialize();
3147
        }
3148
    }
3149
3150 1
    private function objToStr($obj)
3151
    {
3152 1
        return method_exists($obj, '__toString') ? (string)$obj : get_class($obj) . '@' . spl_object_hash($obj);
3153
    }
3154
}
3155