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

UnitOfWork::scheduleForUpsert()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

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

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

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

Loading history...
377
            $options = array_merge($defaultOptions, $options);
378
        } else {
379 557
            $options = $defaultOptions;
380
        }
381
        // Compute changes done since last commit.
382 557
        if ($document === null) {
383 551
            $this->computeChangeSets();
384 556
        } elseif (is_object($document)) {
385 12
            $this->computeSingleDocumentChangeSet($document);
386 12
        } elseif (is_array($document)) {
387 1
            foreach ($document as $object) {
388 1
                $this->computeSingleDocumentChangeSet($object);
389 1
            }
390 1
        }
391
392 555
        if ( ! ($this->documentInsertions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentInsertions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
393 238
            $this->documentUpserts ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentUpserts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
394 201
            $this->documentDeletions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentDeletions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
395 191
            $this->documentUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
396 23
            $this->collectionUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->collectionUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
397 23
            $this->collectionDeletions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->collectionDeletions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
398 23
            $this->orphanRemovals)
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
399 555
        ) {
400 23
            return; // Nothing to do.
401
        }
402
403 552
        if ($this->orphanRemovals) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
404 45
            foreach ($this->orphanRemovals as $removal) {
405 45
                $this->remove($removal);
406 45
            }
407 45
        }
408
409
        // Raise onFlush
410 552
        if ($this->evm->hasListeners(Events::onFlush)) {
411 7
            $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));
412 7
        }
413
414 552
        foreach ($this->getClassesForCommitAction($this->documentUpserts) as $classAndDocuments) {
415 78
            list($class, $documents) = $classAndDocuments;
416 78
            $this->executeUpserts($class, $documents, $options);
417 552
        }
418
419 552
        foreach ($this->getClassesForCommitAction($this->documentInsertions) as $classAndDocuments) {
420 485
            list($class, $documents) = $classAndDocuments;
421 485
            $this->executeInserts($class, $documents, $options);
422 551
        }
423
424 551
        foreach ($this->getClassesForCommitAction($this->documentUpdates) as $classAndDocuments) {
425 217
            list($class, $documents) = $classAndDocuments;
426 217
            $this->executeUpdates($class, $documents, $options);
427 551
        }
428
429 551
        foreach ($this->getClassesForCommitAction($this->documentDeletions, true) as $classAndDocuments) {
430 62
            list($class, $documents) = $classAndDocuments;
431 62
            $this->executeDeletions($class, $documents, $options);
432 551
        }
433
434
        // Raise postFlush
435 551
        if ($this->evm->hasListeners(Events::postFlush)) {
436
            $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm));
437
        }
438
439
        // Clear up
440 551
        $this->documentInsertions =
441 551
        $this->documentUpserts =
442 551
        $this->documentUpdates =
443 551
        $this->documentDeletions =
444 551
        $this->documentChangeSets =
445 551
        $this->collectionUpdates =
446 551
        $this->collectionDeletions =
447 551
        $this->visitedCollections =
448 551
        $this->scheduledForDirtyCheck =
449 551
        $this->orphanRemovals = 
450 551
        $this->hasScheduledCollections = array();
451 551
    }
452
453
    /**
454
     * Groups a list of scheduled documents by their class.
455
     *
456
     * @param array $documents Scheduled documents (e.g. $this->documentInsertions)
457
     * @param bool $includeEmbedded
458
     * @return array Tuples of ClassMetadata and a corresponding array of objects
459
     */
460 552
    private function getClassesForCommitAction($documents, $includeEmbedded = false)
461
    {
462 552
        if (empty($documents)) {
463 552
            return array();
464
        }
465 551
        $divided = array();
466 551
        $embeds = array();
467 551
        foreach ($documents as $oid => $d) {
468 551
            $className = get_class($d);
469 551
            if (isset($embeds[$className])) {
470 68
                continue;
471
            }
472 551
            if (isset($divided[$className])) {
473 135
                $divided[$className][1][$oid] = $d;
474 135
                continue;
475
            }
476 551
            $class = $this->dm->getClassMetadata($className);
477 551
            if ($class->isEmbeddedDocument && ! $includeEmbedded) {
478 165
                $embeds[$className] = true;
479 165
                continue;
480
            }
481 551
            if (empty($divided[$class->name])) {
482 551
                $divided[$class->name] = array($class, array($oid => $d));
483 551
            } else {
484 4
                $divided[$class->name][1][$oid] = $d;
485
            }
486 551
        }
487 551
        return $divided;
488
    }
489
490
    /**
491
     * Compute changesets of all documents scheduled for insertion.
492
     *
493
     * Embedded documents will not be processed.
494
     */
495 559 View Code Duplication
    private function computeScheduleInsertsChangeSets()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

Available Fixes

  1. Adding an additional type check:

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

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

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

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

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

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

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

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

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

Available Fixes

  1. Adding an additional type check:

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

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

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

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

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

    return false;
}

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

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

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1016 1
            return;
1017
        }
1018
1019 19
        $oid = spl_object_hash($document);
1020
1021 19
        if ( ! isset($this->documentStates[$oid]) || $this->documentStates[$oid] != self::STATE_MANAGED) {
1022
            throw new \InvalidArgumentException('Document must be managed.');
1023
        }
1024
1025 19
        if ( ! $class->isInheritanceTypeNone()) {
1026 2
            $class = $this->dm->getClassMetadata(get_class($document));
1027 2
        }
1028
1029 19
        $this->computeOrRecomputeChangeSet($class, $document, true);
1030 19
    }
1031
1032
    /**
1033
     * @param ClassMetadata $class
1034
     * @param object $document
1035
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1036
     */
1037 574
    private function persistNew(ClassMetadata $class, $document)
1038
    {
1039 574
        $oid = spl_object_hash($document);
1040 574 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...
1041 156
            $class->invokeLifecycleCallbacks(Events::prePersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1042 156
        }
1043 574 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...
1044 6
            $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($document, $this->dm));
1045 6
        }
1046
1047 574
        $upsert = false;
1048 574
        if ($class->identifier) {
1049 574
            $idValue = $class->getIdentifierValue($document);
1050 574
            $upsert = ! $class->isEmbeddedDocument && $idValue !== null;
1051
1052 574
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1053 3
                throw new \InvalidArgumentException(sprintf(
1054 3
                    "%s uses NONE identifier generation strategy but no identifier was provided when persisting.",
1055 3
                    get_class($document)
1056 3
                ));
1057
            }
1058
1059 573
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_AUTO && $idValue !== null && ! \MongoId::isValid($idValue)) {
1060 1
                throw new \InvalidArgumentException(sprintf(
1061 1
                    "%s uses AUTO identifier generation strategy but provided identifier is not valid MongoId.",
1062 1
                    get_class($document)
1063 1
                ));
1064
            }
1065
1066 572
            if ($class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1067 501
                $idValue = $class->idGenerator->generate($this->dm, $document);
1068 501
                $idValue = $class->getPHPIdentifierValue($class->getDatabaseIdentifierValue($idValue));
1069 501
                $class->setIdentifierValue($document, $idValue);
1070 501
            }
1071
1072 572
            $this->documentIdentifiers[$oid] = $idValue;
1073 572
        } else {
1074
            // this is for embedded documents without identifiers
1075 142
            $this->documentIdentifiers[$oid] = $oid;
1076
        }
1077
1078 572
        $this->documentStates[$oid] = self::STATE_MANAGED;
1079
1080 572
        if ($upsert) {
1081 81
            $this->scheduleForUpsert($class, $document);
1082 81
        } else {
1083 506
            $this->scheduleForInsert($class, $document);
1084
        }
1085 572
    }
1086
1087
    /**
1088
     * Cascades the postPersist events to embedded documents.
1089
     *
1090
     * @param ClassMetadata $class
1091
     * @param object $document
1092
     */
1093 550
    private function cascadePostPersist(ClassMetadata $class, $document)
1094
    {
1095 550
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1096
1097 550
        $embeddedMappings = array_filter(
1098 550
            $class->associationMappings,
1099
            function($assoc) { return ! empty($assoc['embedded']); }
1100 550
        );
1101
1102 550
        foreach ($embeddedMappings as $mapping) {
1103 334
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1104
1105 334
            if ($value === null) {
1106 213
                continue;
1107
            }
1108
1109 316
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1110
1111 316
            if (isset($mapping['targetDocument'])) {
1112 305
                $embeddedClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1113 305
            }
1114
1115 316
            foreach ($values as $embeddedDocument) {
1116 159
                if ( ! isset($mapping['targetDocument'])) {
1117 13
                    $embeddedClass = $this->dm->getClassMetadata(get_class($embeddedDocument));
1118 13
                }
1119
1120 159 View Code Duplication
                if ( ! empty($embeddedClass->lifecycleCallbacks[Events::postPersist])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1121 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...
1122 9
                }
1123 159
                if ($hasPostPersistListeners) {
1124 4
                    $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($embeddedDocument, $this->dm));
1125 4
                }
1126 159
                $this->cascadePostPersist($embeddedClass, $embeddedDocument);
1127 316
            }
1128 550
         }
1129 550
     }
1130
1131
    /**
1132
     * Executes all document insertions for documents of the specified type.
1133
     *
1134
     * @param ClassMetadata $class
1135
     * @param array $documents Array of documents to insert
1136
     * @param array $options Array of options to be used with batchInsert()
1137
     */
1138 485 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...
1139
    {
1140 485
        $persister = $this->getDocumentPersister($class->name);
1141
1142 485
        foreach ($documents as $oid => $document) {
1143 485
            $persister->addInsert($document);
1144 485
            unset($this->documentInsertions[$oid]);
1145 485
        }
1146
1147 485
        $persister->executeInserts($options);
1148
1149 484
        $hasPostPersistLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postPersist]);
1150 484
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1151
1152 484
        foreach ($documents as $document) {
1153 484
            if ($hasPostPersistLifecycleCallbacks) {
1154 10
                $class->invokeLifecycleCallbacks(Events::postPersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1155 10
            }
1156 484
            if ($hasPostPersistListeners) {
1157 5
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1158 5
            }
1159 484
            $this->cascadePostPersist($class, $document);
1160 484
        }
1161 484
    }
1162
1163
    /**
1164
     * Executes all document upserts for documents of the specified type.
1165
     *
1166
     * @param ClassMetadata $class
1167
     * @param array $documents Array of documents to upsert
1168
     * @param array $options Array of options to be used with batchInsert()
1169
     */
1170 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...
1171
    {
1172 78
        $persister = $this->getDocumentPersister($class->name);
1173
1174
1175 78
        foreach ($documents as $oid => $document) {
1176 78
            $persister->addUpsert($document);
1177 78
            unset($this->documentUpserts[$oid]);
1178 78
        }
1179
1180 78
        $persister->executeUpserts($options);
1181
1182 78
        $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]);
1183 78
        $hasListeners = $this->evm->hasListeners(Events::postPersist);
1184
1185 78
        foreach ($documents as $document) {
1186 78
            if ($hasLifecycleCallbacks) {
1187
                $class->invokeLifecycleCallbacks(Events::postPersist, $document, array(new LifecycleEventArgs($document, $this->dm)));
1188
            }
1189 78
            if ($hasListeners) {
1190 2
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1191 2
            }
1192 78
            $this->cascadePostPersist($class, $document);
1193 78
        }
1194 78
    }
1195
1196
    /**
1197
     * Executes all document updates for documents of the specified type.
1198
     *
1199
     * @param Mapping\ClassMetadata $class
1200
     * @param array $documents Array of documents to update
1201
     * @param array $options Array of options to be used with update()
1202
     */
1203 217
    private function executeUpdates(ClassMetadata $class, array $documents, array $options = array())
1204
    {
1205 217
        $className = $class->name;
1206 217
        $persister = $this->getDocumentPersister($className);
1207
1208 217
        $hasPreUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::preUpdate]);
1209 217
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1210 217
        $hasPostUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postUpdate]);
1211 217
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1212
1213 217
        foreach ($documents as $oid => $document) {
1214 217
            if ( ! isset($this->documentChangeSets[$oid])) {
1215
                // only ReferenceMany collection is scheduled for update
1216 60
                $this->documentChangeSets[$oid] = array();
1217 60
            }
1218 217 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...
1219 12
                $class->invokeLifecycleCallbacks(Events::preUpdate, $document, array(
1220 12
                    new Event\PreUpdateEventArgs($document, $this->dm, $this->documentChangeSets[$oid])
1221 12
                ));
1222 12
                $this->recomputeSingleDocumentChangeSet($class, $document);
1223 12
            }
1224
1225 217 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...
1226 8
                $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1227 8
                    $document, $this->dm, $this->documentChangeSets[$oid])
1228 8
                );
1229 8
            }
1230 217
            $this->cascadePreUpdate($class, $document);
1231
1232 217
            if ( ! empty($this->documentChangeSets[$oid]) || $this->hasScheduledCollections($document)) {
1233 215
                $persister->update($document, $options);
1234 211
            }
1235
1236 213
            unset($this->documentUpdates[$oid]);
1237
1238 213
            if ($hasPostUpdateLifecycleCallbacks) {
1239 7
                $class->invokeLifecycleCallbacks(Events::postUpdate, $document, array(new LifecycleEventArgs($document, $this->dm)));
1240 7
            }
1241 213
            if ($hasPostUpdateListeners) {
1242 8
                $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($document, $this->dm));
1243 8
            }
1244 213
            $this->cascadePostUpdate($class, $document);
1245 213
        }
1246 212
    }
1247
1248
    /**
1249
     * Cascades the preUpdate event to embedded documents.
1250
     *
1251
     * @param ClassMetadata $class
1252
     * @param object $document
1253
     */
1254 217
    private function cascadePreUpdate(ClassMetadata $class, $document)
1255
    {
1256 217
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1257
1258 217
        $embeddedMappings = array_filter(
1259 217
            $class->associationMappings,
1260
            function ($assoc) { return ! empty($assoc['embedded']); }
1261 217
        );
1262
1263 217
        foreach ($embeddedMappings as $mapping) {
1264 133
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1265
1266 133
            if ($value === null) {
1267 49
                continue;
1268
            }
1269
1270 131
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1271
1272 131
            foreach ($values as $entry) {
1273 84
                $entryOid = spl_object_hash($entry);
1274 84
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1275
1276 84
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1277 47
                    continue;
1278
                }
1279
1280 67
                if (isset($this->documentInsertions[$entryOid])) {
1281 52
                    continue;
1282
                }
1283
1284 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...
1285 5
                    $entryClass->invokeLifecycleCallbacks(Events::preUpdate, $entry, array(
1286 5
                        new Event\PreUpdateEventArgs($entry, $this->dm, $this->documentChangeSets[$entryOid])
1287 5
                    ));
1288 5
                    $this->recomputeSingleDocumentChangeSet($entryClass, $entry);
1289 5
                }
1290 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...
1291 3
                    $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1292 3
                        $entry, $this->dm, $this->documentChangeSets[$entryOid])
1293 3
                    );
1294 3
                }
1295
1296 45
                $this->cascadePreUpdate($entryClass, $entry);
1297 131
            }
1298 217
        }
1299 217
    }
1300
1301
    /**
1302
     * Cascades the postUpdate and postPersist events to embedded documents.
1303
     *
1304
     * @param ClassMetadata $class
1305
     * @param object $document
1306
     */
1307 213
    private function cascadePostUpdate(ClassMetadata $class, $document)
1308
    {
1309 213
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1310 213
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1311
1312 213
        $embeddedMappings = array_filter(
1313 213
            $class->associationMappings,
1314
            function($assoc) { return ! empty($assoc['embedded']); }
1315 213
        );
1316
1317 213
        foreach ($embeddedMappings as $mapping) {
1318 129
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1319
1320 129
            if ($value === null) {
1321 52
                continue;
1322
            }
1323
1324 127
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1325
1326 127
            foreach ($values as $entry) {
1327 84
                $entryOid = spl_object_hash($entry);
1328 84
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1329
1330 84
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1331 47
                    continue;
1332
                }
1333
1334 67
                if (isset($this->documentInsertions[$entryOid])) {
1335 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...
1336 1
                        $entryClass->invokeLifecycleCallbacks(Events::postPersist, $entry, array(
1337 1
                            new LifecycleEventArgs($entry, $this->dm)
1338 1
                        ));
1339 1
                    }
1340 52
                    if ($hasPostPersistListeners) {
1341 3
                        $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entry, $this->dm));
1342 3
                    }
1343 52
                } else {
1344 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...
1345 9
                        $entryClass->invokeLifecycleCallbacks(Events::postUpdate, $entry, array(
1346 9
                            new LifecycleEventArgs($entry, $this->dm)
1347 9
                        ));
1348 9
                    }
1349 45
                    if ($hasPostUpdateListeners) {
1350 3
                        $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entry, $this->dm));
1351 3
                    }
1352
                }
1353
1354 67
                $this->cascadePostUpdate($entryClass, $entry);
1355 127
            }
1356 213
        }
1357 213
    }
1358
1359
    /**
1360
     * Executes all document deletions for documents of the specified type.
1361
     *
1362
     * @param ClassMetadata $class
1363
     * @param array $documents Array of documents to delete
1364
     * @param array $options Array of options to be used with remove()
1365
     */
1366 62
    private function executeDeletions(ClassMetadata $class, array $documents, array $options = array())
1367
    {
1368 62
        $hasPostRemoveLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postRemove]);
1369 62
        $hasPostRemoveListeners = $this->evm->hasListeners(Events::postRemove);
1370
1371 62
        $persister = $this->getDocumentPersister($class->name);
1372
1373 62
        foreach ($documents as $oid => $document) {
1374 62
            if ( ! $class->isEmbeddedDocument) {
1375 28
                $persister->delete($document, $options);
1376 26
            }
1377
            unset(
1378 60
                $this->documentDeletions[$oid],
1379 60
                $this->documentIdentifiers[$oid],
1380 60
                $this->originalDocumentData[$oid]
1381
            );
1382
1383
            // Clear snapshot information for any referenced PersistentCollection
1384
            // http://www.doctrine-project.org/jira/browse/MODM-95
1385 60
            foreach ($class->associationMappings as $fieldMapping) {
1386 41
                if (isset($fieldMapping['type']) && $fieldMapping['type'] === ClassMetadata::MANY) {
1387 26
                    $value = $class->reflFields[$fieldMapping['fieldName']]->getValue($document);
1388 26
                    if ($value instanceof PersistentCollection) {
1389 22
                        $value->clearSnapshot();
1390 22
                    }
1391 26
                }
1392 60
            }
1393
1394
            // Document with this $oid after deletion treated as NEW, even if the $oid
1395
            // is obtained by a new document because the old one went out of scope.
1396 60
            $this->documentStates[$oid] = self::STATE_NEW;
1397
1398 60
            if ($hasPostRemoveLifecycleCallbacks) {
1399 8
                $class->invokeLifecycleCallbacks(Events::postRemove, $document, array(new LifecycleEventArgs($document, $this->dm)));
1400 8
            }
1401 60
            if ($hasPostRemoveListeners) {
1402 2
                $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($document, $this->dm));
1403 2
            }
1404 60
        }
1405 60
    }
1406
1407
    /**
1408
     * Schedules a document for insertion into the database.
1409
     * If the document already has an identifier, it will be added to the
1410
     * identity map.
1411
     *
1412
     * @param ClassMetadata $class
1413
     * @param object $document The document to schedule for insertion.
1414
     * @throws \InvalidArgumentException
1415
     */
1416 509
    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...
1417
    {
1418 509
        $oid = spl_object_hash($document);
1419
1420 509
        if (isset($this->documentUpdates[$oid])) {
1421
            throw new \InvalidArgumentException("Dirty document can not be scheduled for insertion.");
1422
        }
1423 509
        if (isset($this->documentDeletions[$oid])) {
1424
            throw new \InvalidArgumentException("Removed document can not be scheduled for insertion.");
1425
        }
1426 509
        if (isset($this->documentInsertions[$oid])) {
1427
            throw new \InvalidArgumentException("Document can not be scheduled for insertion twice.");
1428
        }
1429
1430 509
        $this->documentInsertions[$oid] = $document;
1431
1432 509
        if (isset($this->documentIdentifiers[$oid])) {
1433 506
            $this->addToIdentityMap($document);
1434 506
        }
1435 509
    }
1436
1437
    /**
1438
     * Schedules a document for upsert into the database and adds it to the
1439
     * identity map
1440
     *
1441
     * @param ClassMetadata $class
1442
     * @param object $document The document to schedule for upsert.
1443
     * @throws \InvalidArgumentException
1444
     */
1445 84
    public function scheduleForUpsert(ClassMetadata $class, $document)
1446
    {
1447 84
        $oid = spl_object_hash($document);
1448
1449 84
        if ($class->isEmbeddedDocument) {
1450
            throw new \InvalidArgumentException("Embedded document can not be scheduled for upsert.");
1451
        }
1452 84
        if (isset($this->documentUpdates[$oid])) {
1453
            throw new \InvalidArgumentException("Dirty document can not be scheduled for upsert.");
1454
        }
1455 84
        if (isset($this->documentDeletions[$oid])) {
1456
            throw new \InvalidArgumentException("Removed document can not be scheduled for upsert.");
1457
        }
1458 84
        if (isset($this->documentUpserts[$oid])) {
1459
            throw new \InvalidArgumentException("Document can not be scheduled for upsert twice.");
1460
        }
1461
1462 84
        $this->documentUpserts[$oid] = $document;
1463 84
        $this->documentIdentifiers[$oid] = $class->getIdentifierValue($document);
1464 84
        $this->addToIdentityMap($document);
1465 84
    }
1466
1467
    /**
1468
     * Checks whether a document is scheduled for insertion.
1469
     *
1470
     * @param object $document
1471
     * @return boolean
1472
     */
1473 70
    public function isScheduledForInsert($document)
1474
    {
1475 70
        return isset($this->documentInsertions[spl_object_hash($document)]);
1476
    }
1477
1478
    /**
1479
     * Checks whether a document is scheduled for upsert.
1480
     *
1481
     * @param object $document
1482
     * @return boolean
1483
     */
1484 5
    public function isScheduledForUpsert($document)
1485
    {
1486 5
        return isset($this->documentUpserts[spl_object_hash($document)]);
1487
    }
1488
1489
    /**
1490
     * Schedules a document for being updated.
1491
     *
1492
     * @param object $document The document to schedule for being updated.
1493
     * @throws \InvalidArgumentException
1494
     */
1495 226
    public function scheduleForUpdate($document)
1496
    {
1497 226
        $oid = spl_object_hash($document);
1498 226
        if ( ! isset($this->documentIdentifiers[$oid])) {
1499
            throw new \InvalidArgumentException("Document has no identity.");
1500
        }
1501
1502 226
        if (isset($this->documentDeletions[$oid])) {
1503
            throw new \InvalidArgumentException("Document is removed.");
1504
        }
1505
1506 226
        if ( ! isset($this->documentUpdates[$oid])
1507 226
            && ! isset($this->documentInsertions[$oid])
1508 226
            && ! isset($this->documentUpserts[$oid])) {
1509 222
            $this->documentUpdates[$oid] = $document;
1510 222
        }
1511 226
    }
1512
1513
    /**
1514
     * Checks whether a document is registered as dirty in the unit of work.
1515
     * Note: Is not very useful currently as dirty documents are only registered
1516
     * at commit time.
1517
     *
1518
     * @param object $document
1519
     * @return boolean
1520
     */
1521 13
    public function isScheduledForUpdate($document)
1522
    {
1523 13
        return isset($this->documentUpdates[spl_object_hash($document)]);
1524
    }
1525
1526 1
    public function isScheduledForDirtyCheck($document)
1527
    {
1528 1
        $class = $this->dm->getClassMetadata(get_class($document));
1529 1
        return isset($this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)]);
1530
    }
1531
1532
    /**
1533
     * INTERNAL:
1534
     * Schedules a document for deletion.
1535
     *
1536
     * @param object $document
1537
     */
1538 67
    public function scheduleForDelete($document)
1539
    {
1540 67
        $oid = spl_object_hash($document);
1541
1542 67
        if (isset($this->documentInsertions[$oid])) {
1543 2
            if ($this->isInIdentityMap($document)) {
1544 2
                $this->removeFromIdentityMap($document);
1545 2
            }
1546 2
            unset($this->documentInsertions[$oid]);
1547 2
            return; // document has not been persisted yet, so nothing more to do.
1548
        }
1549
1550 66
        if ( ! $this->isInIdentityMap($document)) {
1551 1
            return; // ignore
1552
        }
1553
1554 65
        $this->removeFromIdentityMap($document);
1555 65
        $this->documentStates[$oid] = self::STATE_REMOVED;
1556
1557 65
        if (isset($this->documentUpdates[$oid])) {
1558
            unset($this->documentUpdates[$oid]);
1559
        }
1560 65
        if ( ! isset($this->documentDeletions[$oid])) {
1561 65
            $this->documentDeletions[$oid] = $document;
1562 65
        }
1563 65
    }
1564
1565
    /**
1566
     * Checks whether a document is registered as removed/deleted with the unit
1567
     * of work.
1568
     *
1569
     * @param object $document
1570
     * @return boolean
1571
     */
1572 8
    public function isScheduledForDelete($document)
1573
    {
1574 8
        return isset($this->documentDeletions[spl_object_hash($document)]);
1575
    }
1576
1577
    /**
1578
     * Checks whether a document is scheduled for insertion, update or deletion.
1579
     *
1580
     * @param $document
1581
     * @return boolean
1582
     */
1583 224
    public function isDocumentScheduled($document)
1584
    {
1585 224
        $oid = spl_object_hash($document);
1586 224
        return isset($this->documentInsertions[$oid]) ||
1587 121
            isset($this->documentUpserts[$oid]) ||
1588 112
            isset($this->documentUpdates[$oid]) ||
1589 224
            isset($this->documentDeletions[$oid]);
1590
    }
1591
1592
    /**
1593
     * INTERNAL:
1594
     * Registers a document in the identity map.
1595
     *
1596
     * Note that documents in a hierarchy are registered with the class name of
1597
     * the root document. Identifiers are serialized before being used as array
1598
     * keys to allow differentiation of equal, but not identical, values.
1599
     *
1600
     * @ignore
1601
     * @param object $document  The document to register.
1602
     * @return boolean  TRUE if the registration was successful, FALSE if the identity of
1603
     *                  the document in question is already managed.
1604
     */
1605 601
    public function addToIdentityMap($document)
1606
    {
1607 601
        $class = $this->dm->getClassMetadata(get_class($document));
1608 601
        $id = $this->getIdForIdentityMap($document);
1609
1610 601
        if (isset($this->identityMap[$class->name][$id])) {
1611 53
            return false;
1612
        }
1613
1614 601
        $this->identityMap[$class->name][$id] = $document;
1615
1616 601
        if ($document instanceof NotifyPropertyChanged &&
1617 601
            ( ! $document instanceof Proxy || $document->__isInitialized())) {
1618 3
            $document->addPropertyChangedListener($this);
1619 3
        }
1620
1621 601
        return true;
1622
    }
1623
1624
    /**
1625
     * Gets the state of a document with regard to the current unit of work.
1626
     *
1627
     * @param object   $document
1628
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1629
     *                         This parameter can be set to improve performance of document state detection
1630
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1631
     *                         is either known or does not matter for the caller of the method.
1632
     * @return int The document state.
1633
     */
1634 577
    public function getDocumentState($document, $assume = null)
1635
    {
1636 577
        $oid = spl_object_hash($document);
1637
1638 577
        if (isset($this->documentStates[$oid])) {
1639 353
            return $this->documentStates[$oid];
1640
        }
1641
1642 577
        $class = $this->dm->getClassMetadata(get_class($document));
1643
1644 577
        if ($class->isEmbeddedDocument) {
1645 175
            return self::STATE_NEW;
1646
        }
1647
1648 574
        if ($assume !== null) {
1649 571
            return $assume;
1650
        }
1651
1652
        /* State can only be NEW or DETACHED, because MANAGED/REMOVED states are
1653
         * known. Note that you cannot remember the NEW or DETACHED state in
1654
         * _documentStates since the UoW does not hold references to such
1655
         * objects and the object hash can be reused. More generally, because
1656
         * the state may "change" between NEW/DETACHED without the UoW being
1657
         * aware of it.
1658
         */
1659 4
        $id = $class->getIdentifierObject($document);
1660
1661 4
        if ($id === null) {
1662 2
            return self::STATE_NEW;
1663
        }
1664
1665
        // Check for a version field, if available, to avoid a DB lookup.
1666 2
        if ($class->isVersioned) {
1667
            return ($class->getFieldValue($document, $class->versionField))
1668
                ? self::STATE_DETACHED
1669
                : self::STATE_NEW;
1670
        }
1671
1672
        // Last try before DB lookup: check the identity map.
1673 2
        if ($this->tryGetById($id, $class)) {
1674 1
            return self::STATE_DETACHED;
1675
        }
1676
1677
        // DB lookup
1678 2
        if ($this->getDocumentPersister($class->name)->exists($document)) {
1679 1
            return self::STATE_DETACHED;
1680
        }
1681
1682 1
        return self::STATE_NEW;
1683
    }
1684
1685
    /**
1686
     * INTERNAL:
1687
     * Removes a document from the identity map. This effectively detaches the
1688
     * document from the persistence management of Doctrine.
1689
     *
1690
     * @ignore
1691
     * @param object $document
1692
     * @throws \InvalidArgumentException
1693
     * @return boolean
1694
     */
1695 76
    public function removeFromIdentityMap($document)
1696
    {
1697 76
        $oid = spl_object_hash($document);
1698
1699
        // Check if id is registered first
1700 76
        if ( ! isset($this->documentIdentifiers[$oid])) {
1701
            return false;
1702
        }
1703
1704 76
        $class = $this->dm->getClassMetadata(get_class($document));
1705 76
        $id = $this->getIdForIdentityMap($document);
1706
1707 76
        if (isset($this->identityMap[$class->name][$id])) {
1708 76
            unset($this->identityMap[$class->name][$id]);
1709 76
            $this->documentStates[$oid] = self::STATE_DETACHED;
1710 76
            return true;
1711
        }
1712
1713
        return false;
1714
    }
1715
1716
    /**
1717
     * INTERNAL:
1718
     * Gets a document in the identity map by its identifier hash.
1719
     *
1720
     * @ignore
1721
     * @param mixed         $id    Document identifier
1722
     * @param ClassMetadata $class Document class
1723
     * @return object
1724
     * @throws InvalidArgumentException if the class does not have an identifier
1725
     */
1726 31
    public function getById($id, ClassMetadata $class)
1727
    {
1728 31
        if ( ! $class->identifier) {
1729
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1730
        }
1731
1732 31
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1733
1734 31
        return $this->identityMap[$class->name][$serializedId];
1735
    }
1736
1737
    /**
1738
     * INTERNAL:
1739
     * Tries to get a document by its identifier hash. If no document is found
1740
     * for the given hash, FALSE is returned.
1741
     *
1742
     * @ignore
1743
     * @param mixed         $id    Document identifier
1744
     * @param ClassMetadata $class Document class
1745
     * @return mixed The found document or FALSE.
1746
     * @throws InvalidArgumentException if the class does not have an identifier
1747
     */
1748 290
    public function tryGetById($id, ClassMetadata $class)
1749
    {
1750 290
        if ( ! $class->identifier) {
1751
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1752
        }
1753
1754 290
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1755
1756 290
        return isset($this->identityMap[$class->name][$serializedId]) ?
1757 290
            $this->identityMap[$class->name][$serializedId] : false;
1758
    }
1759
1760
    /**
1761
     * Schedules a document for dirty-checking at commit-time.
1762
     *
1763
     * @param object $document The document to schedule for dirty-checking.
1764
     * @todo Rename: scheduleForSynchronization
1765
     */
1766 2
    public function scheduleForDirtyCheck($document)
1767
    {
1768 2
        $class = $this->dm->getClassMetadata(get_class($document));
1769 2
        $this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)] = $document;
1770 2
    }
1771
1772
    /**
1773
     * Checks whether a document is registered in the identity map.
1774
     *
1775
     * @param object $document
1776
     * @return boolean
1777
     */
1778 76
    public function isInIdentityMap($document)
1779
    {
1780 76
        $oid = spl_object_hash($document);
1781
1782 76
        if ( ! isset($this->documentIdentifiers[$oid])) {
1783 4
            return false;
1784
        }
1785
1786 75
        $class = $this->dm->getClassMetadata(get_class($document));
1787 75
        $id = $this->getIdForIdentityMap($document);
1788
1789 75
        return isset($this->identityMap[$class->name][$id]);
1790
    }
1791
1792
    /**
1793
     * @param object $document
1794
     * @return string
1795
     */
1796 601
    private function getIdForIdentityMap($document)
1797
    {
1798 601
        $class = $this->dm->getClassMetadata(get_class($document));
1799
1800 601
        if ( ! $class->identifier) {
1801 145
            $id = spl_object_hash($document);
1802 145
        } else {
1803 600
            $id = $this->documentIdentifiers[spl_object_hash($document)];
1804 600
            $id = serialize($class->getDatabaseIdentifierValue($id));
1805
        }
1806
1807 601
        return $id;
1808
    }
1809
1810
    /**
1811
     * INTERNAL:
1812
     * Checks whether an identifier exists in the identity map.
1813
     *
1814
     * @ignore
1815
     * @param string $id
1816
     * @param string $rootClassName
1817
     * @return boolean
1818
     */
1819
    public function containsId($id, $rootClassName)
1820
    {
1821
        return isset($this->identityMap[$rootClassName][serialize($id)]);
1822
    }
1823
1824
    /**
1825
     * Persists a document as part of the current unit of work.
1826
     *
1827
     * @param object $document The document to persist.
1828
     * @throws MongoDBException If trying to persist MappedSuperclass.
1829
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1830
     */
1831 572
    public function persist($document)
1832
    {
1833 572
        $class = $this->dm->getClassMetadata(get_class($document));
1834 572
        if ($class->isMappedSuperclass) {
1835 1
            throw MongoDBException::cannotPersistMappedSuperclass($class->name);
1836
        }
1837 571
        $visited = array();
1838 571
        $this->doPersist($document, $visited);
1839 567
    }
1840
1841
    /**
1842
     * Saves a document as part of the current unit of work.
1843
     * This method is internally called during save() cascades as it tracks
1844
     * the already visited documents to prevent infinite recursions.
1845
     *
1846
     * NOTE: This method always considers documents that are not yet known to
1847
     * this UnitOfWork as NEW.
1848
     *
1849
     * @param object $document The document to persist.
1850
     * @param array $visited The already visited documents.
1851
     * @throws \InvalidArgumentException
1852
     * @throws MongoDBException
1853
     */
1854 571
    private function doPersist($document, array &$visited)
1855
    {
1856 571
        $oid = spl_object_hash($document);
1857 571
        if (isset($visited[$oid])) {
1858 24
            return; // Prevent infinite recursion
1859
        }
1860
1861 571
        $visited[$oid] = $document; // Mark visited
1862
1863 571
        $class = $this->dm->getClassMetadata(get_class($document));
1864
1865 571
        $documentState = $this->getDocumentState($document, self::STATE_NEW);
1866
        switch ($documentState) {
1867 571
            case self::STATE_MANAGED:
1868
                // Nothing to do, except if policy is "deferred explicit"
1869 44
                if ($class->isChangeTrackingDeferredExplicit()) {
1870
                    $this->scheduleForDirtyCheck($document);
1871
                }
1872 44
                break;
1873 571
            case self::STATE_NEW:
1874 571
                $this->persistNew($class, $document);
1875 569
                break;
1876
1877 2
            case self::STATE_REMOVED:
1878
                // Document becomes managed again
1879 2
                unset($this->documentDeletions[$oid]);
1880
1881 2
                $this->documentStates[$oid] = self::STATE_MANAGED;
1882 2
                break;
1883
1884
            case self::STATE_DETACHED:
1885
                throw new \InvalidArgumentException(
1886
                    "Behavior of persist() for a detached document is not yet defined.");
1887
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

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

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

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

    return false;
}

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

Loading history...
1888
1889
            default:
1890
                throw MongoDBException::invalidDocumentState($documentState);
1891
        }
1892
1893 569
        $this->cascadePersist($document, $visited);
1894 567
    }
1895
1896
    /**
1897
     * Deletes a document as part of the current unit of work.
1898
     *
1899
     * @param object $document The document to remove.
1900
     */
1901 66
    public function remove($document)
1902
    {
1903 66
        $visited = array();
1904 66
        $this->doRemove($document, $visited);
1905 66
    }
1906
1907
    /**
1908
     * Deletes a document as part of the current unit of work.
1909
     *
1910
     * This method is internally called during delete() cascades as it tracks
1911
     * the already visited documents to prevent infinite recursions.
1912
     *
1913
     * @param object $document The document to delete.
1914
     * @param array $visited The map of the already visited documents.
1915
     * @throws MongoDBException
1916
     */
1917 66
    private function doRemove($document, array &$visited)
1918
    {
1919 66
        $oid = spl_object_hash($document);
1920 66
        if (isset($visited[$oid])) {
1921 1
            return; // Prevent infinite recursion
1922
        }
1923
1924 66
        $visited[$oid] = $document; // mark visited
1925
1926
        /* Cascade first, because scheduleForDelete() removes the entity from
1927
         * the identity map, which can cause problems when a lazy Proxy has to
1928
         * be initialized for the cascade operation.
1929
         */
1930 66
        $this->cascadeRemove($document, $visited);
1931
1932 66
        $class = $this->dm->getClassMetadata(get_class($document));
1933 66
        $documentState = $this->getDocumentState($document);
1934
        switch ($documentState) {
1935 66
            case self::STATE_NEW:
1936 66
            case self::STATE_REMOVED:
1937
                // nothing to do
1938 1
                break;
1939 66
            case self::STATE_MANAGED:
1940 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...
1941 8
                    $class->invokeLifecycleCallbacks(Events::preRemove, $document, array(new LifecycleEventArgs($document, $this->dm)));
1942 8
                }
1943 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...
1944 1
                    $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($document, $this->dm));
1945 1
                }
1946 66
                $this->scheduleForDelete($document);
1947 66
                break;
1948
            case self::STATE_DETACHED:
1949
                throw MongoDBException::detachedDocumentCannotBeRemoved();
1950
            default:
1951
                throw MongoDBException::invalidDocumentState($documentState);
1952
        }
1953 66
    }
1954
1955
    /**
1956
     * Merges the state of the given detached document into this UnitOfWork.
1957
     *
1958
     * @param object $document
1959
     * @return object The managed copy of the document.
1960
     */
1961 13
    public function merge($document)
1962
    {
1963 13
        $visited = array();
1964
1965 13
        return $this->doMerge($document, $visited);
1966
    }
1967
1968
    /**
1969
     * Executes a merge operation on a document.
1970
     *
1971
     * @param object      $document
1972
     * @param array       $visited
1973
     * @param object|null $prevManagedCopy
1974
     * @param array|null  $assoc
1975
     *
1976
     * @return object The managed copy of the document.
1977
     *
1978
     * @throws InvalidArgumentException If the entity instance is NEW.
1979
     * @throws LockException If the document uses optimistic locking through a
1980
     *                       version attribute and the version check against the
1981
     *                       managed copy fails.
1982
     */
1983 13
    private function doMerge($document, array &$visited, $prevManagedCopy = null, $assoc = null)
1984
    {
1985 13
        $oid = spl_object_hash($document);
1986
1987 13
        if (isset($visited[$oid])) {
1988 1
            return $visited[$oid]; // Prevent infinite recursion
1989
        }
1990
1991 13
        $visited[$oid] = $document; // mark visited
1992
1993 13
        $class = $this->dm->getClassMetadata(get_class($document));
1994
1995
        /* First we assume DETACHED, although it can still be NEW but we can
1996
         * avoid an extra DB round trip this way. If it is not MANAGED but has
1997
         * an identity, we need to fetch it from the DB anyway in order to
1998
         * merge. MANAGED documents are ignored by the merge operation.
1999
         */
2000 13
        $managedCopy = $document;
2001
2002 13
        if ($this->getDocumentState($document, self::STATE_DETACHED) !== self::STATE_MANAGED) {
2003 13
            if ($document instanceof Proxy && ! $document->__isInitialized()) {
2004
                $document->__load();
2005
            }
2006
2007
            // Try to look the document up in the identity map.
2008 13
            $id = $class->isEmbeddedDocument ? null : $class->getIdentifierObject($document);
2009
2010 13
            if ($id === null) {
2011
                // If there is no identifier, it is actually NEW.
2012 5
                $managedCopy = $class->newInstance();
2013 5
                $this->persistNew($class, $managedCopy);
2014 5
            } else {
2015 10
                $managedCopy = $this->tryGetById($id, $class);
2016
2017 10
                if ($managedCopy) {
2018
                    // We have the document in memory already, just make sure it is not removed.
2019 5
                    if ($this->getDocumentState($managedCopy) === self::STATE_REMOVED) {
2020
                        throw new \InvalidArgumentException('Removed entity detected during merge. Cannot merge with a removed entity.');
2021
                    }
2022 5
                } else {
2023
                    // We need to fetch the managed copy in order to merge.
2024 7
                    $managedCopy = $this->dm->find($class->name, $id);
2025
                }
2026
2027 10
                if ($managedCopy === null) {
2028
                    // If the identifier is ASSIGNED, it is NEW
2029
                    $managedCopy = $class->newInstance();
2030
                    $class->setIdentifierValue($managedCopy, $id);
2031
                    $this->persistNew($class, $managedCopy);
2032
                } else {
2033 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...
2034
                        $managedCopy->__load();
2035
                    }
2036
                }
2037
            }
2038
2039 13
            if ($class->isVersioned) {
2040
                $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
2041
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2042
2043
                // Throw exception if versions don't match
2044
                if ($managedCopyVersion != $documentVersion) {
2045
                    throw LockException::lockFailedVersionMissmatch($document, $documentVersion, $managedCopyVersion);
2046
                }
2047
            }
2048
2049
            // Merge state of $document into existing (managed) document
2050 13
            foreach ($class->reflClass->getProperties() as $prop) {
2051 13
                $name = $prop->name;
2052 13
                $prop->setAccessible(true);
2053 13
                if ( ! isset($class->associationMappings[$name])) {
2054 13
                    if ( ! $class->isIdentifier($name)) {
2055 13
                        $prop->setValue($managedCopy, $prop->getValue($document));
2056 13
                    }
2057 13
                } else {
2058 13
                    $assoc2 = $class->associationMappings[$name];
2059
2060 13
                    if ($assoc2['type'] === 'one') {
2061 5
                        $other = $prop->getValue($document);
2062
2063 5
                        if ($other === null) {
2064 2
                            $prop->setValue($managedCopy, null);
2065 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...
2066
                            // Do not merge fields marked lazy that have not been fetched
2067 1
                            continue;
2068 3
                        } elseif ( ! $assoc2['isCascadeMerge']) {
2069
                            if ($this->getDocumentState($other) === self::STATE_DETACHED) {
2070
                                $targetDocument = isset($assoc2['targetDocument']) ? $assoc2['targetDocument'] : get_class($other);
2071
                                /* @var $targetClass \Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo */
2072
                                $targetClass = $this->dm->getClassMetadata($targetDocument);
2073
                                $relatedId = $targetClass->getIdentifierObject($other);
2074
2075
                                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...
2076
                                    $other = $this->dm->find($targetClass->name, $relatedId);
2077
                                } else {
2078
                                    $other = $this
2079
                                        ->dm
2080
                                        ->getProxyFactory()
2081
                                        ->getProxy($assoc2['targetDocument'], array($targetClass->identifier => $relatedId));
2082
                                    $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...
2083
                                }
2084
                            }
2085
2086
                            $prop->setValue($managedCopy, $other);
2087
                        }
2088 4
                    } else {
2089 10
                        $mergeCol = $prop->getValue($document);
2090
2091 10
                        if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) {
2092
                            /* Do not merge fields marked lazy that have not
2093
                             * been fetched. Keep the lazy persistent collection
2094
                             * of the managed copy.
2095
                             */
2096 3
                            continue;
2097
                        }
2098
2099 7
                        $managedCol = $prop->getValue($managedCopy);
2100
2101 7
                        if ( ! $managedCol) {
2102 2
                            $managedCol = new PersistentCollection(new ArrayCollection(), $this->dm, $this);
2103 2
                            $managedCol->setOwner($managedCopy, $assoc2);
2104 2
                            $prop->setValue($managedCopy, $managedCol);
2105 2
                            $this->originalDocumentData[$oid][$name] = $managedCol;
2106 2
                        }
2107
2108
                        /* Note: do not process association's target documents.
2109
                         * They will be handled during the cascade. Initialize
2110
                         * and, if necessary, clear $managedCol for now.
2111
                         */
2112 7
                        if ($assoc2['isCascadeMerge']) {
2113 7
                            $managedCol->initialize();
2114
2115
                            // If $managedCol differs from the merged collection, clear and set dirty
2116 7
                            if ( ! $managedCol->isEmpty() && $managedCol !== $mergeCol) {
2117 2
                                $managedCol->unwrap()->clear();
2118 2
                                $managedCol->setDirty(true);
2119
2120 2
                                if ($assoc2['isOwningSide'] && $class->isChangeTrackingNotify()) {
2121
                                    $this->scheduleForDirtyCheck($managedCopy);
2122
                                }
2123 2
                            }
2124 7
                        }
2125
                    }
2126
                }
2127
2128 13
                if ($class->isChangeTrackingNotify()) {
2129
                    // Just treat all properties as changed, there is no other choice.
2130
                    $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy));
2131
                }
2132 13
            }
2133
2134 13
            if ($class->isChangeTrackingDeferredExplicit()) {
2135
                $this->scheduleForDirtyCheck($document);
2136
            }
2137 13
        }
2138
2139 13
        if ($prevManagedCopy !== null) {
2140 6
            $assocField = $assoc['fieldName'];
2141 6
            $prevClass = $this->dm->getClassMetadata(get_class($prevManagedCopy));
2142
2143 6
            if ($assoc['type'] === 'one') {
2144 2
                $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
2145 2
            } else {
2146 4
                $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
2147
2148 4
                if ($assoc['type'] === 'many' && isset($assoc['mappedBy'])) {
2149 1
                    $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy);
2150 1
                }
2151
            }
2152 6
        }
2153
2154
        // Mark the managed copy visited as well
2155 13
        $visited[spl_object_hash($managedCopy)] = true;
2156
2157 13
        $this->cascadeMerge($document, $managedCopy, $visited);
2158
2159 13
        return $managedCopy;
2160
    }
2161
2162
    /**
2163
     * Detaches a document from the persistence management. It's persistence will
2164
     * no longer be managed by Doctrine.
2165
     *
2166
     * @param object $document The document to detach.
2167
     */
2168 9
    public function detach($document)
2169
    {
2170 9
        $visited = array();
2171 9
        $this->doDetach($document, $visited);
2172 9
    }
2173
2174
    /**
2175
     * Executes a detach operation on the given document.
2176
     *
2177
     * @param object $document
2178
     * @param array $visited
2179
     * @internal This method always considers documents with an assigned identifier as DETACHED.
2180
     */
2181 12
    private function doDetach($document, array &$visited)
2182
    {
2183 12
        $oid = spl_object_hash($document);
2184 12
        if (isset($visited[$oid])) {
2185 4
            return; // Prevent infinite recursion
2186
        }
2187
2188 12
        $visited[$oid] = $document; // mark visited
2189
2190 12
        switch ($this->getDocumentState($document, self::STATE_DETACHED)) {
2191 12
            case self::STATE_MANAGED:
2192 12
                $this->removeFromIdentityMap($document);
2193 12
                unset($this->documentInsertions[$oid], $this->documentUpdates[$oid],
2194 12
                    $this->documentDeletions[$oid], $this->documentIdentifiers[$oid],
2195 12
                    $this->documentStates[$oid], $this->originalDocumentData[$oid],
2196 12
                    $this->parentAssociations[$oid], $this->documentUpserts[$oid],
2197 12
                    $this->hasScheduledCollections[$oid]);
2198 12
                break;
2199 4
            case self::STATE_NEW:
2200 4
            case self::STATE_DETACHED:
2201 4
                return;
2202 12
        }
2203
2204 12
        $this->cascadeDetach($document, $visited);
2205 12
    }
2206
2207
    /**
2208
     * Refreshes the state of the given document from the database, overwriting
2209
     * any local, unpersisted changes.
2210
     *
2211
     * @param object $document The document to refresh.
2212
     * @throws \InvalidArgumentException If the document is not MANAGED.
2213
     */
2214 21
    public function refresh($document)
2215
    {
2216 21
        $visited = array();
2217 21
        $this->doRefresh($document, $visited);
2218 20
    }
2219
2220
    /**
2221
     * Executes a refresh operation on a document.
2222
     *
2223
     * @param object $document The document to refresh.
2224
     * @param array $visited The already visited documents during cascades.
2225
     * @throws \InvalidArgumentException If the document is not MANAGED.
2226
     */
2227 21
    private function doRefresh($document, array &$visited)
2228
    {
2229 21
        $oid = spl_object_hash($document);
2230 21
        if (isset($visited[$oid])) {
2231
            return; // Prevent infinite recursion
2232
        }
2233
2234 21
        $visited[$oid] = $document; // mark visited
2235
2236 21
        $class = $this->dm->getClassMetadata(get_class($document));
2237
2238 21
        if ( ! $class->isEmbeddedDocument) {
2239 21
            if ($this->getDocumentState($document) == self::STATE_MANAGED) {
2240 20
                $id = $class->getDatabaseIdentifierValue($this->documentIdentifiers[$oid]);
2241 20
                $this->getDocumentPersister($class->name)->refresh($id, $document);
2242 20
            } else {
2243 1
                throw new \InvalidArgumentException("Document is not MANAGED.");
2244
            }
2245 20
        }
2246
2247 20
        $this->cascadeRefresh($document, $visited);
2248 20
    }
2249
2250
    /**
2251
     * Cascades a refresh operation to associated documents.
2252
     *
2253
     * @param object $document
2254
     * @param array $visited
2255
     */
2256 20
    private function cascadeRefresh($document, array &$visited)
2257
    {
2258 20
        $class = $this->dm->getClassMetadata(get_class($document));
2259
2260 20
        $associationMappings = array_filter(
2261 20
            $class->associationMappings,
2262
            function ($assoc) { return $assoc['isCascadeRefresh']; }
2263 20
        );
2264
2265 20
        foreach ($associationMappings as $mapping) {
2266 15
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2267 15
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2268 15
                if ($relatedDocuments instanceof PersistentCollection) {
2269
                    // Unwrap so that foreach() does not initialize
2270 15
                    $relatedDocuments = $relatedDocuments->unwrap();
2271 15
                }
2272 15
                foreach ($relatedDocuments as $relatedDocument) {
2273
                    $this->doRefresh($relatedDocument, $visited);
2274 15
                }
2275 15
            } elseif ($relatedDocuments !== null) {
2276 2
                $this->doRefresh($relatedDocuments, $visited);
2277 2
            }
2278 20
        }
2279 20
    }
2280
2281
    /**
2282
     * Cascades a detach operation to associated documents.
2283
     *
2284
     * @param object $document
2285
     * @param array $visited
2286
     */
2287 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...
2288
    {
2289 12
        $class = $this->dm->getClassMetadata(get_class($document));
2290 12
        foreach ($class->fieldMappings as $mapping) {
2291 12
            if ( ! $mapping['isCascadeDetach']) {
2292 12
                continue;
2293
            }
2294 7
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2295 7
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2296 7
                if ($relatedDocuments instanceof PersistentCollection) {
2297
                    // Unwrap so that foreach() does not initialize
2298 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2299 6
                }
2300 7
                foreach ($relatedDocuments as $relatedDocument) {
2301 5
                    $this->doDetach($relatedDocument, $visited);
2302 7
                }
2303 7
            } elseif ($relatedDocuments !== null) {
2304 5
                $this->doDetach($relatedDocuments, $visited);
2305 5
            }
2306 12
        }
2307 12
    }
2308
    /**
2309
     * Cascades a merge operation to associated documents.
2310
     *
2311
     * @param object $document
2312
     * @param object $managedCopy
2313
     * @param array $visited
2314
     */
2315 13
    private function cascadeMerge($document, $managedCopy, array &$visited)
2316
    {
2317 13
        $class = $this->dm->getClassMetadata(get_class($document));
2318
2319 13
        $associationMappings = array_filter(
2320 13
            $class->associationMappings,
2321
            function ($assoc) { return $assoc['isCascadeMerge']; }
2322 13
        );
2323
2324 13
        foreach ($associationMappings as $assoc) {
2325 12
            $relatedDocuments = $class->reflFields[$assoc['fieldName']]->getValue($document);
2326
2327 12
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2328 8
                if ($relatedDocuments === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
2329
                    // Collections are the same, so there is nothing to do
2330
                    continue;
2331
                }
2332
2333 8
                if ($relatedDocuments instanceof PersistentCollection) {
2334
                    // Unwrap so that foreach() does not initialize
2335 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2336 6
                }
2337
2338 8
                foreach ($relatedDocuments as $relatedDocument) {
2339 4
                    $this->doMerge($relatedDocument, $visited, $managedCopy, $assoc);
2340 8
                }
2341 12
            } elseif ($relatedDocuments !== null) {
2342 3
                $this->doMerge($relatedDocuments, $visited, $managedCopy, $assoc);
2343 3
            }
2344 13
        }
2345 13
    }
2346
2347
    /**
2348
     * Cascades the save operation to associated documents.
2349
     *
2350
     * @param object $document
2351
     * @param array $visited
2352
     */
2353 569
    private function cascadePersist($document, array &$visited)
2354
    {
2355 569
        $class = $this->dm->getClassMetadata(get_class($document));
2356
2357 569
        $associationMappings = array_filter(
2358 569
            $class->associationMappings,
2359
            function ($assoc) { return $assoc['isCascadePersist']; }
2360 569
        );
2361
2362 569
        foreach ($associationMappings as $fieldName => $mapping) {
2363 389
            $relatedDocuments = $class->reflFields[$fieldName]->getValue($document);
2364
2365 389
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2366 339
                if ($relatedDocuments instanceof PersistentCollection) {
2367 17
                    if ($relatedDocuments->getOwner() !== $document) {
2368 2
                        $relatedDocuments = $this->fixPersistentCollectionOwnership($relatedDocuments, $document, $class, $mapping['fieldName']);
2369 2
                    }
2370
                    // Unwrap so that foreach() does not initialize
2371 17
                    $relatedDocuments = $relatedDocuments->unwrap();
2372 17
                }
2373
2374 339
                $count = 0;
2375 339
                foreach ($relatedDocuments as $relatedKey => $relatedDocument) {
2376 187
                    if ( ! empty($mapping['embedded'])) {
2377 113
                        list(, $knownParent, ) = $this->getParentAssociation($relatedDocument);
2378 113
                        if ($knownParent && $knownParent !== $document) {
2379 4
                            $relatedDocument = clone $relatedDocument;
2380 4
                            $relatedDocuments[$relatedKey] = $relatedDocument;
2381 4
                        }
2382 113
                        $pathKey = ! isset($mapping['strategy']) || CollectionHelper::isList($mapping['strategy']) ? $count++ : $relatedKey;
2383 113
                        $this->setParentAssociation($relatedDocument, $mapping, $document, $mapping['fieldName'] . '.' . $pathKey);
2384 113
                    }
2385 187
                    $this->doPersist($relatedDocument, $visited);
2386 338
                }
2387 389
            } elseif ($relatedDocuments !== null) {
2388 120
                if ( ! empty($mapping['embedded'])) {
2389 66
                    list(, $knownParent, ) = $this->getParentAssociation($relatedDocuments);
2390 66
                    if ($knownParent && $knownParent !== $document) {
2391 5
                        $relatedDocuments = clone $relatedDocuments;
2392 5
                        $class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments);
2393 5
                    }
2394 66
                    $this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']);
2395 66
                }
2396 120
                $this->doPersist($relatedDocuments, $visited);
2397 119
            }
2398 568
        }
2399 567
    }
2400
2401
    /**
2402
     * Cascades the delete operation to associated documents.
2403
     *
2404
     * @param object $document
2405
     * @param array $visited
2406
     */
2407 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...
2408
    {
2409 66
        $class = $this->dm->getClassMetadata(get_class($document));
2410 66
        foreach ($class->fieldMappings as $mapping) {
2411 66
            if ( ! $mapping['isCascadeRemove']) {
2412 66
                continue;
2413
            }
2414 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...
2415 2
                $document->__load();
2416 2
            }
2417
2418 33
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2419 33
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2420
                // If its a PersistentCollection initialization is intended! No unwrap!
2421 24
                foreach ($relatedDocuments as $relatedDocument) {
2422 13
                    $this->doRemove($relatedDocument, $visited);
2423 24
                }
2424 33
            } elseif ($relatedDocuments !== null) {
2425 12
                $this->doRemove($relatedDocuments, $visited);
2426 12
            }
2427 66
        }
2428 66
    }
2429
2430
    /**
2431
     * Acquire a lock on the given document.
2432
     *
2433
     * @param object $document
2434
     * @param int $lockMode
2435
     * @param int $lockVersion
2436
     * @throws LockException
2437
     * @throws \InvalidArgumentException
2438
     */
2439 9
    public function lock($document, $lockMode, $lockVersion = null)
2440
    {
2441 9
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2442 1
            throw new \InvalidArgumentException("Document is not MANAGED.");
2443
        }
2444
2445 8
        $documentName = get_class($document);
2446 8
        $class = $this->dm->getClassMetadata($documentName);
2447
2448 8
        if ($lockMode == LockMode::OPTIMISTIC) {
2449 3
            if ( ! $class->isVersioned) {
2450 1
                throw LockException::notVersioned($documentName);
2451
            }
2452
2453 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...
2454 2
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2455 2
                if ($documentVersion != $lockVersion) {
2456 1
                    throw LockException::lockFailedVersionMissmatch($document, $lockVersion, $documentVersion);
2457
                }
2458 1
            }
2459 6
        } elseif (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) {
2460 5
            $this->getDocumentPersister($class->name)->lock($document, $lockMode);
2461 5
        }
2462 6
    }
2463
2464
    /**
2465
     * Releases a lock on the given document.
2466
     *
2467
     * @param object $document
2468
     * @throws \InvalidArgumentException
2469
     */
2470 1
    public function unlock($document)
2471
    {
2472 1
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2473
            throw new \InvalidArgumentException("Document is not MANAGED.");
2474
        }
2475 1
        $documentName = get_class($document);
2476 1
        $this->getDocumentPersister($documentName)->unlock($document);
2477 1
    }
2478
2479
    /**
2480
     * Clears the UnitOfWork.
2481
     *
2482
     * @param string|null $documentName if given, only documents of this type will get detached.
2483
     */
2484 388
    public function clear($documentName = null)
2485
    {
2486 388
        if ($documentName === null) {
2487 382
            $this->identityMap =
2488 382
            $this->documentIdentifiers =
2489 382
            $this->originalDocumentData =
2490 382
            $this->documentChangeSets =
2491 382
            $this->documentStates =
2492 382
            $this->scheduledForDirtyCheck =
2493 382
            $this->documentInsertions =
2494 382
            $this->documentUpserts =
2495 382
            $this->documentUpdates =
2496 382
            $this->documentDeletions =
2497 382
            $this->collectionUpdates =
2498 382
            $this->collectionDeletions =
2499 382
            $this->parentAssociations =
2500 382
            $this->orphanRemovals = 
2501 382
            $this->hasScheduledCollections = array();
2502 382
        } else {
2503 6
            $visited = array();
2504 6
            foreach ($this->identityMap as $className => $documents) {
2505 6
                if ($className === $documentName) {
2506 3
                    foreach ($documents as $document) {
2507 3
                        $this->doDetach($document, $visited, true);
0 ignored issues
show
Unused Code introduced by
The call to UnitOfWork::doDetach() has too many arguments starting with true.

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
2808 15
            $discriminatorValue = $data[$class->discriminatorField];
2809 187
        } elseif ($class->defaultDiscriminatorValue !== null) {
2810 1
            $discriminatorValue = $class->defaultDiscriminatorValue;
2811 1
        }
2812
2813 187
        if ($discriminatorValue !== null) {
2814 16
            return isset($class->discriminatorMap[$discriminatorValue])
2815 16
                ? $class->discriminatorMap[$discriminatorValue]
2816 16
                : $discriminatorValue;
2817
        }
2818
2819 171
        return $mapping['targetDocument'];
2820
    }
2821
2822
    /**
2823
     * INTERNAL:
2824
     * Creates a document. Used for reconstitution of documents during hydration.
2825
     *
2826
     * @ignore
2827
     * @param string $className The name of the document class.
2828
     * @param array $data The data for the document.
2829
     * @param array $hints Any hints to account for during reconstitution/lookup of the document.
2830
     * @param object The document to be hydrated into in case of creation
2831
     * @return object The document instance.
2832
     * @internal Highly performance-sensitive method.
2833
     */
2834 382
    public function getOrCreateDocument($className, $data, &$hints = array(), $document = null)
2835
    {
2836 382
        $class = $this->dm->getClassMetadata($className);
2837
2838
        // @TODO figure out how to remove this
2839 382
        $discriminatorValue = null;
2840 382 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...
2841 17
            $discriminatorValue = $data[$class->discriminatorField];
2842 382
        } elseif (isset($class->defaultDiscriminatorValue)) {
2843 2
            $discriminatorValue = $class->defaultDiscriminatorValue;
2844 2
        }
2845
2846 382
        if ($discriminatorValue !== null) {
2847 18
            $className = isset($class->discriminatorMap[$discriminatorValue])
2848 18
                ? $class->discriminatorMap[$discriminatorValue]
2849 18
                : $discriminatorValue;
2850
2851 18
            $class = $this->dm->getClassMetadata($className);
2852
2853 18
            unset($data[$class->discriminatorField]);
2854 18
        }
2855
2856 382
        $id = $class->getDatabaseIdentifierValue($data['_id']);
2857 382
        $serializedId = serialize($id);
2858
2859 382
        if (isset($this->identityMap[$class->name][$serializedId])) {
2860 89
            $document = $this->identityMap[$class->name][$serializedId];
2861 89
            $oid = spl_object_hash($document);
2862 89
            if ($document instanceof Proxy && ! $document->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

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