Completed
Pull Request — master (#1289)
by Jefersson
05:23
created

UnitOfWork::isScheduledForSynchronization()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4286
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB;
21
22
use Doctrine\Common\Collections\ArrayCollection;
23
use Doctrine\Common\Collections\Collection;
24
use Doctrine\Common\EventManager;
25
use Doctrine\Common\NotifyPropertyChanged;
26
use Doctrine\Common\PropertyChangedListener;
27
use Doctrine\MongoDB\GridFSFile;
28
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;
29
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
30
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
31
use Doctrine\ODM\MongoDB\PersistentCollection;
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
     */
134
    private $scheduledForSynchronization = array();
135
136
    /**
137
     * A list of all pending document insertions.
138
     *
139
     * @var array
140
     */
141
    private $documentInsertions = array();
142
143
    /**
144
     * A list of all pending document updates.
145
     *
146
     * @var array
147
     */
148
    private $documentUpdates = array();
149
150
    /**
151
     * A list of all pending document upserts.
152
     *
153
     * @var array
154
     */
155
    private $documentUpserts = array();
156
157
    /**
158
     * A list of all pending document deletions.
159
     *
160
     * @var array
161
     */
162
    private $documentDeletions = array();
163
164
    /**
165
     * All pending collection deletions.
166
     *
167
     * @var array
168
     */
169
    private $collectionDeletions = array();
170
171
    /**
172
     * All pending collection updates.
173
     *
174
     * @var array
175
     */
176
    private $collectionUpdates = array();
177
    
178
    /**
179
     * A list of documents related to collections scheduled for update or deletion
180
     * 
181
     * @var array
182
     */
183
    private $hasScheduledCollections = array();
184
185
    /**
186
     * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
187
     * At the end of the UnitOfWork all these collections will make new snapshots
188
     * of their data.
189
     *
190
     * @var array
191
     */
192
    private $visitedCollections = array();
193
194
    /**
195
     * The DocumentManager that "owns" this UnitOfWork instance.
196
     *
197
     * @var DocumentManager
198
     */
199
    private $dm;
200
201
    /**
202
     * The EventManager used for dispatching events.
203
     *
204
     * @var EventManager
205
     */
206
    private $evm;
207
208
    /**
209
     * Embedded documents that are scheduled for removal.
210
     *
211
     * @var array
212
     */
213
    private $orphanRemovals = array();
214
215
    /**
216
     * The HydratorFactory used for hydrating array Mongo documents to Doctrine object documents.
217
     *
218
     * @var HydratorFactory
219
     */
220
    private $hydratorFactory;
221
222
    /**
223
     * The document persister instances used to persist document instances.
224
     *
225
     * @var array
226
     */
227
    private $persisters = array();
228
229
    /**
230
     * The collection persister instance used to persist changes to collections.
231
     *
232
     * @var Persisters\CollectionPersister
233
     */
234
    private $collectionPersister;
235
236
    /**
237
     * The persistence builder instance used in DocumentPersisters.
238
     *
239
     * @var PersistenceBuilder
240
     */
241
    private $persistenceBuilder;
242
243
    /**
244
     * Array of parent associations between embedded documents
245
     *
246
     * @todo We might need to clean up this array in clear(), doDetach(), etc.
247
     * @var array
248
     */
249
    private $parentAssociations = array();
250
251
    /**
252
     * Initializes a new UnitOfWork instance, bound to the given DocumentManager.
253
     *
254
     * @param DocumentManager $dm
255
     * @param EventManager $evm
256
     * @param HydratorFactory $hydratorFactory
257
     */
258 899
    public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory)
259
    {
260 899
        $this->dm = $dm;
261 899
        $this->evm = $evm;
262 899
        $this->hydratorFactory = $hydratorFactory;
263 899
    }
264
265
    /**
266
     * Factory for returning new PersistenceBuilder instances used for preparing data into
267
     * queries for insert persistence.
268
     *
269
     * @return PersistenceBuilder $pb
270
     */
271 648
    public function getPersistenceBuilder()
272
    {
273 648
        if ( ! $this->persistenceBuilder) {
274 648
            $this->persistenceBuilder = new PersistenceBuilder($this->dm, $this);
275 648
        }
276 648
        return $this->persistenceBuilder;
277
    }
278
279
    /**
280
     * Sets the parent association for a given embedded document.
281
     *
282
     * @param object $document
283
     * @param array $mapping
284
     * @param object $parent
285
     * @param string $propertyPath
286
     */
287 174
    public function setParentAssociation($document, $mapping, $parent, $propertyPath)
288
    {
289 174
        $oid = spl_object_hash($document);
290 174
        $this->parentAssociations[$oid] = array($mapping, $parent, $propertyPath);
291 174
    }
292
293
    /**
294
     * Gets the parent association for a given embedded document.
295
     *
296
     *     <code>
297
     *     list($mapping, $parent, $propertyPath) = $this->getParentAssociation($embeddedDocument);
298
     *     </code>
299
     *
300
     * @param object $document
301
     * @return array $association
302
     */
303 201
    public function getParentAssociation($document)
304
    {
305 201
        $oid = spl_object_hash($document);
306 201
        if ( ! isset($this->parentAssociations[$oid])) {
307 197
            return null;
308
        }
309 159
        return $this->parentAssociations[$oid];
310
    }
311
312
    /**
313
     * Get the document persister instance for the given document name
314
     *
315
     * @param string $documentName
316
     * @return Persisters\DocumentPersister
317
     */
318 646
    public function getDocumentPersister($documentName)
319
    {
320 646
        if ( ! isset($this->persisters[$documentName])) {
321 632
            $class = $this->dm->getClassMetadata($documentName);
322 632
            $pb = $this->getPersistenceBuilder();
323 632
            $this->persisters[$documentName] = new Persisters\DocumentPersister($pb, $this->dm, $this->evm, $this, $this->hydratorFactory, $class);
324 632
        }
325 646
        return $this->persisters[$documentName];
326
    }
327
328
    /**
329
     * Get the collection persister instance.
330
     *
331
     * @return \Doctrine\ODM\MongoDB\Persisters\CollectionPersister
332
     */
333 646
    public function getCollectionPersister()
334
    {
335 646
        if ( ! isset($this->collectionPersister)) {
336 646
            $pb = $this->getPersistenceBuilder();
337 646
            $this->collectionPersister = new Persisters\CollectionPersister($this->dm, $pb, $this);
338 646
        }
339 646
        return $this->collectionPersister;
340
    }
341
342
    /**
343
     * Set the document persister instance to use for the given document name
344
     *
345
     * @param string $documentName
346
     * @param Persisters\DocumentPersister $persister
347
     */
348 14
    public function setDocumentPersister($documentName, Persisters\DocumentPersister $persister)
349
    {
350 14
        $this->persisters[$documentName] = $persister;
351 14
    }
352
353
    /**
354
     * Commits the UnitOfWork, executing all operations that have been postponed
355
     * up to this point. The state of all managed documents will be synchronized with
356
     * the database.
357
     *
358
     * The operations are executed in the following order:
359
     *
360
     * 1) All document insertions
361
     * 2) All document updates
362
     * 3) All document deletions
363
     *
364
     * @param object $document
365
     * @param array $options Array of options to be used with batchInsert(), update() and remove()
366
     */
367 535
    public function commit($document = null, array $options = array())
368
    {
369
        // Raise preFlush
370 535
        if ($this->evm->hasListeners(Events::preFlush)) {
371
            $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->dm));
372
        }
373
374 535
        $defaultOptions = $this->dm->getConfiguration()->getDefaultCommitOptions();
375 535
        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...
376
            $options = array_merge($defaultOptions, $options);
377
        } else {
378 535
            $options = $defaultOptions;
379
        }
380
        // Compute changes done since last commit.
381 535
        if ($document === null) {
382 529
            $this->computeChangeSets();
383 534
        } elseif (is_object($document)) {
384 12
            $this->computeSingleDocumentChangeSet($document);
385 12
        } elseif (is_array($document)) {
386 1
            foreach ($document as $object) {
387 1
                $this->computeSingleDocumentChangeSet($object);
388 1
            }
389 1
        }
390
391 533
        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...
392 226
            $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...
393 190
            $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...
394 180
            $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...
395 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...
396 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...
397 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...
398 533
        ) {
399 23
            return; // Nothing to do.
400
        }
401
402 530
        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...
403 45
            foreach ($this->orphanRemovals as $removal) {
404 44
                $this->remove($removal);
405 44
            }
406 44
        }
407
408
        // Raise onFlush
409 530
        if ($this->evm->hasListeners(Events::onFlush)) {
410 7
            $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));
411 7
        }
412
413 530
        foreach ($this->getClassesForCommitAction($this->documentUpserts) as $classAndDocuments) {
414 77
            list($class, $documents) = $classAndDocuments;
415 77
            $this->executeUpserts($class, $documents, $options);
0 ignored issues
show
Bug introduced by
It seems like $options defined by $defaultOptions on line 378 can also be of type boolean; however, Doctrine\ODM\MongoDB\UnitOfWork::executeUpserts() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
416 530
        }
417
418 530
        foreach ($this->getClassesForCommitAction($this->documentInsertions) as $classAndDocuments) {
419 464
            list($class, $documents) = $classAndDocuments;
420 464
            $this->executeInserts($class, $documents, $options);
0 ignored issues
show
Bug introduced by
It seems like $options defined by $defaultOptions on line 378 can also be of type boolean; however, Doctrine\ODM\MongoDB\UnitOfWork::executeInserts() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
421 529
        }
422
423 529
        foreach ($this->getClassesForCommitAction($this->documentUpdates) as $classAndDocuments) {
424 206
            list($class, $documents) = $classAndDocuments;
425 206
            $this->executeUpdates($class, $documents, $options);
0 ignored issues
show
Bug introduced by
It seems like $options defined by $defaultOptions on line 378 can also be of type boolean; however, Doctrine\ODM\MongoDB\UnitOfWork::executeUpdates() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
426 529
        }
427
428 529
        foreach ($this->getClassesForCommitAction($this->documentDeletions, true) as $classAndDocuments) {
429 61
            list($class, $documents) = $classAndDocuments;
430 61
            $this->executeDeletions($class, $documents, $options);
0 ignored issues
show
Bug introduced by
It seems like $options defined by $defaultOptions on line 378 can also be of type boolean; however, Doctrine\ODM\MongoDB\Uni...ork::executeDeletions() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
431 529
        }
432
433
        // Raise postFlush
434 529
        if ($this->evm->hasListeners(Events::postFlush)) {
435
            $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm));
436
        }
437
438
        // Clear up
439 529
        $this->documentInsertions =
440 529
        $this->documentUpserts =
441 529
        $this->documentUpdates =
442 529
        $this->documentDeletions =
443 529
        $this->documentChangeSets =
444 529
        $this->collectionUpdates =
445 529
        $this->collectionDeletions =
446 529
        $this->visitedCollections =
447 529
        $this->scheduledForSynchronization =
448 529
        $this->orphanRemovals = 
449 529
        $this->hasScheduledCollections = array();
450 529
    }
451
452
    /**
453
     * Groups a list of scheduled documents by their class.
454
     *
455
     * @param array $documents Scheduled documents (e.g. $this->documentInsertions)
456
     * @param bool $includeEmbedded
457
     * @return array Tuples of ClassMetadata and a corresponding array of objects
458
     */
459 530
    private function getClassesForCommitAction($documents, $includeEmbedded = false)
460
    {
461 530
        if (empty($documents)) {
462 530
            return array();
463
        }
464 529
        $divided = array();
465 529
        $embeds = array();
466 529
        foreach ($documents as $oid => $d) {
467 529
            $className = get_class($d);
468 529
            if (isset($embeds[$className])) {
469 62
                continue;
470
            }
471 529
            if (isset($divided[$className])) {
472 132
                $divided[$className][1][$oid] = $d;
473 132
                continue;
474
            }
475 529
            $class = $this->dm->getClassMetadata($className);
476 529
            if ($class->isEmbeddedDocument && ! $includeEmbedded) {
477 159
                $embeds[$className] = true;
478 159
                continue;
479
            }
480 529
            if (empty($divided[$class->name])) {
481 529
                $divided[$class->name] = array($class, array($oid => $d));
482 529
            } else {
483 4
                $divided[$class->name][1][$oid] = $d;
484
            }
485 529
        }
486 529
        return $divided;
487
    }
488
489
    /**
490
     * Compute changesets of all documents scheduled for insertion.
491
     *
492
     * Embedded documents will not be processed.
493
     */
494 537 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...
495
    {
496 537
        foreach ($this->documentInsertions as $document) {
497 472
            $class = $this->dm->getClassMetadata(get_class($document));
498 472
            if ( ! $class->isEmbeddedDocument) {
499 469
                $this->computeChangeSet($class, $document);
500 468
            }
501 536
        }
502 536
    }
503
504
    /**
505
     * Compute changesets of all documents scheduled for upsert.
506
     *
507
     * Embedded documents will not be processed.
508
     */
509 536 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...
510
    {
511 536
        foreach ($this->documentUpserts as $document) {
512 76
            $class = $this->dm->getClassMetadata(get_class($document));
513 76
            if ( ! $class->isEmbeddedDocument) {
514 76
                $this->computeChangeSet($class, $document);
515 76
            }
516 536
        }
517 536
    }
518
519
    /**
520
     * Only flush the given document according to a ruleset that keeps the UoW consistent.
521
     *
522
     * 1. All documents scheduled for insertion and (orphan) removals are processed as well!
523
     * 2. Proxies are skipped.
524
     * 3. Only if document is properly managed.
525
     *
526
     * @param  object $document
527
     * @throws \InvalidArgumentException If the document is not STATE_MANAGED
528
     * @return void
529
     */
530 13
    private function computeSingleDocumentChangeSet($document)
531
    {
532 13
        $state = $this->getDocumentState($document);
533
534 13
        if ($state !== self::STATE_MANAGED && $state !== self::STATE_REMOVED) {
535 1
            throw new \InvalidArgumentException("Document has to be managed or scheduled for removal for single computation " . self::objToStr($document));
536
        }
537
538 12
        $class = $this->dm->getClassMetadata(get_class($document));
539
540 12
        if ($state === self::STATE_MANAGED && $class->isChangeTrackingDeferredImplicit()) {
541 9
            $this->persist($document);
542 9
        }
543
544
        // Compute changes for INSERTed and UPSERTed documents first. This must always happen even in this case.
545 12
        $this->computeScheduleInsertsChangeSets();
546 12
        $this->computeScheduleUpsertsChangeSets();
547
548
        // Ignore uninitialized proxy objects
549 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...
550
            return;
551
        }
552
553
        // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here.
554 12
        $oid = spl_object_hash($document);
555
556 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...
557 12
            && ! isset($this->documentUpserts[$oid])
558 12
            && ! isset($this->documentDeletions[$oid])
559 12
            && isset($this->documentStates[$oid])
560 12
        ) {
561 8
            $this->computeChangeSet($class, $document);
562 8
        }
563 12
    }
564
565
    /**
566
     * Gets the changeset for a document.
567
     *
568
     * @param object $document
569
     * @return array array('property' => array(0 => mixed|null, 1 => mixed|null))
570
     */
571 520
    public function getDocumentChangeSet($document)
572
    {
573 520
        $oid = spl_object_hash($document);
574 520
        if (isset($this->documentChangeSets[$oid])) {
575 520
            return $this->documentChangeSets[$oid];
576
        }
577 66
        return array();
578
    }
579
580
    /**
581
     * Get a documents actual data, flattening all the objects to arrays.
582
     *
583
     * @param object $document
584
     * @return array
585
     */
586 534
    public function getDocumentActualData($document)
587
    {
588 534
        $class = $this->dm->getClassMetadata(get_class($document));
589 534
        $actualData = array();
590 534
        foreach ($class->reflFields as $name => $refProp) {
591 534
            $mapping = $class->fieldMappings[$name];
592
            // skip not saved fields
593 534
            if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
594 49
                continue;
595
            }
596 534
            $value = $refProp->getValue($document);
597 534
            if (isset($mapping['file']) && ! $value instanceof GridFSFile) {
598 5
                $value = new GridFSFile($value);
599 5
                $class->reflFields[$name]->setValue($document, $value);
600 5
                $actualData[$name] = $value;
601 534
            } elseif ((isset($mapping['association']) && $mapping['type'] === 'many')
602 534
                && $value !== null && ! ($value instanceof PersistentCollection)) {
603
                // If $actualData[$name] is not a Collection then use an ArrayCollection.
604 347
                if ( ! $value instanceof Collection) {
605 118
                    $value = new ArrayCollection($value);
606 118
                }
607
608
                // Inject PersistentCollection
609 347
                $coll = new PersistentCollection($value, $this->dm, $this);
610 347
                $coll->setOwner($document, $mapping);
611 347
                $coll->setDirty( ! $value->isEmpty());
612 347
                $class->reflFields[$name]->setValue($document, $coll);
613 347
                $actualData[$name] = $coll;
614 347
            } else {
615 534
                $actualData[$name] = $value;
616
            }
617 534
        }
618 534
        return $actualData;
619
    }
620
621
    /**
622
     * Computes the changes that happened to a single document.
623
     *
624
     * Modifies/populates the following properties:
625
     *
626
     * {@link originalDocumentData}
627
     * If the document is NEW or MANAGED but not yet fully persisted (only has an id)
628
     * then it was not fetched from the database and therefore we have no original
629
     * document data yet. All of the current document data is stored as the original document data.
630
     *
631
     * {@link documentChangeSets}
632
     * The changes detected on all properties of the document are stored there.
633
     * A change is a tuple array where the first entry is the old value and the second
634
     * entry is the new value of the property. Changesets are used by persisters
635
     * to INSERT/UPDATE the persistent document state.
636
     *
637
     * {@link documentUpdates}
638
     * If the document is already fully MANAGED (has been fetched from the database before)
639
     * and any changes to its properties are detected, then a reference to the document is stored
640
     * there to mark it for an update.
641
     *
642
     * @param ClassMetadata $class The class descriptor of the document.
643
     * @param object $document The document for which to compute the changes.
644
     */
645 534
    public function computeChangeSet(ClassMetadata $class, $document)
646
    {
647 534
        if ( ! $class->isInheritanceTypeNone()) {
648 161
            $class = $this->dm->getClassMetadata(get_class($document));
649 161
        }
650
651
        // Fire PreFlush lifecycle callbacks
652 534
        if ( ! empty($class->lifecycleCallbacks[Events::preFlush])) {
653 10
            $class->invokeLifecycleCallbacks(Events::preFlush, $document);
654 10
        }
655
656 534
        $this->computeOrRecomputeChangeSet($class, $document);
657 533
    }
658
659
    /**
660
     * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet
661
     *
662
     * @param \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $class
663
     * @param object $document
664
     * @param boolean $recompute
665
     */
666 534
    private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false)
667
    {
668 534
        $oid = spl_object_hash($document);
669 534
        $actualData = $this->getDocumentActualData($document);
670 534
        $isNewDocument = ! isset($this->originalDocumentData[$oid]);
671 534
        if ($isNewDocument) {
672
            // Document is either NEW or MANAGED but not yet fully persisted (only has an id).
673
            // These result in an INSERT.
674 534
            $this->originalDocumentData[$oid] = $actualData;
675 534
            $changeSet = array();
676 534
            foreach ($actualData as $propName => $actualValue) {
677
                /* At this PersistentCollection shouldn't be here, probably it
678
                 * was cloned and its ownership must be fixed
679
                 */
680 534
                if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) {
681
                    $actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
682
                    $actualValue = $actualData[$propName];
683
                }
684 534
                $changeSet[$propName] = array(null, $actualValue);
685 534
            }
686 534
            $this->documentChangeSets[$oid] = $changeSet;
687 534
        } else {
688
            // Document is "fully" MANAGED: it was already fully persisted before
689
            // and we have a copy of the original data
690 266
            $originalData = $this->originalDocumentData[$oid];
691 266
            $isChangeTrackingNotify = $class->isChangeTrackingNotify();
692 266
            if ($isChangeTrackingNotify && ! $recompute && isset($this->documentChangeSets[$oid])) {
693 2
                $changeSet = $this->documentChangeSets[$oid];
694 2
            } else {
695 266
                $changeSet = array();
696
            }
697
698 266
            foreach ($actualData as $propName => $actualValue) {
699
                // skip not saved fields
700 266
                if (isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) {
701
                    continue;
702
                }
703
704 266
                $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
705
706
                // skip if value has not changed
707 266
                if ($orgValue === $actualValue) {
708
                    // but consider dirty GridFSFile instances as changed
709 265
                    if ( ! (isset($class->fieldMappings[$propName]['file']) && $actualValue->isDirty())) {
710 265
                        continue;
711
                    }
712 1
                }
713
714
                // if relationship is a embed-one, schedule orphan removal to trigger cascade remove operations
715 173
                if (isset($class->fieldMappings[$propName]['embedded']) && $class->fieldMappings[$propName]['type'] === 'one') {
716 10
                    if ($orgValue !== null) {
717 5
                        $this->scheduleOrphanRemoval($orgValue);
718 5
                    }
719
720 10
                    $changeSet[$propName] = array($orgValue, $actualValue);
721 10
                    continue;
722
                }
723
724
                // if owning side of reference-one relationship
725 166
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $class->fieldMappings[$propName]['isOwningSide']) {
726 8
                    if ($orgValue !== null && $class->fieldMappings[$propName]['orphanRemoval']) {
727 1
                        $this->scheduleOrphanRemoval($orgValue);
728 1
                    }
729
730 8
                    $changeSet[$propName] = array($orgValue, $actualValue);
731 8
                    continue;
732
                }
733
734 160
                if ($isChangeTrackingNotify) {
735 2
                    continue;
736
                }
737
738
                // ignore inverse side of reference-many relationship
739 159
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'many' && $class->fieldMappings[$propName]['isInverseSide']) {
740
                    continue;
741
                }
742
743
                // Persistent collection was exchanged with the "originally"
744
                // created one. This can only mean it was cloned and replaced
745
                // on another document.
746 159
                if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) {
747 6
                    $actualValue = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
748 6
                }
749
750
                // if embed-many or reference-many relationship
751 159
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'many') {
752 25
                    $changeSet[$propName] = array($orgValue, $actualValue);
753
                    /* If original collection was exchanged with a non-empty value
754
                     * and $set will be issued, there is no need to $unset it first
755
                     */
756 25
                    if ($actualValue && $actualValue->isDirty() && CollectionHelper::usesSet($class->fieldMappings[$propName]['strategy'])) {
757 7
                        continue;
758
                    }
759 19
                    if ($orgValue instanceof PersistentCollection) {
760 17
                        $this->scheduleCollectionDeletion($orgValue);
761 17
                    }
762 19
                    continue;
763
                }
764
765
                // skip equivalent date values
766 145
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'date') {
767 35
                    $dateType = Type::getType('date');
768 35
                    $dbOrgValue = $dateType->convertToDatabaseValue($orgValue);
769 35
                    $dbActualValue = $dateType->convertToDatabaseValue($actualValue);
770
771 35
                    if ($dbOrgValue instanceof \MongoDate && $dbActualValue instanceof \MongoDate && $dbOrgValue == $dbActualValue) {
772 29
                        continue;
773
                    }
774 9
                }
775
776
                // regular field
777 129
                $changeSet[$propName] = array($orgValue, $actualValue);
778 266
            }
779 266
            if ($changeSet) {
780 159
                $this->documentChangeSets[$oid] = ($recompute && isset($this->documentChangeSets[$oid]))
781 159
                    ? $changeSet + $this->documentChangeSets[$oid]
782 159
                    : $changeSet;
783
784 159
                $this->originalDocumentData[$oid] = $actualData;
785 159
                $this->scheduleForUpdate($document);
786 159
            }
787
        }
788
789
        // Look for changes in associations of the document
790 534
        $associationMappings = array_filter(
791 534
            $class->associationMappings,
792
            function ($assoc) { return empty($assoc['notSaved']); }
793 534
        );
794
795 534
        foreach ($associationMappings as $mapping) {
796 409
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
797
798 409
            if ($value === null) {
799 269
                continue;
800
            }
801
802 400
            $this->computeAssociationChanges($document, $mapping, $value);
803
804 399
            if (isset($mapping['reference'])) {
805 296
                continue;
806
            }
807
808 308
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value->unwrap();
809
810 308
            foreach ($values as $obj) {
811 163
                $oid2 = spl_object_hash($obj);
812
813 163
                if (isset($this->documentChangeSets[$oid2])) {
814 161
                    $this->documentChangeSets[$oid][$mapping['fieldName']] = array($value, $value);
815
816 161
                    if ( ! $isNewDocument) {
817 71
                        $this->scheduleForUpdate($document);
818 71
                    }
819
820 161
                    break;
821
                }
822 308
            }
823 533
        }
824 533
    }
825
826
    /**
827
     * Computes all the changes that have been done to documents and collections
828
     * since the last commit and stores these changes in the _documentChangeSet map
829
     * temporarily for access by the persisters, until the UoW commit is finished.
830
     */
831 532
    public function computeChangeSets()
832
    {
833 532
        $this->computeScheduleInsertsChangeSets();
834 531
        $this->computeScheduleUpsertsChangeSets();
835
836
        // Compute changes for other MANAGED documents. Change tracking policies take effect here.
837 531
        foreach ($this->identityMap as $className => $documents) {
838 531
            $class = $this->dm->getClassMetadata($className);
839 531
            if ($class->isEmbeddedDocument) {
840
                /* we do not want to compute changes to embedded documents up front
841
                 * in case embedded document was replaced and its changeset
842
                 * would corrupt data. Embedded documents' change set will
843
                 * be calculated by reachability from owning document.
844
                 */
845 152
                continue;
846
            }
847
848
            // If change tracking is explicit or happens through notification, then only compute
849
            // changes on document of that type that are explicitly marked for synchronization.
850 531
            switch (true) {
851 531
                case ($class->isChangeTrackingDeferredImplicit()):
852 530
                    $documentsToProcess = $documents;
853 530
                    break;
854
855 3
                case (isset($this->scheduledForSynchronization[$className])):
856 2
                    $documentsToProcess = $this->scheduledForSynchronization[$className];
857 2
                    break;
858
859 3
                default:
860 3
                    $documentsToProcess = array();
861
862 3
            }
863
864 531
            foreach ($documentsToProcess as $document) {
865
                // Ignore uninitialized proxy objects
866 527
                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...
867 10
                    continue;
868
                }
869
                // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here.
870 527
                $oid = spl_object_hash($document);
871 527 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...
872 527
                    && ! isset($this->documentUpserts[$oid])
873 527
                    && ! isset($this->documentDeletions[$oid])
874 527
                    && isset($this->documentStates[$oid])
875 527
                ) {
876 251
                    $this->computeChangeSet($class, $document);
877 251
                }
878 531
            }
879 531
        }
880 531
    }
881
882
    /**
883
     * Computes the changes of an association.
884
     *
885
     * @param object $parentDocument
886
     * @param array $assoc
887
     * @param mixed $value The value of the association.
888
     * @throws \InvalidArgumentException
889
     */
890 400
    private function computeAssociationChanges($parentDocument, array $assoc, $value)
891
    {
892 400
        $isNewParentDocument = isset($this->documentInsertions[spl_object_hash($parentDocument)]);
893 400
        $class = $this->dm->getClassMetadata(get_class($parentDocument));
894 400
        $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument);
895
896 400
        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...
897 8
            return;
898
        }
899
900 399
        if ($value instanceof PersistentCollection && $value->isDirty() && $assoc['isOwningSide']) {
901 207
            if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) {
902 203
                $this->scheduleCollectionUpdate($value);
903 203
            }
904 207
            $topmostOwner = $this->getOwningDocument($value->getOwner());
905 207
            $this->visitedCollections[spl_object_hash($topmostOwner)][] = $value;
906 207
        }
907
908
        // Look through the documents, and in any of their associations,
909
        // for transient (new) documents, recursively. ("Persistence by reachability")
910
        // Unwrap. Uninitialized collections will simply be empty.
911 399
        $unwrappedValue = ($assoc['type'] === ClassMetadata::ONE) ? array($value) : $value->unwrap();
912
913 399
        $count = 0;
914 399
        foreach ($unwrappedValue as $key => $entry) {
915 304
            if ( ! is_object($entry)) {
916 1
                throw new \InvalidArgumentException(
917 1
                        sprintf('Expected object, found "%s" in %s::%s', $entry, get_class($parentDocument), $assoc['name'])
918 1
                );
919
            }
920
921 303
            $targetClass = $this->dm->getClassMetadata(get_class($entry));
922
923 303
            $state = $this->getDocumentState($entry, self::STATE_NEW);
924
925
            // Handle "set" strategy for multi-level hierarchy
926 303
            $pathKey = ! isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key;
927 303
            $path = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name'];
928
929 303
            $count++;
930
931
            switch ($state) {
932 303
                case self::STATE_NEW:
933 56
                    if ( ! $assoc['isCascadePersist']) {
934
                        throw new \InvalidArgumentException("A new document was found through a relationship that was not"
935
                            . " configured to cascade persist operations: " . self::objToStr($entry) . "."
936
                            . " Explicitly persist the new document or configure cascading persist operations"
937
                            . " on the relationship.");
938
                    }
939
940 56
                    $this->persistNew($targetClass, $entry);
941 56
                    $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
942 56
                    $this->computeChangeSet($targetClass, $entry);
943 56
                    break;
944
945 299
                case self::STATE_MANAGED:
946 299
                    if ($targetClass->isEmbeddedDocument) {
947 155
                        list(, $knownParent, ) = $this->getParentAssociation($entry);
948 155
                        if ($knownParent && $knownParent !== $parentDocument) {
949 6
                            $entry = clone $entry;
950 6
                            if ($assoc['type'] === ClassMetadata::ONE) {
951 3
                                $class->setFieldValue($parentDocument, $assoc['name'], $entry);
952 3
                                $this->setOriginalDocumentProperty(spl_object_hash($parentDocument), $assoc['name'], $entry);
953 3
                            } else {
954
                                // must use unwrapped value to not trigger orphan removal
955 6
                                $unwrappedValue[$key] = $entry;
956
                            }
957 6
                            $this->persistNew($targetClass, $entry);
958 6
                        }
959 155
                        $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
960 155
                        $this->computeChangeSet($targetClass, $entry);
961 155
                    }
962 299
                    break;
963
964 1
                case self::STATE_REMOVED:
965
                    // Consume the $value as array (it's either an array or an ArrayAccess)
966
                    // and remove the element from Collection.
967 1
                    if ($assoc['type'] === ClassMetadata::MANY) {
968
                        unset($value[$key]);
969
                    }
970 1
                    break;
971
972
                case self::STATE_DETACHED:
973
                    // Can actually not happen right now as we assume STATE_NEW,
974
                    // so the exception will be raised from the DBAL layer (constraint violation).
975
                    throw new \InvalidArgumentException("A detached document was found through a "
976
                        . "relationship during cascading a persist operation.");
977
                    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...
978
979
                default:
980
                    // MANAGED associated documents are already taken into account
981
                    // during changeset calculation anyway, since they are in the identity map.
982
983
            }
984 398
        }
985 398
    }
986
987
    /**
988
     * INTERNAL:
989
     * Computes the changeset of an individual document, independently of the
990
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
991
     *
992
     * The passed document must be a managed document. If the document already has a change set
993
     * because this method is invoked during a commit cycle then the change sets are added.
994
     * whereby changes detected in this method prevail.
995
     *
996
     * @ignore
997
     * @param ClassMetadata $class The class descriptor of the document.
998
     * @param object $document The document for which to (re)calculate the change set.
999
     * @throws \InvalidArgumentException If the passed document is not MANAGED.
1000
     */
1001 19
    public function recomputeSingleDocumentChangeSet(ClassMetadata $class, $document)
1002
    {
1003
        // Ignore uninitialized proxy objects
1004 19
        if ($document instanceof Proxy && ! $document->__isInitialized__) {
0 ignored issues
show
Bug introduced by
Accessing __isInitialized__ on the interface Doctrine\ODM\MongoDB\Proxy\Proxy suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1005 1
            return;
1006
        }
1007
1008 18
        $oid = spl_object_hash($document);
1009
1010 18
        if ( ! isset($this->documentStates[$oid]) || $this->documentStates[$oid] != self::STATE_MANAGED) {
1011
            throw new \InvalidArgumentException('Document must be managed.');
1012
        }
1013
1014 18
        if ( ! $class->isInheritanceTypeNone()) {
1015 2
            $class = $this->dm->getClassMetadata(get_class($document));
1016 2
        }
1017
1018 18
        $this->computeOrRecomputeChangeSet($class, $document, true);
1019 18
    }
1020
1021
    /**
1022
     * @param ClassMetadata $class
1023
     * @param object $document
1024
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1025
     */
1026 552
    private function persistNew(ClassMetadata $class, $document)
1027
    {
1028 552
        $oid = spl_object_hash($document);
1029 552
        if ( ! empty($class->lifecycleCallbacks[Events::prePersist])) {
1030 145
            $class->invokeLifecycleCallbacks(Events::prePersist, $document);
1031 145
        }
1032 552 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...
1033 6
            $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($document, $this->dm));
1034 6
        }
1035
1036 552
        $upsert = false;
1037 552
        if ($class->identifier) {
1038 552
            $idValue = $class->getIdentifierValue($document);
1039 552
            $upsert = ! $class->isEmbeddedDocument && $idValue !== null;
1040
1041 552
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1042 3
                throw new \InvalidArgumentException(sprintf(
1043 3
                    "%s uses NONE identifier generation strategy but no identifier was provided when persisting.",
1044 3
                    get_class($document)
1045 3
                ));
1046
            }
1047
1048
            // \MongoId::isValid($idValue) was introduced in 1.5.0 so it's no good
1049 551
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_AUTO && $idValue !== null && ! preg_match('/^[0-9a-f]{24}$/', $idValue)) {
1050 1
                throw new \InvalidArgumentException(sprintf(
1051 1
                    "%s uses AUTO identifier generation strategy but provided identifier is not valid MongoId.",
1052 1
                    get_class($document)
1053 1
                ));
1054
            }
1055
1056 550
            if ($class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1057 480
                $idValue = $class->idGenerator->generate($this->dm, $document);
1058 480
                $idValue = $class->getPHPIdentifierValue($class->getDatabaseIdentifierValue($idValue));
1059 480
                $class->setIdentifierValue($document, $idValue);
1060 480
            }
1061
1062 550
            $this->documentIdentifiers[$oid] = $idValue;
1063 550
        } else {
1064
            // this is for embedded documents without identifiers
1065 142
            $this->documentIdentifiers[$oid] = $oid;
1066
        }
1067
1068 550
        $this->documentStates[$oid] = self::STATE_MANAGED;
1069
1070 550
        if ($upsert) {
1071 80
            $this->scheduleForUpsert($class, $document);
1072 80
        } else {
1073 485
            $this->scheduleForInsert($class, $document);
1074
        }
1075 550
    }
1076
1077
    /**
1078
     * Cascades the postPersist events to embedded documents.
1079
     *
1080
     * @param ClassMetadata $class
1081
     * @param object $document
1082
     */
1083 528
    private function cascadePostPersist(ClassMetadata $class, $document)
1084
    {
1085 528
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1086
1087 528
        $embeddedMappings = array_filter(
1088 528
            $class->associationMappings,
1089
            function($assoc) { return ! empty($assoc['embedded']); }
1090 528
        );
1091
1092 528
        foreach ($embeddedMappings as $mapping) {
1093 316
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1094
1095 316
            if ($value === null) {
1096 203
                continue;
1097
            }
1098
1099 298
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1100
1101 298
            if (isset($mapping['targetDocument'])) {
1102 287
                $embeddedClass = $this->dm->getClassMetadata($mapping['targetDocument']);
1103 287
            }
1104
1105 298
            foreach ($values as $embeddedDocument) {
1106 153
                if ( ! isset($mapping['targetDocument'])) {
1107 13
                    $embeddedClass = $this->dm->getClassMetadata(get_class($embeddedDocument));
1108 13
                }
1109
1110 153
                if ( ! empty($embeddedClass->lifecycleCallbacks[Events::postPersist])) {
1111 9
                    $embeddedClass->invokeLifecycleCallbacks(Events::postPersist, $embeddedDocument);
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...
1112 9
                }
1113 153
                if ($hasPostPersistListeners) {
1114 4
                    $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($embeddedDocument, $this->dm));
1115 4
                }
1116 153
                $this->cascadePostPersist($embeddedClass, $embeddedDocument);
1117 298
            }
1118 528
         }
1119 528
     }
1120
1121
    /**
1122
     * Executes all document insertions for documents of the specified type.
1123
     *
1124
     * @param ClassMetadata $class
1125
     * @param array $documents Array of documents to insert
1126
     * @param array $options Array of options to be used with batchInsert()
1127
     */
1128 464 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...
1129
    {
1130 464
        $persister = $this->getDocumentPersister($class->name);
1131
1132 464
        foreach ($documents as $oid => $document) {
1133 464
            $persister->addInsert($document);
1134 464
            unset($this->documentInsertions[$oid]);
1135 464
        }
1136
1137 464
        $persister->executeInserts($options);
1138
1139 463
        $hasPostPersistLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postPersist]);
1140 463
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1141
1142 463
        foreach ($documents as $document) {
1143 463
            if ($hasPostPersistLifecycleCallbacks) {
1144 9
                $class->invokeLifecycleCallbacks(Events::postPersist, $document);
1145 9
            }
1146 463
            if ($hasPostPersistListeners) {
1147 5
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1148 5
            }
1149 463
            $this->cascadePostPersist($class, $document);
1150 463
        }
1151 463
    }
1152
1153
    /**
1154
     * Executes all document upserts for documents of the specified type.
1155
     *
1156
     * @param ClassMetadata $class
1157
     * @param array $documents Array of documents to upsert
1158
     * @param array $options Array of options to be used with batchInsert()
1159
     */
1160 77 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...
1161
    {
1162 77
        $persister = $this->getDocumentPersister($class->name);
1163
1164
1165 77
        foreach ($documents as $oid => $document) {
1166 77
            $persister->addUpsert($document);
1167 77
            unset($this->documentUpserts[$oid]);
1168 77
        }
1169
1170 77
        $persister->executeUpserts($options);
1171
1172 77
        $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]);
1173 77
        $hasListeners = $this->evm->hasListeners(Events::postPersist);
1174
1175 77
        foreach ($documents as $document) {
1176 77
            if ($hasLifecycleCallbacks) {
1177
                $class->invokeLifecycleCallbacks(Events::postPersist, $document);
1178
            }
1179 77
            if ($hasListeners) {
1180 2
                $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm));
1181 2
            }
1182 77
            $this->cascadePostPersist($class, $document);
1183 77
        }
1184 77
    }
1185
1186
    /**
1187
     * Executes all document updates for documents of the specified type.
1188
     *
1189
     * @param Mapping\ClassMetadata $class
1190
     * @param array $documents Array of documents to update
1191
     * @param array $options Array of options to be used with update()
1192
     */
1193 206
    private function executeUpdates(ClassMetadata $class, array $documents, array $options = array())
1194
    {
1195 206
        $className = $class->name;
1196 206
        $persister = $this->getDocumentPersister($className);
1197
1198 206
        $hasPreUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::preUpdate]);
1199 206
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1200 206
        $hasPostUpdateLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postUpdate]);
1201 206
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1202
1203 206
        foreach ($documents as $oid => $document) {
1204 206
            if ($hasPreUpdateLifecycleCallbacks) {
1205 11
                $class->invokeLifecycleCallbacks(Events::preUpdate, $document);
1206 11
                $this->recomputeSingleDocumentChangeSet($class, $document);
1207 11
            }
1208
1209 206
            if ($hasPreUpdateListeners) {
1210 8
                if ( ! isset($this->documentChangeSets[$oid])) {
1211
                    // only ReferenceMany collection is scheduled for update
1212 1
                    $this->documentChangeSets[$oid] = array();
1213 1
                }
1214 8
                $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1215 8
                    $document, $this->dm, $this->documentChangeSets[$oid])
1216 8
                );
1217 8
            }
1218 206
            $this->cascadePreUpdate($class, $document);
1219
1220 206
            if ( ! empty($this->documentChangeSets[$oid]) || $this->hasScheduledCollections($document)) {
1221 205
                $persister->update($document, $options);
1222 201
            }
1223
1224 202
            unset($this->documentUpdates[$oid]);
1225
1226 202
            if ($hasPostUpdateLifecycleCallbacks) {
1227 6
                $class->invokeLifecycleCallbacks(Events::postUpdate, $document);
1228 6
            }
1229 202
            if ($hasPostUpdateListeners) {
1230 8
                $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($document, $this->dm));
1231 8
            }
1232 202
            $this->cascadePostUpdate($class, $document);
1233 202
        }
1234 201
    }
1235
1236
    /**
1237
     * Cascades the preUpdate event to embedded documents.
1238
     *
1239
     * @param ClassMetadata $class
1240
     * @param object $document
1241
     */
1242 206
    private function cascadePreUpdate(ClassMetadata $class, $document)
1243
    {
1244 206
        $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
1245
1246 206
        $embeddedMappings = array_filter(
1247 206
            $class->associationMappings,
1248
            function ($assoc) { return ! empty($assoc['embedded']); }
1249 206
        );
1250
1251 206
        foreach ($embeddedMappings as $mapping) {
1252 125
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1253
1254 125
            if ($value === null) {
1255 49
                continue;
1256
            }
1257
1258 123
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1259
1260 123
            foreach ($values as $entry) {
1261 78
                $entryOid = spl_object_hash($entry);
1262 78
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1263
1264 78
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1265 41
                    continue;
1266
                }
1267
1268 67
                if (isset($this->documentInsertions[$entryOid])) {
1269 52
                    continue;
1270
                }
1271
1272 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...
1273 5
                    $entryClass->invokeLifecycleCallbacks(Events::preUpdate, $entry);
1274 5
                    $this->recomputeSingleDocumentChangeSet($entryClass, $entry);
1275 5
                }
1276 45
                if ($hasPreUpdateListeners) {
1277 3
                    $this->evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs(
1278 3
                        $entry, $this->dm, $this->documentChangeSets[$entryOid])
1279 3
                    );
1280 3
                }
1281
1282 45
                $this->cascadePreUpdate($entryClass, $entry);
1283 123
            }
1284 206
        }
1285 206
    }
1286
1287
    /**
1288
     * Cascades the postUpdate and postPersist events to embedded documents.
1289
     *
1290
     * @param ClassMetadata $class
1291
     * @param object $document
1292
     */
1293 202
    private function cascadePostUpdate(ClassMetadata $class, $document)
1294
    {
1295 202
        $hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist);
1296 202
        $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
1297
1298 202
        $embeddedMappings = array_filter(
1299 202
            $class->associationMappings,
1300
            function($assoc) { return ! empty($assoc['embedded']); }
1301 202
        );
1302
1303 202
        foreach ($embeddedMappings as $mapping) {
1304 121
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
1305
1306 121
            if ($value === null) {
1307 52
                continue;
1308
            }
1309
1310 119
            $values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value;
1311
1312 119
            foreach ($values as $entry) {
1313 78
                $entryOid = spl_object_hash($entry);
1314 78
                $entryClass = $this->dm->getClassMetadata(get_class($entry));
1315
1316 78
                if ( ! isset($this->documentChangeSets[$entryOid])) {
1317 41
                    continue;
1318
                }
1319
1320 67
                if (isset($this->documentInsertions[$entryOid])) {
1321 52
                    if ( ! empty($entryClass->lifecycleCallbacks[Events::postPersist])) {
1322 1
                        $entryClass->invokeLifecycleCallbacks(Events::postPersist, $entry);
1323 1
                    }
1324 52
                    if ($hasPostPersistListeners) {
1325 3
                        $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entry, $this->dm));
1326 3
                    }
1327 52
                } else {
1328 45
                    if ( ! empty($entryClass->lifecycleCallbacks[Events::postUpdate])) {
1329 9
                        $entryClass->invokeLifecycleCallbacks(Events::postUpdate, $entry);
1330 9
                    }
1331 45
                    if ($hasPostUpdateListeners) {
1332 3
                        $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entry, $this->dm));
1333 3
                    }
1334
                }
1335
1336 67
                $this->cascadePostUpdate($entryClass, $entry);
1337 119
            }
1338 202
        }
1339 202
    }
1340
1341
    /**
1342
     * Executes all document deletions for documents of the specified type.
1343
     *
1344
     * @param ClassMetadata $class
1345
     * @param array $documents Array of documents to delete
1346
     * @param array $options Array of options to be used with remove()
1347
     */
1348 61
    private function executeDeletions(ClassMetadata $class, array $documents, array $options = array())
1349
    {
1350 61
        $hasPostRemoveLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postRemove]);
1351 61
        $hasPostRemoveListeners = $this->evm->hasListeners(Events::postRemove);
1352
1353 61
        $persister = $this->getDocumentPersister($class->name);
1354
1355 61
        foreach ($documents as $oid => $document) {
1356 61
            if ( ! $class->isEmbeddedDocument) {
1357 28
                $persister->delete($document, $options);
1358 26
            }
1359
            unset(
1360 59
                $this->documentDeletions[$oid],
1361 59
                $this->documentIdentifiers[$oid],
1362 59
                $this->originalDocumentData[$oid]
1363
            );
1364
1365
            // Clear snapshot information for any referenced PersistentCollection
1366
            // http://www.doctrine-project.org/jira/browse/MODM-95
1367 59
            foreach ($class->associationMappings as $fieldMapping) {
1368 41
                if (isset($fieldMapping['type']) && $fieldMapping['type'] === ClassMetadata::MANY) {
1369 26
                    $value = $class->reflFields[$fieldMapping['fieldName']]->getValue($document);
1370 26
                    if ($value instanceof PersistentCollection) {
1371 22
                        $value->clearSnapshot();
1372 22
                    }
1373 26
                }
1374 59
            }
1375
1376
            // Document with this $oid after deletion treated as NEW, even if the $oid
1377
            // is obtained by a new document because the old one went out of scope.
1378 59
            $this->documentStates[$oid] = self::STATE_NEW;
1379
1380 59
            if ($hasPostRemoveLifecycleCallbacks) {
1381 8
                $class->invokeLifecycleCallbacks(Events::postRemove, $document);
1382 8
            }
1383 59
            if ($hasPostRemoveListeners) {
1384 2
                $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($document, $this->dm));
1385 2
            }
1386 59
        }
1387 59
    }
1388
1389
    /**
1390
     * Schedules a document for insertion into the database.
1391
     * If the document already has an identifier, it will be added to the
1392
     * identity map.
1393
     *
1394
     * @param ClassMetadata $class
1395
     * @param object $document The document to schedule for insertion.
1396
     * @throws \InvalidArgumentException
1397
     */
1398 488
    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...
1399
    {
1400 488
        $oid = spl_object_hash($document);
1401
1402 488
        if (isset($this->documentUpdates[$oid])) {
1403
            throw new \InvalidArgumentException("Dirty document can not be scheduled for insertion.");
1404
        }
1405 488
        if (isset($this->documentDeletions[$oid])) {
1406
            throw new \InvalidArgumentException("Removed document can not be scheduled for insertion.");
1407
        }
1408 488
        if (isset($this->documentInsertions[$oid])) {
1409
            throw new \InvalidArgumentException("Document can not be scheduled for insertion twice.");
1410
        }
1411
1412 488
        $this->documentInsertions[$oid] = $document;
1413
1414 488
        if (isset($this->documentIdentifiers[$oid])) {
1415 485
            $this->addToIdentityMap($document);
1416 485
        }
1417 488
    }
1418
1419
    /**
1420
     * Schedules a document for upsert into the database and adds it to the
1421
     * identity map
1422
     *
1423
     * @param ClassMetadata $class
1424
     * @param object $document The document to schedule for upsert.
1425
     * @throws \InvalidArgumentException
1426
     */
1427 83
    public function scheduleForUpsert(ClassMetadata $class, $document)
1428
    {
1429 83
        $oid = spl_object_hash($document);
1430
1431 83
        if ($class->isEmbeddedDocument) {
1432
            throw new \InvalidArgumentException("Embedded document can not be scheduled for upsert.");
1433
        }
1434 83
        if (isset($this->documentUpdates[$oid])) {
1435
            throw new \InvalidArgumentException("Dirty document can not be scheduled for upsert.");
1436
        }
1437 83
        if (isset($this->documentDeletions[$oid])) {
1438
            throw new \InvalidArgumentException("Removed document can not be scheduled for upsert.");
1439
        }
1440 83
        if (isset($this->documentUpserts[$oid])) {
1441
            throw new \InvalidArgumentException("Document can not be scheduled for upsert twice.");
1442
        }
1443
1444 83
        $this->documentUpserts[$oid] = $document;
1445 83
        $this->documentIdentifiers[$oid] = $class->getIdentifierValue($document);
1446 83
        $this->addToIdentityMap($document);
1447 83
    }
1448
1449
    /**
1450
     * Checks whether a document is scheduled for insertion.
1451
     *
1452
     * @param object $document
1453
     * @return boolean
1454
     */
1455 70
    public function isScheduledForInsert($document)
1456
    {
1457 70
        return isset($this->documentInsertions[spl_object_hash($document)]);
1458
    }
1459
1460
    /**
1461
     * Checks whether a document is scheduled for upsert.
1462
     *
1463
     * @param object $document
1464
     * @return boolean
1465
     */
1466 5
    public function isScheduledForUpsert($document)
1467
    {
1468 5
        return isset($this->documentUpserts[spl_object_hash($document)]);
1469
    }
1470
1471
    /**
1472
     * Schedules a document for being updated.
1473
     *
1474
     * @param object $document The document to schedule for being updated.
1475
     * @throws \InvalidArgumentException
1476
     */
1477 215
    public function scheduleForUpdate($document)
1478
    {
1479 215
        $oid = spl_object_hash($document);
1480 215
        if ( ! isset($this->documentIdentifiers[$oid])) {
1481
            throw new \InvalidArgumentException("Document has no identity.");
1482
        }
1483
1484 215
        if (isset($this->documentDeletions[$oid])) {
1485
            throw new \InvalidArgumentException("Document is removed.");
1486
        }
1487
1488 215
        if ( ! isset($this->documentUpdates[$oid])
1489 215
            && ! isset($this->documentInsertions[$oid])
1490 215
            && ! isset($this->documentUpserts[$oid])) {
1491 211
            $this->documentUpdates[$oid] = $document;
1492 211
        }
1493 215
    }
1494
1495
    /**
1496
     * Checks whether a document is registered as dirty in the unit of work.
1497
     * Note: Is not very useful currently as dirty documents are only registered
1498
     * at commit time.
1499
     *
1500
     * @param object $document
1501
     * @return boolean
1502
     */
1503 13
    public function isScheduledForUpdate($document)
1504
    {
1505 13
        return isset($this->documentUpdates[spl_object_hash($document)]);
1506
    }
1507
1508 1
    public function isScheduledForSynchronization($document)
1509
    {
1510 1
        $class = $this->dm->getClassMetadata(get_class($document));
1511 1
        return isset($this->scheduledForSynchronization[$class->name][spl_object_hash($document)]);
1512
    }
1513
1514
    /**
1515
     * @deprecated to be removed in 2.0
1516
     * @since 1.1
1517
     */
1518
    public function isScheduledForDirtyCheck($document)
1519
    {
1520
        return $this->isScheduledForSynchronization($document);
1521
    }
1522
1523
    /**
1524
     * INTERNAL:
1525
     * Schedules a document for deletion.
1526
     *
1527
     * @param object $document
1528
     */
1529 66
    public function scheduleForDelete($document)
1530
    {
1531 66
        $oid = spl_object_hash($document);
1532
1533 66
        if (isset($this->documentInsertions[$oid])) {
1534 2
            if ($this->isInIdentityMap($document)) {
1535 2
                $this->removeFromIdentityMap($document);
1536 2
            }
1537 2
            unset($this->documentInsertions[$oid]);
1538 2
            return; // document has not been persisted yet, so nothing more to do.
1539
        }
1540
1541 65
        if ( ! $this->isInIdentityMap($document)) {
1542 1
            return; // ignore
1543
        }
1544
1545 64
        $this->removeFromIdentityMap($document);
1546 64
        $this->documentStates[$oid] = self::STATE_REMOVED;
1547
1548 64
        if (isset($this->documentUpdates[$oid])) {
1549
            unset($this->documentUpdates[$oid]);
1550
        }
1551 64
        if ( ! isset($this->documentDeletions[$oid])) {
1552 64
            $this->documentDeletions[$oid] = $document;
1553 64
        }
1554 64
    }
1555
1556
    /**
1557
     * Checks whether a document is registered as removed/deleted with the unit
1558
     * of work.
1559
     *
1560
     * @param object $document
1561
     * @return boolean
1562
     */
1563 8
    public function isScheduledForDelete($document)
1564
    {
1565 8
        return isset($this->documentDeletions[spl_object_hash($document)]);
1566
    }
1567
1568
    /**
1569
     * Checks whether a document is scheduled for insertion, update or deletion.
1570
     *
1571
     * @param $document
1572
     * @return boolean
1573
     */
1574 206
    public function isDocumentScheduled($document)
1575
    {
1576 206
        $oid = spl_object_hash($document);
1577 206
        return isset($this->documentInsertions[$oid]) ||
1578 113
            isset($this->documentUpserts[$oid]) ||
1579 104
            isset($this->documentUpdates[$oid]) ||
1580 206
            isset($this->documentDeletions[$oid]);
1581
    }
1582
1583
    /**
1584
     * INTERNAL:
1585
     * Registers a document in the identity map.
1586
     *
1587
     * Note that documents in a hierarchy are registered with the class name of
1588
     * the root document. Identifiers are serialized before being used as array
1589
     * keys to allow differentiation of equal, but not identical, values.
1590
     *
1591
     * @ignore
1592
     * @param object $document  The document to register.
1593
     * @return boolean  TRUE if the registration was successful, FALSE if the identity of
1594
     *                  the document in question is already managed.
1595
     */
1596 579
    public function addToIdentityMap($document)
1597
    {
1598 579
        $class = $this->dm->getClassMetadata(get_class($document));
1599 579
        $id = $this->getIdForIdentityMap($document);
1600
1601 579
        if (isset($this->identityMap[$class->name][$id])) {
1602 47
            return false;
1603
        }
1604
1605 579
        $this->identityMap[$class->name][$id] = $document;
1606
1607 579
        if ($document instanceof NotifyPropertyChanged &&
1608 579
            ( ! $document instanceof Proxy || $document->__isInitialized())) {
1609 3
            $document->addPropertyChangedListener($this);
1610 3
        }
1611
1612 579
        return true;
1613
    }
1614
1615
    /**
1616
     * Gets the state of a document with regard to the current unit of work.
1617
     *
1618
     * @param object   $document
1619
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1620
     *                         This parameter can be set to improve performance of document state detection
1621
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1622
     *                         is either known or does not matter for the caller of the method.
1623
     * @return int The document state.
1624
     */
1625 555
    public function getDocumentState($document, $assume = null)
1626
    {
1627 555
        $oid = spl_object_hash($document);
1628
1629 555
        if (isset($this->documentStates[$oid])) {
1630 333
            return $this->documentStates[$oid];
1631
        }
1632
1633 555
        $class = $this->dm->getClassMetadata(get_class($document));
1634
1635 555
        if ($class->isEmbeddedDocument) {
1636 169
            return self::STATE_NEW;
1637
        }
1638
1639 552
        if ($assume !== null) {
1640 549
            return $assume;
1641
        }
1642
1643
        /* State can only be NEW or DETACHED, because MANAGED/REMOVED states are
1644
         * known. Note that you cannot remember the NEW or DETACHED state in
1645
         * _documentStates since the UoW does not hold references to such
1646
         * objects and the object hash can be reused. More generally, because
1647
         * the state may "change" between NEW/DETACHED without the UoW being
1648
         * aware of it.
1649
         */
1650 4
        $id = $class->getIdentifierObject($document);
1651
1652 4
        if ($id === null) {
1653 2
            return self::STATE_NEW;
1654
        }
1655
1656
        // Check for a version field, if available, to avoid a DB lookup.
1657 2
        if ($class->isVersioned) {
1658
            return ($class->getFieldValue($document, $class->versionField))
1659
                ? self::STATE_DETACHED
1660
                : self::STATE_NEW;
1661
        }
1662
1663
        // Last try before DB lookup: check the identity map.
1664 2
        if ($this->tryGetById($id, $class)) {
1665 1
            return self::STATE_DETACHED;
1666
        }
1667
1668
        // DB lookup
1669 2
        if ($this->getDocumentPersister($class->name)->exists($document)) {
1670 1
            return self::STATE_DETACHED;
1671
        }
1672
1673 1
        return self::STATE_NEW;
1674
    }
1675
1676
    /**
1677
     * INTERNAL:
1678
     * Removes a document from the identity map. This effectively detaches the
1679
     * document from the persistence management of Doctrine.
1680
     *
1681
     * @ignore
1682
     * @param object $document
1683
     * @throws \InvalidArgumentException
1684
     * @return boolean
1685
     */
1686 75
    public function removeFromIdentityMap($document)
1687
    {
1688 75
        $oid = spl_object_hash($document);
1689
1690
        // Check if id is registered first
1691 75
        if ( ! isset($this->documentIdentifiers[$oid])) {
1692
            return false;
1693
        }
1694
1695 75
        $class = $this->dm->getClassMetadata(get_class($document));
1696 75
        $id = $this->getIdForIdentityMap($document);
1697
1698 75
        if (isset($this->identityMap[$class->name][$id])) {
1699 75
            unset($this->identityMap[$class->name][$id]);
1700 75
            $this->documentStates[$oid] = self::STATE_DETACHED;
1701 75
            return true;
1702
        }
1703
1704
        return false;
1705
    }
1706
1707
    /**
1708
     * INTERNAL:
1709
     * Gets a document in the identity map by its identifier hash.
1710
     *
1711
     * @ignore
1712
     * @param mixed         $id    Document identifier
1713
     * @param ClassMetadata $class Document class
1714
     * @return object
1715
     * @throws InvalidArgumentException if the class does not have an identifier
1716
     */
1717 31
    public function getById($id, ClassMetadata $class)
1718
    {
1719 31
        if ( ! $class->identifier) {
1720
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1721
        }
1722
1723 31
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1724
1725 31
        return $this->identityMap[$class->name][$serializedId];
1726
    }
1727
1728
    /**
1729
     * INTERNAL:
1730
     * Tries to get a document by its identifier hash. If no document is found
1731
     * for the given hash, FALSE is returned.
1732
     *
1733
     * @ignore
1734
     * @param mixed         $id    Document identifier
1735
     * @param ClassMetadata $class Document class
1736
     * @return mixed The found document or FALSE.
1737
     * @throws InvalidArgumentException if the class does not have an identifier
1738
     */
1739 288
    public function tryGetById($id, ClassMetadata $class)
1740
    {
1741 288
        if ( ! $class->identifier) {
1742
            throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1743
        }
1744
1745 288
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1746
1747 288
        return isset($this->identityMap[$class->name][$serializedId]) ?
1748 288
            $this->identityMap[$class->name][$serializedId] : false;
1749
    }
1750
1751
    /**
1752
     * Schedules a document for dirty-checking at commit-time.
1753
     *
1754
     * @param object $document The document to schedule for dirty-checking.
1755
     */
1756 2
    public function scheduleForSynchronization($document)
1757
    {
1758 2
        $class = $this->dm->getClassMetadata(get_class($document));
1759 2
        $this->scheduledForSynchronization[$class->name][spl_object_hash($document)] = $document;
1760 2
    }
1761
1762
    /**
1763
     * @deprecated to be removed in 2.0
1764
     * @since 1.1
1765
     */
1766
    public function scheduleForDirtyCheck($document)
1767
    {
1768
        $this->scheduleForSynchronization($document);
1769
    }
1770
1771
    /**
1772
     * Checks whether a document is registered in the identity map.
1773
     *
1774
     * @param object $document
1775
     * @return boolean
1776
     */
1777 75
    public function isInIdentityMap($document)
1778
    {
1779 75
        $oid = spl_object_hash($document);
1780
1781 75
        if ( ! isset($this->documentIdentifiers[$oid])) {
1782 4
            return false;
1783
        }
1784
1785 74
        $class = $this->dm->getClassMetadata(get_class($document));
1786 74
        $id = $this->getIdForIdentityMap($document);
1787
1788 74
        return isset($this->identityMap[$class->name][$id]);
1789
    }
1790
1791
    /**
1792
     * @param object $document
1793
     * @return string
1794
     */
1795 579
    private function getIdForIdentityMap($document)
1796
    {
1797 579
        $class = $this->dm->getClassMetadata(get_class($document));
1798
1799 579
        if ( ! $class->identifier) {
1800 145
            $id = spl_object_hash($document);
1801 145
        } else {
1802 578
            $id = $this->documentIdentifiers[spl_object_hash($document)];
1803 578
            $id = serialize($class->getDatabaseIdentifierValue($id));
1804
        }
1805
1806 579
        return $id;
1807
    }
1808
1809
    /**
1810
     * INTERNAL:
1811
     * Checks whether an identifier exists in the identity map.
1812
     *
1813
     * @ignore
1814
     * @param string $id
1815
     * @param string $rootClassName
1816
     * @return boolean
1817
     */
1818
    public function containsId($id, $rootClassName)
1819
    {
1820
        return isset($this->identityMap[$rootClassName][serialize($id)]);
1821
    }
1822
1823
    /**
1824
     * Persists a document as part of the current unit of work.
1825
     *
1826
     * @param object $document The document to persist.
1827
     * @throws MongoDBException If trying to persist MappedSuperclass.
1828
     * @throws \InvalidArgumentException If there is something wrong with document's identifier.
1829
     */
1830 550
    public function persist($document)
1831
    {
1832 550
        $class = $this->dm->getClassMetadata(get_class($document));
1833 550
        if ($class->isMappedSuperclass) {
1834 1
            throw MongoDBException::cannotPersistMappedSuperclass($class->name);
1835
        }
1836 549
        $visited = array();
1837 549
        $this->doPersist($document, $visited);
1838 545
    }
1839
1840
    /**
1841
     * Saves a document as part of the current unit of work.
1842
     * This method is internally called during save() cascades as it tracks
1843
     * the already visited documents to prevent infinite recursions.
1844
     *
1845
     * NOTE: This method always considers documents that are not yet known to
1846
     * this UnitOfWork as NEW.
1847
     *
1848
     * @param object $document The document to persist.
1849
     * @param array $visited The already visited documents.
1850
     * @throws \InvalidArgumentException
1851
     * @throws MongoDBException
1852
     */
1853 549
    private function doPersist($document, array &$visited)
1854
    {
1855 549
        $oid = spl_object_hash($document);
1856 549
        if (isset($visited[$oid])) {
1857 24
            return; // Prevent infinite recursion
1858
        }
1859
1860 549
        $visited[$oid] = $document; // Mark visited
1861
1862 549
        $class = $this->dm->getClassMetadata(get_class($document));
1863
1864 549
        $documentState = $this->getDocumentState($document, self::STATE_NEW);
1865
        switch ($documentState) {
1866 549
            case self::STATE_MANAGED:
1867
                // Nothing to do, except if policy is "deferred explicit"
1868 43
                if ($class->isChangeTrackingDeferredExplicit()) {
1869
                    $this->scheduleForSynchronization($document);
1870
                }
1871 43
                break;
1872 549
            case self::STATE_NEW:
1873 549
                $this->persistNew($class, $document);
1874 547
                break;
1875
1876 2
            case self::STATE_REMOVED:
1877
                // Document becomes managed again
1878 2
                unset($this->documentDeletions[$oid]);
1879
1880 2
                $this->documentStates[$oid] = self::STATE_MANAGED;
1881 2
                break;
1882
1883
            case self::STATE_DETACHED:
1884
                throw new \InvalidArgumentException(
1885
                    "Behavior of persist() for a detached document is not yet defined.");
1886
                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...
1887
1888
            default:
1889
                throw MongoDBException::invalidDocumentState($documentState);
1890
        }
1891
1892 547
        $this->cascadePersist($document, $visited);
1893 545
    }
1894
1895
    /**
1896
     * Deletes a document as part of the current unit of work.
1897
     *
1898
     * @param object $document The document to remove.
1899
     */
1900 65
    public function remove($document)
1901
    {
1902 65
        $visited = array();
1903 65
        $this->doRemove($document, $visited);
1904 65
    }
1905
1906
    /**
1907
     * Deletes a document as part of the current unit of work.
1908
     *
1909
     * This method is internally called during delete() cascades as it tracks
1910
     * the already visited documents to prevent infinite recursions.
1911
     *
1912
     * @param object $document The document to delete.
1913
     * @param array $visited The map of the already visited documents.
1914
     * @throws MongoDBException
1915
     */
1916 65
    private function doRemove($document, array &$visited)
1917
    {
1918 65
        $oid = spl_object_hash($document);
1919 65
        if (isset($visited[$oid])) {
1920 1
            return; // Prevent infinite recursion
1921
        }
1922
1923 65
        $visited[$oid] = $document; // mark visited
1924
1925
        /* Cascade first, because scheduleForDelete() removes the entity from
1926
         * the identity map, which can cause problems when a lazy Proxy has to
1927
         * be initialized for the cascade operation.
1928
         */
1929 65
        $this->cascadeRemove($document, $visited);
1930
1931 65
        $class = $this->dm->getClassMetadata(get_class($document));
1932 65
        $documentState = $this->getDocumentState($document);
1933
        switch ($documentState) {
1934 65
            case self::STATE_NEW:
1935 65
            case self::STATE_REMOVED:
1936
                // nothing to do
1937 1
                break;
1938 65
            case self::STATE_MANAGED:
1939 65
                if ( ! empty($class->lifecycleCallbacks[Events::preRemove])) {
1940 8
                    $class->invokeLifecycleCallbacks(Events::preRemove, $document);
1941 8
                }
1942 65 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...
1943 1
                    $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($document, $this->dm));
1944 1
                }
1945 65
                $this->scheduleForDelete($document);
1946 65
                break;
1947
            case self::STATE_DETACHED:
1948
                throw MongoDBException::detachedDocumentCannotBeRemoved();
1949
            default:
1950
                throw MongoDBException::invalidDocumentState($documentState);
1951
        }
1952 65
    }
1953
1954
    /**
1955
     * Merges the state of the given detached document into this UnitOfWork.
1956
     *
1957
     * @param object $document
1958
     * @return object The managed copy of the document.
1959
     */
1960 13
    public function merge($document)
1961
    {
1962 13
        $visited = array();
1963
1964 13
        return $this->doMerge($document, $visited);
1965
    }
1966
1967
    /**
1968
     * Executes a merge operation on a document.
1969
     *
1970
     * @param object      $document
1971
     * @param array       $visited
1972
     * @param object|null $prevManagedCopy
1973
     * @param array|null  $assoc
1974
     *
1975
     * @return object The managed copy of the document.
1976
     *
1977
     * @throws InvalidArgumentException If the entity instance is NEW.
1978
     * @throws LockException If the document uses optimistic locking through a
1979
     *                       version attribute and the version check against the
1980
     *                       managed copy fails.
1981
     */
1982 13
    private function doMerge($document, array &$visited, $prevManagedCopy = null, $assoc = null)
1983
    {
1984 13
        $oid = spl_object_hash($document);
1985
1986 13
        if (isset($visited[$oid])) {
1987 1
            return $visited[$oid]; // Prevent infinite recursion
1988
        }
1989
1990 13
        $visited[$oid] = $document; // mark visited
1991
1992 13
        $class = $this->dm->getClassMetadata(get_class($document));
1993
1994
        /* First we assume DETACHED, although it can still be NEW but we can
1995
         * avoid an extra DB round trip this way. If it is not MANAGED but has
1996
         * an identity, we need to fetch it from the DB anyway in order to
1997
         * merge. MANAGED documents are ignored by the merge operation.
1998
         */
1999 13
        $managedCopy = $document;
2000
2001 13
        if ($this->getDocumentState($document, self::STATE_DETACHED) !== self::STATE_MANAGED) {
2002 13
            if ($document instanceof Proxy && ! $document->__isInitialized()) {
2003
                $document->__load();
2004
            }
2005
2006
            // Try to look the document up in the identity map.
2007 13
            $id = $class->isEmbeddedDocument ? null : $class->getIdentifierObject($document);
2008
2009 13
            if ($id === null) {
2010
                // If there is no identifier, it is actually NEW.
2011 5
                $managedCopy = $class->newInstance();
2012 5
                $this->persistNew($class, $managedCopy);
2013 5
            } else {
2014 10
                $managedCopy = $this->tryGetById($id, $class);
2015
2016 10
                if ($managedCopy) {
2017
                    // We have the document in memory already, just make sure it is not removed.
2018 5
                    if ($this->getDocumentState($managedCopy) === self::STATE_REMOVED) {
2019
                        throw new \InvalidArgumentException('Removed entity detected during merge. Cannot merge with a removed entity.');
2020
                    }
2021 5
                } else {
2022
                    // We need to fetch the managed copy in order to merge.
2023 7
                    $managedCopy = $this->dm->find($class->name, $id);
2024
                }
2025
2026 10
                if ($managedCopy === null) {
2027
                    // If the identifier is ASSIGNED, it is NEW
2028
                    $managedCopy = $class->newInstance();
2029
                    $class->setIdentifierValue($managedCopy, $id);
2030
                    $this->persistNew($class, $managedCopy);
2031
                } else {
2032 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...
2033
                        $managedCopy->__load();
2034
                    }
2035
                }
2036
            }
2037
2038 13
            if ($class->isVersioned) {
2039
                $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
2040
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2041
2042
                // Throw exception if versions don't match
2043
                if ($managedCopyVersion != $documentVersion) {
2044
                    throw LockException::lockFailedVersionMissmatch($document, $documentVersion, $managedCopyVersion);
2045
                }
2046
            }
2047
2048
            // Merge state of $document into existing (managed) document
2049 13
            foreach ($class->reflClass->getProperties() as $prop) {
2050 13
                $name = $prop->name;
2051 13
                $prop->setAccessible(true);
2052 13
                if ( ! isset($class->associationMappings[$name])) {
2053 13
                    if ( ! $class->isIdentifier($name)) {
2054 13
                        $prop->setValue($managedCopy, $prop->getValue($document));
2055 13
                    }
2056 13
                } else {
2057 13
                    $assoc2 = $class->associationMappings[$name];
2058
2059 13
                    if ($assoc2['type'] === 'one') {
2060 5
                        $other = $prop->getValue($document);
2061
2062 5
                        if ($other === null) {
2063 2
                            $prop->setValue($managedCopy, null);
2064 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...
2065
                            // Do not merge fields marked lazy that have not been fetched
2066 1
                            continue;
2067 3
                        } elseif ( ! $assoc2['isCascadeMerge']) {
2068
                            if ($this->getDocumentState($other) === self::STATE_DETACHED) {
2069
                                $targetDocument = isset($assoc2['targetDocument']) ? $assoc2['targetDocument'] : get_class($other);
2070
                                /* @var $targetClass \Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo */
2071
                                $targetClass = $this->dm->getClassMetadata($targetDocument);
2072
                                $relatedId = $targetClass->getIdentifierObject($other);
2073
2074
                                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...
2075
                                    $other = $this->dm->find($targetClass->name, $relatedId);
2076
                                } else {
2077
                                    $other = $this
2078
                                        ->dm
2079
                                        ->getProxyFactory()
2080
                                        ->getProxy($assoc2['targetDocument'], array($targetClass->identifier => $relatedId));
2081
                                    $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...
2082
                                }
2083
                            }
2084
2085
                            $prop->setValue($managedCopy, $other);
2086
                        }
2087 4
                    } else {
2088 10
                        $mergeCol = $prop->getValue($document);
2089
2090 10
                        if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) {
2091
                            /* Do not merge fields marked lazy that have not
2092
                             * been fetched. Keep the lazy persistent collection
2093
                             * of the managed copy.
2094
                             */
2095 3
                            continue;
2096
                        }
2097
2098 7
                        $managedCol = $prop->getValue($managedCopy);
2099
2100 7
                        if ( ! $managedCol) {
2101 2
                            $managedCol = new PersistentCollection(new ArrayCollection(), $this->dm, $this);
2102 2
                            $managedCol->setOwner($managedCopy, $assoc2);
2103 2
                            $prop->setValue($managedCopy, $managedCol);
2104 2
                            $this->originalDocumentData[$oid][$name] = $managedCol;
2105 2
                        }
2106
2107
                        /* Note: do not process association's target documents.
2108
                         * They will be handled during the cascade. Initialize
2109
                         * and, if necessary, clear $managedCol for now.
2110
                         */
2111 7
                        if ($assoc2['isCascadeMerge']) {
2112 7
                            $managedCol->initialize();
2113
2114
                            // If $managedCol differs from the merged collection, clear and set dirty
2115 7
                            if ( ! $managedCol->isEmpty() && $managedCol !== $mergeCol) {
2116 2
                                $managedCol->unwrap()->clear();
2117 2
                                $managedCol->setDirty(true);
2118
2119 2
                                if ($assoc2['isOwningSide'] && $class->isChangeTrackingNotify()) {
2120
                                    $this->scheduleForSynchronization($managedCopy);
2121
                                }
2122 2
                            }
2123 7
                        }
2124
                    }
2125
                }
2126
2127 13
                if ($class->isChangeTrackingNotify()) {
2128
                    // Just treat all properties as changed, there is no other choice.
2129
                    $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy));
2130
                }
2131 13
            }
2132
2133 13
            if ($class->isChangeTrackingDeferredExplicit()) {
2134
                $this->scheduleForSynchronization($document);
2135
            }
2136 13
        }
2137
2138 13
        if ($prevManagedCopy !== null) {
2139 6
            $assocField = $assoc['fieldName'];
2140 6
            $prevClass = $this->dm->getClassMetadata(get_class($prevManagedCopy));
2141
2142 6
            if ($assoc['type'] === 'one') {
2143 2
                $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
2144 2
            } else {
2145 4
                $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
2146
2147 4
                if ($assoc['type'] === 'many' && isset($assoc['mappedBy'])) {
2148 1
                    $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy);
2149 1
                }
2150
            }
2151 6
        }
2152
2153
        // Mark the managed copy visited as well
2154 13
        $visited[spl_object_hash($managedCopy)] = true;
2155
2156 13
        $this->cascadeMerge($document, $managedCopy, $visited);
2157
2158 13
        return $managedCopy;
2159
    }
2160
2161
    /**
2162
     * Detaches a document from the persistence management. It's persistence will
2163
     * no longer be managed by Doctrine.
2164
     *
2165
     * @param object $document The document to detach.
2166
     */
2167 9
    public function detach($document)
2168
    {
2169 9
        $visited = array();
2170 9
        $this->doDetach($document, $visited);
2171 9
    }
2172
2173
    /**
2174
     * Executes a detach operation on the given document.
2175
     *
2176
     * @param object $document
2177
     * @param array $visited
2178
     * @internal This method always considers documents with an assigned identifier as DETACHED.
2179
     */
2180 12
    private function doDetach($document, array &$visited)
2181
    {
2182 12
        $oid = spl_object_hash($document);
2183 12
        if (isset($visited[$oid])) {
2184 4
            return; // Prevent infinite recursion
2185
        }
2186
2187 12
        $visited[$oid] = $document; // mark visited
2188
2189 12
        switch ($this->getDocumentState($document, self::STATE_DETACHED)) {
2190 12
            case self::STATE_MANAGED:
2191 12
                $this->removeFromIdentityMap($document);
2192 12
                unset($this->documentInsertions[$oid], $this->documentUpdates[$oid],
2193 12
                    $this->documentDeletions[$oid], $this->documentIdentifiers[$oid],
2194 12
                    $this->documentStates[$oid], $this->originalDocumentData[$oid],
2195 12
                    $this->parentAssociations[$oid], $this->documentUpserts[$oid],
2196 12
                    $this->hasScheduledCollections[$oid]);
2197 12
                break;
2198 4
            case self::STATE_NEW:
2199 4
            case self::STATE_DETACHED:
2200 4
                return;
2201 12
        }
2202
2203 12
        $this->cascadeDetach($document, $visited);
2204 12
    }
2205
2206
    /**
2207
     * Refreshes the state of the given document from the database, overwriting
2208
     * any local, unpersisted changes.
2209
     *
2210
     * @param object $document The document to refresh.
2211
     * @throws \InvalidArgumentException If the document is not MANAGED.
2212
     */
2213 12
    public function refresh($document)
2214
    {
2215 12
        $visited = array();
2216 12
        $this->doRefresh($document, $visited);
2217 11
    }
2218
2219
    /**
2220
     * Executes a refresh operation on a document.
2221
     *
2222
     * @param object $document The document to refresh.
2223
     * @param array $visited The already visited documents during cascades.
2224
     * @throws \InvalidArgumentException If the document is not MANAGED.
2225
     */
2226 12
    private function doRefresh($document, array &$visited)
2227
    {
2228 12
        $oid = spl_object_hash($document);
2229 12
        if (isset($visited[$oid])) {
2230
            return; // Prevent infinite recursion
2231
        }
2232
2233 12
        $visited[$oid] = $document; // mark visited
2234
2235 12
        $class = $this->dm->getClassMetadata(get_class($document));
2236
2237 12
        if ( ! $class->isEmbeddedDocument) {
2238 12
            if ($this->getDocumentState($document) == self::STATE_MANAGED) {
2239 11
                $id = $class->getDatabaseIdentifierValue($this->documentIdentifiers[$oid]);
2240 11
                $this->getDocumentPersister($class->name)->refresh($id, $document);
2241 11
            } else {
2242 1
                throw new \InvalidArgumentException("Document is not MANAGED.");
2243
            }
2244 11
        }
2245
2246 11
        $this->cascadeRefresh($document, $visited);
2247 11
    }
2248
2249
    /**
2250
     * Cascades a refresh operation to associated documents.
2251
     *
2252
     * @param object $document
2253
     * @param array $visited
2254
     */
2255 11
    private function cascadeRefresh($document, array &$visited)
2256
    {
2257 11
        $class = $this->dm->getClassMetadata(get_class($document));
2258
2259 11
        $associationMappings = array_filter(
2260 11
            $class->associationMappings,
2261
            function ($assoc) { return $assoc['isCascadeRefresh']; }
2262 11
        );
2263
2264 11
        foreach ($associationMappings as $mapping) {
2265 7
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2266 7
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2267 7
                if ($relatedDocuments instanceof PersistentCollection) {
2268
                    // Unwrap so that foreach() does not initialize
2269 7
                    $relatedDocuments = $relatedDocuments->unwrap();
2270 7
                }
2271 7
                foreach ($relatedDocuments as $relatedDocument) {
2272
                    $this->doRefresh($relatedDocument, $visited);
2273 7
                }
2274 7
            } elseif ($relatedDocuments !== null) {
2275
                $this->doRefresh($relatedDocuments, $visited);
2276
            }
2277 11
        }
2278 11
    }
2279
2280
    /**
2281
     * Cascades a detach operation to associated documents.
2282
     *
2283
     * @param object $document
2284
     * @param array $visited
2285
     */
2286 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...
2287
    {
2288 12
        $class = $this->dm->getClassMetadata(get_class($document));
2289 12
        foreach ($class->fieldMappings as $mapping) {
2290 12
            if ( ! $mapping['isCascadeDetach']) {
2291 12
                continue;
2292
            }
2293 7
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2294 7
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2295 7
                if ($relatedDocuments instanceof PersistentCollection) {
2296
                    // Unwrap so that foreach() does not initialize
2297 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2298 6
                }
2299 7
                foreach ($relatedDocuments as $relatedDocument) {
2300 5
                    $this->doDetach($relatedDocument, $visited);
2301 7
                }
2302 7
            } elseif ($relatedDocuments !== null) {
2303 5
                $this->doDetach($relatedDocuments, $visited);
2304 5
            }
2305 12
        }
2306 12
    }
2307
    /**
2308
     * Cascades a merge operation to associated documents.
2309
     *
2310
     * @param object $document
2311
     * @param object $managedCopy
2312
     * @param array $visited
2313
     */
2314 13
    private function cascadeMerge($document, $managedCopy, array &$visited)
2315
    {
2316 13
        $class = $this->dm->getClassMetadata(get_class($document));
2317
2318 13
        $associationMappings = array_filter(
2319 13
            $class->associationMappings,
2320
            function ($assoc) { return $assoc['isCascadeMerge']; }
2321 13
        );
2322
2323 13
        foreach ($associationMappings as $assoc) {
2324 12
            $relatedDocuments = $class->reflFields[$assoc['fieldName']]->getValue($document);
2325
2326 12
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2327 8
                if ($relatedDocuments === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
2328
                    // Collections are the same, so there is nothing to do
2329
                    continue;
2330
                }
2331
2332 8
                if ($relatedDocuments instanceof PersistentCollection) {
2333
                    // Unwrap so that foreach() does not initialize
2334 6
                    $relatedDocuments = $relatedDocuments->unwrap();
2335 6
                }
2336
2337 8
                foreach ($relatedDocuments as $relatedDocument) {
2338 4
                    $this->doMerge($relatedDocument, $visited, $managedCopy, $assoc);
2339 8
                }
2340 12
            } elseif ($relatedDocuments !== null) {
2341 3
                $this->doMerge($relatedDocuments, $visited, $managedCopy, $assoc);
2342 3
            }
2343 13
        }
2344 13
    }
2345
2346
    /**
2347
     * Cascades the save operation to associated documents.
2348
     *
2349
     * @param object $document
2350
     * @param array $visited
2351
     */
2352 547
    private function cascadePersist($document, array &$visited)
2353
    {
2354 547
        $class = $this->dm->getClassMetadata(get_class($document));
2355
2356 547
        $associationMappings = array_filter(
2357 547
            $class->associationMappings,
2358
            function ($assoc) { return $assoc['isCascadePersist']; }
2359 547
        );
2360
2361 547
        foreach ($associationMappings as $fieldName => $mapping) {
2362 371
            $relatedDocuments = $class->reflFields[$fieldName]->getValue($document);
2363
2364 371
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2365 321
                if ($relatedDocuments instanceof PersistentCollection) {
2366 16
                    if ($relatedDocuments->getOwner() !== $document) {
2367 2
                        $relatedDocuments = $this->fixPersistentCollectionOwnership($relatedDocuments, $document, $class, $mapping['fieldName']);
2368 2
                    }
2369
                    // Unwrap so that foreach() does not initialize
2370 16
                    $relatedDocuments = $relatedDocuments->unwrap();
2371 16
                }
2372
2373 321
                $count = 0;
2374 321
                foreach ($relatedDocuments as $relatedKey => $relatedDocument) {
2375 170
                    if ( ! empty($mapping['embedded'])) {
2376 107
                        list(, $knownParent, ) = $this->getParentAssociation($relatedDocument);
2377 107
                        if ($knownParent && $knownParent !== $document) {
2378 4
                            $relatedDocument = clone $relatedDocument;
2379 4
                            $relatedDocuments[$relatedKey] = $relatedDocument;
2380 4
                        }
2381 107
                        $pathKey = ! isset($mapping['strategy']) || CollectionHelper::isList($mapping['strategy']) ? $count++ : $relatedKey;
2382 107
                        $this->setParentAssociation($relatedDocument, $mapping, $document, $mapping['fieldName'] . '.' . $pathKey);
2383 107
                    }
2384 170
                    $this->doPersist($relatedDocument, $visited);
2385 320
                }
2386 371
            } elseif ($relatedDocuments !== null) {
2387 120
                if ( ! empty($mapping['embedded'])) {
2388 66
                    list(, $knownParent, ) = $this->getParentAssociation($relatedDocuments);
2389 66
                    if ($knownParent && $knownParent !== $document) {
2390 5
                        $relatedDocuments = clone $relatedDocuments;
2391 5
                        $class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments);
2392 5
                    }
2393 66
                    $this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']);
2394 66
                }
2395 120
                $this->doPersist($relatedDocuments, $visited);
2396 119
            }
2397 546
        }
2398 545
    }
2399
2400
    /**
2401
     * Cascades the delete operation to associated documents.
2402
     *
2403
     * @param object $document
2404
     * @param array $visited
2405
     */
2406 65 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...
2407
    {
2408 65
        $class = $this->dm->getClassMetadata(get_class($document));
2409 65
        foreach ($class->fieldMappings as $mapping) {
2410 65
            if ( ! $mapping['isCascadeRemove']) {
2411 65
                continue;
2412
            }
2413 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...
2414 2
                $document->__load();
2415 2
            }
2416
2417 33
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
2418 33
            if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) {
2419
                // If its a PersistentCollection initialization is intended! No unwrap!
2420 24
                foreach ($relatedDocuments as $relatedDocument) {
2421 13
                    $this->doRemove($relatedDocument, $visited);
2422 24
                }
2423 33
            } elseif ($relatedDocuments !== null) {
2424 12
                $this->doRemove($relatedDocuments, $visited);
2425 12
            }
2426 65
        }
2427 65
    }
2428
2429
    /**
2430
     * Acquire a lock on the given document.
2431
     *
2432
     * @param object $document
2433
     * @param int $lockMode
2434
     * @param int $lockVersion
2435
     * @throws LockException
2436
     * @throws \InvalidArgumentException
2437
     */
2438 9
    public function lock($document, $lockMode, $lockVersion = null)
2439
    {
2440 9
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2441 1
            throw new \InvalidArgumentException("Document is not MANAGED.");
2442
        }
2443
2444 8
        $documentName = get_class($document);
2445 8
        $class = $this->dm->getClassMetadata($documentName);
2446
2447 8
        if ($lockMode == LockMode::OPTIMISTIC) {
2448 3
            if ( ! $class->isVersioned) {
2449 1
                throw LockException::notVersioned($documentName);
2450
            }
2451
2452 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...
2453 2
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
2454 2
                if ($documentVersion != $lockVersion) {
2455 1
                    throw LockException::lockFailedVersionMissmatch($document, $lockVersion, $documentVersion);
2456
                }
2457 1
            }
2458 6
        } elseif (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) {
2459 5
            $this->getDocumentPersister($class->name)->lock($document, $lockMode);
2460 5
        }
2461 6
    }
2462
2463
    /**
2464
     * Releases a lock on the given document.
2465
     *
2466
     * @param object $document
2467
     * @throws \InvalidArgumentException
2468
     */
2469 1
    public function unlock($document)
2470
    {
2471 1
        if ($this->getDocumentState($document) != self::STATE_MANAGED) {
2472
            throw new \InvalidArgumentException("Document is not MANAGED.");
2473
        }
2474 1
        $documentName = get_class($document);
2475 1
        $this->getDocumentPersister($documentName)->unlock($document);
2476 1
    }
2477
2478
    /**
2479
     * Clears the UnitOfWork.
2480
     *
2481
     * @param string|null $documentName if given, only documents of this type will get detached.
2482
     */
2483 375
    public function clear($documentName = null)
2484
    {
2485 375
        if ($documentName === null) {
2486 369
            $this->identityMap =
2487 369
            $this->documentIdentifiers =
2488 369
            $this->originalDocumentData =
2489 369
            $this->documentChangeSets =
2490 369
            $this->documentStates =
2491 369
            $this->scheduledForSynchronization =
2492 369
            $this->documentInsertions =
2493 369
            $this->documentUpserts =
2494 369
            $this->documentUpdates =
2495 369
            $this->documentDeletions =
2496 369
            $this->collectionUpdates =
2497 369
            $this->collectionDeletions =
2498 369
            $this->parentAssociations =
2499 369
            $this->orphanRemovals = 
2500 369
            $this->hasScheduledCollections = array();
2501 369
        } else {
2502 6
            $visited = array();
2503 6
            foreach ($this->identityMap as $className => $documents) {
2504 6
                if ($className === $documentName) {
2505 3
                    foreach ($documents as $document) {
2506 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...
2507 3
                    }
2508 3
                }
2509 6
            }
2510
        }
2511
2512 375 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...
2513
            $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->dm, $documentName));
2514
        }
2515 375
    }
2516
2517
    /**
2518
     * INTERNAL:
2519
     * Schedules an embedded document for removal. The remove() operation will be
2520
     * invoked on that document at the beginning of the next commit of this
2521
     * UnitOfWork.
2522
     *
2523
     * @ignore
2524
     * @param object $document
2525
     */
2526 48
    public function scheduleOrphanRemoval($document)
2527
    {
2528 48
        $this->orphanRemovals[spl_object_hash($document)] = $document;
2529 48
    }
2530
2531
    /**
2532
     * INTERNAL:
2533
     * Unschedules an embedded or referenced object for removal.
2534
     *
2535
     * @ignore
2536
     * @param object $document
2537
     */
2538 95
    public function unscheduleOrphanRemoval($document)
2539
    {
2540 95
        $oid = spl_object_hash($document);
2541 95
        if (isset($this->orphanRemovals[$oid])) {
2542 3
            unset($this->orphanRemovals[$oid]);
2543 3
        }
2544 95
    }
2545
2546
    /**
2547
     * Fixes PersistentCollection state if it wasn't used exactly as we had in mind:
2548
     *  1) sets owner if it was cloned
2549
     *  2) clones collection, sets owner, updates document's property and, if necessary, updates originalData
2550
     *  3) NOP if state is OK
2551
     * Returned collection should be used from now on (only important with 2nd point)
2552
     *
2553
     * @param PersistentCollection $coll
2554
     * @param object $document
2555
     * @param ClassMetadata $class
2556
     * @param string $propName
2557
     * @return PersistentCollection
2558
     */
2559 8
    private function fixPersistentCollectionOwnership(PersistentCollection $coll, $document, ClassMetadata $class, $propName)
2560
    {
2561 8
        $owner = $coll->getOwner();
2562 8
        if ($owner === null) { // cloned
2563 6
            $coll->setOwner($document, $class->fieldMappings[$propName]);
2564 8
        } elseif ($owner !== $document) { // no clone, we have to fix
2565 2
            if ( ! $coll->isInitialized()) {
2566 1
                $coll->initialize(); // we have to do this otherwise the cols share state
2567 1
            }
2568 2
            $newValue = clone $coll;
2569 2
            $newValue->setOwner($document, $class->fieldMappings[$propName]);
2570 2
            $class->reflFields[$propName]->setValue($document, $newValue);
2571 2
            if ($this->isScheduledForUpdate($document)) {
2572
                // @todo following line should be superfluous once collections are stored in change sets
2573
                $this->setOriginalDocumentProperty(spl_object_hash($document), $propName, $newValue);
2574
            }
2575 2
            return $newValue;
2576
        }
2577 6
        return $coll;
2578
    }
2579
2580
    /**
2581
     * INTERNAL:
2582
     * Schedules a complete collection for removal when this UnitOfWork commits.
2583
     *
2584
     * @param PersistentCollection $coll
2585
     */
2586 41
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2587
    {
2588 41
        $oid = spl_object_hash($coll);
2589 41
        unset($this->collectionUpdates[$oid]);
2590 41
        if ( ! isset($this->collectionDeletions[$oid])) {
2591 41
            $this->collectionDeletions[$oid] = $coll;
2592 41
            $this->scheduleCollectionOwner($coll);
2593 41
        }
2594 41
    }
2595
2596
    /**
2597
     * Checks whether a PersistentCollection is scheduled for deletion.
2598
     *
2599
     * @param PersistentCollection $coll
2600
     * @return boolean
2601
     */
2602 102
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2603
    {
2604 102
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2605
    }
2606
    
2607
    /**
2608
     * INTERNAL:
2609
     * Unschedules a collection from being deleted when this UnitOfWork commits.
2610
     * 
2611
     * @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
2612
     */
2613 188 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...
2614
    {
2615 188
        $oid = spl_object_hash($coll);
2616 188
        if (isset($this->collectionDeletions[$oid])) {
2617 11
            $topmostOwner = $this->getOwningDocument($coll->getOwner());
2618 11
            unset($this->collectionDeletions[$oid]);
2619 11
            unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2620 11
        }
2621 188
    }
2622
2623
    /**
2624
     * INTERNAL:
2625
     * Schedules a collection for update when this UnitOfWork commits.
2626
     *
2627
     * @param PersistentCollection $coll
2628
     */
2629 203
    public function scheduleCollectionUpdate(PersistentCollection $coll)
2630
    {
2631 203
        $mapping = $coll->getMapping();
2632 203
        if (CollectionHelper::usesSet($mapping['strategy'])) {
2633
            /* There is no need to $unset collection if it will be $set later
2634
             * This is NOP if collection is not scheduled for deletion
2635
             */
2636 34
            $this->unscheduleCollectionDeletion($coll);
2637 34
        }
2638 203
        $oid = spl_object_hash($coll);
2639 203
        if ( ! isset($this->collectionUpdates[$oid])) {
2640 203
            $this->collectionUpdates[$oid] = $coll;
2641 203
            $this->scheduleCollectionOwner($coll);
2642 203
        }
2643 203
    }
2644
    
2645
    /**
2646
     * INTERNAL:
2647
     * Unschedules a collection from being updated when this UnitOfWork commits.
2648
     * 
2649
     * @param \Doctrine\ODM\MongoDB\PersistentCollection $coll
2650
     */
2651 188 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...
2652
    {
2653 188
        $oid = spl_object_hash($coll);
2654 188
        if (isset($this->collectionUpdates[$oid])) {
2655 178
            $topmostOwner = $this->getOwningDocument($coll->getOwner());
2656 178
            unset($this->collectionUpdates[$oid]);
2657 178
            unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2658 178
        }
2659 188
    }
2660
    
2661
    /**
2662
     * Checks whether a PersistentCollection is scheduled for update.
2663
     *
2664
     * @param PersistentCollection $coll
2665
     * @return boolean
2666
     */
2667 114
    public function isCollectionScheduledForUpdate(PersistentCollection $coll)
2668
    {
2669 114
        return isset($this->collectionUpdates[spl_object_hash($coll)]);
2670
    }
2671
2672
    /**
2673
     * INTERNAL:
2674
     * Gets PersistentCollections that have been visited during computing change
2675
     * set of $document
2676
     *
2677
     * @param object $document
2678
     * @return PersistentCollection[]
2679
     */
2680 515
    public function getVisitedCollections($document)
2681
    {
2682 515
        $oid = spl_object_hash($document);
2683 515
        return isset($this->visitedCollections[$oid])
2684 515
                ? $this->visitedCollections[$oid]
2685 515
                : array();
2686
    }
2687
    
2688
    /**
2689
     * INTERNAL:
2690
     * Gets PersistentCollections that are scheduled to update and related to $document
2691
     * 
2692
     * @param object $document
2693
     * @return array
2694
     */
2695 515
    public function getScheduledCollections($document)
2696
    {
2697 515
        $oid = spl_object_hash($document);
2698 515
        return isset($this->hasScheduledCollections[$oid]) 
2699 515
                ? $this->hasScheduledCollections[$oid]
2700 515
                : array();
2701
    }
2702
    
2703
    /**
2704
     * Checks whether the document is related to a PersistentCollection
2705
     * scheduled for update or deletion.
2706
     *
2707
     * @param object $document
2708
     * @return boolean
2709
     */
2710 52
    public function hasScheduledCollections($document)
2711
    {
2712 52
        return isset($this->hasScheduledCollections[spl_object_hash($document)]);
2713
    }
2714
    
2715
    /**
2716
     * Marks the PersistentCollection's top-level owner as having a relation to
2717
     * a collection scheduled for update or deletion.
2718
     *
2719
     * If the owner is not scheduled for any lifecycle action, it will be
2720
     * scheduled for update to ensure that versioning takes place if necessary.
2721
     *
2722
     * If the collection is nested within atomic collection, it is immediately
2723
     * unscheduled and atomic one is scheduled for update instead. This makes
2724
     * calculating update data way easier.
2725
     * 
2726
     * @param PersistentCollection $coll
2727
     */
2728 205
    private function scheduleCollectionOwner(PersistentCollection $coll)
2729
    {
2730 205
        $document = $this->getOwningDocument($coll->getOwner());
2731 205
        $this->hasScheduledCollections[spl_object_hash($document)][spl_object_hash($coll)] = $coll;
2732
2733 205
        if ($document !== $coll->getOwner()) {
2734 24
            $parent = $coll->getOwner();
2735 24
            while (null !== ($parentAssoc = $this->getParentAssociation($parent))) {
2736 24
                list($mapping, $parent, ) = $parentAssoc;
2737 24
            }
2738 24
            if (isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
2739 7
                $class = $this->dm->getClassMetadata(get_class($document));
2740 7
                $atomicCollection = $class->getFieldValue($document, $mapping['fieldName']);
2741 7
                $this->scheduleCollectionUpdate($atomicCollection);
2742 7
                $this->unscheduleCollectionDeletion($coll);
2743 7
                $this->unscheduleCollectionUpdate($coll);
2744 7
            }
2745 24
        }
2746
2747 205
        if ( ! $this->isDocumentScheduled($document)) {
2748 85
            $this->scheduleForUpdate($document);
2749 85
        }
2750 205
    }
2751
2752
    /**
2753
     * Get the top-most owning document of a given document
2754
     *
2755
     * If a top-level document is provided, that same document will be returned.
2756
     * For an embedded document, we will walk through parent associations until
2757
     * we find a top-level document.
2758
     *
2759
     * @param object $document
2760
     * @throws \UnexpectedValueException when a top-level document could not be found
2761
     * @return object
2762
     */
2763 207
    public function getOwningDocument($document)
2764
    {
2765 207
        $class = $this->dm->getClassMetadata(get_class($document));
2766 207
        while ($class->isEmbeddedDocument) {
2767 38
            $parentAssociation = $this->getParentAssociation($document);
2768
2769 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...
2770
                throw new \UnexpectedValueException("Could not determine parent association for " . get_class($document));
2771
            }
2772
2773 38
            list(, $document, ) = $parentAssociation;
2774 38
            $class = $this->dm->getClassMetadata(get_class($document));
2775 38
        }
2776
2777 207
        return $document;
2778
    }
2779
2780
    /**
2781
     * Gets the class name for an association (embed or reference) with respect
2782
     * to any discriminator value.
2783
     *
2784
     * @param array      $mapping Field mapping for the association
2785
     * @param array|null $data    Data for the embedded document or reference
2786
     */
2787 199
    public function getClassNameForAssociation(array $mapping, $data)
2788
    {
2789 199
        $discriminatorField = isset($mapping['discriminatorField']) ? $mapping['discriminatorField'] : null;
2790
2791 199
        $discriminatorValue = null;
2792 199
        if (isset($discriminatorField, $data[$discriminatorField])) {
2793 21
            $discriminatorValue = $data[$discriminatorField];
2794 199
        } elseif (isset($mapping['defaultDiscriminatorValue'])) {
2795
            $discriminatorValue = $mapping['defaultDiscriminatorValue'];
2796
        }
2797
2798 199
        if ($discriminatorValue !== null) {
2799 21
            return isset($mapping['discriminatorMap'][$discriminatorValue])
2800 21
                ? $mapping['discriminatorMap'][$discriminatorValue]
2801 21
                : $discriminatorValue;
2802
        }
2803
2804 179
            $class = $this->dm->getClassMetadata($mapping['targetDocument']);
2805
2806 179 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...
2807 15
            $discriminatorValue = $data[$class->discriminatorField];
2808 179
        } elseif ($class->defaultDiscriminatorValue !== null) {
2809 1
            $discriminatorValue = $class->defaultDiscriminatorValue;
2810 1
        }
2811
2812 179
        if ($discriminatorValue !== null) {
2813 16
            return isset($class->discriminatorMap[$discriminatorValue])
2814 16
                ? $class->discriminatorMap[$discriminatorValue]
2815 16
                : $discriminatorValue;
2816
        }
2817
2818 163
        return $mapping['targetDocument'];
2819
    }
2820
2821
    /**
2822
     * INTERNAL:
2823
     * Creates a document. Used for reconstitution of documents during hydration.
2824
     *
2825
     * @ignore
2826
     * @param string $className The name of the document class.
2827
     * @param array $data The data for the document.
2828
     * @param array $hints Any hints to account for during reconstitution/lookup of the document.
2829
     * @param object The document to be hydrated into in case of creation
2830
     * @return object The document instance.
2831
     * @internal Highly performance-sensitive method.
2832
     */
2833 370
    public function getOrCreateDocument($className, $data, &$hints = array(), $document = null)
2834
    {
2835 370
        $class = $this->dm->getClassMetadata($className);
2836
2837
        // @TODO figure out how to remove this
2838 370
        $discriminatorValue = null;
2839 370 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...
2840 17
            $discriminatorValue = $data[$class->discriminatorField];
2841 370
        } elseif (isset($class->defaultDiscriminatorValue)) {
2842 2
            $discriminatorValue = $class->defaultDiscriminatorValue;
2843 2
        }
2844
2845 370
        if ($discriminatorValue !== null) {
2846 18
            $className = isset($class->discriminatorMap[$discriminatorValue])
2847 18
                ? $class->discriminatorMap[$discriminatorValue]
2848 18
                : $discriminatorValue;
2849
2850 18
            $class = $this->dm->getClassMetadata($className);
2851
2852 18
            unset($data[$class->discriminatorField]);
2853 18
        }
2854
2855 370
        $id = $class->getDatabaseIdentifierValue($data['_id']);
2856 370
        $serializedId = serialize($id);
2857
2858 370
        if (isset($this->identityMap[$class->name][$serializedId])) {
2859 89
            $document = $this->identityMap[$class->name][$serializedId];
2860 89
            $oid = spl_object_hash($document);
2861 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...
2862 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...
2863 10
                $overrideLocalValues = true;
2864 10
                if ($document instanceof NotifyPropertyChanged) {
2865
                    $document->addPropertyChangedListener($this);
2866
                }
2867 10
            } else {
2868 85
                $overrideLocalValues = ! empty($hints[Query::HINT_REFRESH]);
2869
            }
2870 89
            if ($overrideLocalValues) {
2871 46
                $data = $this->hydratorFactory->hydrate($document, $data, $hints);
2872 46
                $this->originalDocumentData[$oid] = $data;
2873 46
            }
2874 89
        } else {
2875 342
            if ($document === null) {
2876 342
                $document = $class->newInstance();
2877 342
            }
2878 342
            $this->registerManaged($document, $id, $data);
2879 342
            $oid = spl_object_hash($document);
2880 342
            $this->documentStates[$oid] = self::STATE_MANAGED;
2881 342
            $this->identityMap[$class->name][$serializedId] = $document;
2882 342
            $data = $this->hydratorFactory->hydrate($document, $data, $hints);
2883 342
            $this->originalDocumentData[$oid] = $data;
2884
        }
2885 370
        return $document;
2886
    }
2887
2888
    /**
2889
     * Initializes (loads) an uninitialized persistent collection of a document.
2890
     *
2891
     * @param PersistentCollection $collection The collection to initialize.
2892
     */
2893 149
    public function loadCollection(PersistentCollection $collection)
2894
    {
2895 149
        $this->getDocumentPersister(get_class($collection->getOwner()))->loadCollection($collection);
2896 149
    }
2897
2898
    /**
2899
     * Gets the identity map of the UnitOfWork.
2900
     *
2901
     * @return array
2902
     */
2903
    public function getIdentityMap()
2904
    {
2905
        return $this->identityMap;
2906
    }
2907
2908
    /**
2909
     * Gets the original data of a document. The original data is the data that was
2910
     * present at the time the document was reconstituted from the database.
2911
     *
2912
     * @param object $document
2913
     * @return array
2914
     */
2915 1
    public function getOriginalDocumentData($document)
2916
    {
2917 1
        $oid = spl_object_hash($document);
2918 1
        if (isset($this->originalDocumentData[$oid])) {
2919 1
            return $this->originalDocumentData[$oid];
2920
        }
2921
        return array();
2922
    }
2923
2924
    /**
2925
     * @ignore
2926
     */
2927 42
    public function setOriginalDocumentData($document, array $data)
2928
    {
2929 42
        $this->originalDocumentData[spl_object_hash($document)] = $data;
2930 42
    }
2931
2932
    /**
2933
     * INTERNAL:
2934
     * Sets a property value of the original data array of a document.
2935
     *
2936
     * @ignore
2937
     * @param string $oid
2938
     * @param string $property
2939
     * @param mixed $value
2940
     */
2941 3
    public function setOriginalDocumentProperty($oid, $property, $value)
2942
    {
2943 3
        $this->originalDocumentData[$oid][$property] = $value;
2944 3
    }
2945
2946
    /**
2947
     * Gets the identifier of a document.
2948
     *
2949
     * @param object $document
2950
     * @return mixed The identifier value
2951
     */
2952 335
    public function getDocumentIdentifier($document)
2953
    {
2954 335
        return isset($this->documentIdentifiers[spl_object_hash($document)]) ?
2955 335
            $this->documentIdentifiers[spl_object_hash($document)] : null;
2956
    }
2957
2958
    /**
2959
     * Checks whether the UnitOfWork has any pending insertions.
2960
     *
2961
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2962
     */
2963
    public function hasPendingInsertions()
2964
    {
2965
        return ! empty($this->documentInsertions);
2966
    }
2967
2968
    /**
2969
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2970
     * number of documents in the identity map.
2971
     *
2972
     * @return integer
2973
     */
2974 2
    public function size()
2975
    {
2976 2
        $count = 0;
2977 2
        foreach ($this->identityMap as $documentSet) {
2978 2
            $count += count($documentSet);
2979 2
        }
2980 2
        return $count;
2981
    }
2982
2983
    /**
2984
     * INTERNAL:
2985
     * Registers a document as managed.
2986
     *
2987
     * TODO: This method assumes that $id is a valid PHP identifier for the
2988
     * document class. If the class expects its database identifier to be a
2989
     * MongoId, and an incompatible $id is registered (e.g. an integer), the
2990
     * document identifiers map will become inconsistent with the identity map.
2991
     * In the future, we may want to round-trip $id through a PHP and database
2992
     * conversion and throw an exception if it's inconsistent.
2993
     *
2994
     * @param object $document The document.
2995
     * @param array $id The identifier values.
2996
     * @param array $data The original document data.
2997
     */
2998 358
    public function registerManaged($document, $id, array $data)
2999
    {
3000 358
        $oid = spl_object_hash($document);
3001 358
        $class = $this->dm->getClassMetadata(get_class($document));
3002
3003 358
        if ( ! $class->identifier || $id === null) {
3004 102
            $this->documentIdentifiers[$oid] = $oid;
3005 102
        } else {
3006 352
            $this->documentIdentifiers[$oid] = $class->getPHPIdentifierValue($id);
3007
        }
3008
3009 358
        $this->documentStates[$oid] = self::STATE_MANAGED;
3010 358
        $this->originalDocumentData[$oid] = $data;
3011 358
        $this->addToIdentityMap($document);
3012 358
    }
3013
3014
    /**
3015
     * INTERNAL:
3016
     * Clears the property changeset of the document with the given OID.
3017
     *
3018
     * @param string $oid The document's OID.
3019
     */
3020 1
    public function clearDocumentChangeSet($oid)
3021
    {
3022 1
        $this->documentChangeSets[$oid] = array();
3023 1
    }
3024
3025
    /* PropertyChangedListener implementation */
3026
3027
    /**
3028
     * Notifies this UnitOfWork of a property change in a document.
3029
     *
3030
     * @param object $document The document that owns the property.
3031
     * @param string $propertyName The name of the property that changed.
3032
     * @param mixed $oldValue The old value of the property.
3033
     * @param mixed $newValue The new value of the property.
3034
     */
3035 2
    public function propertyChanged($document, $propertyName, $oldValue, $newValue)
3036
    {
3037 2
        $oid = spl_object_hash($document);
3038 2
        $class = $this->dm->getClassMetadata(get_class($document));
3039
3040 2
        if ( ! isset($class->fieldMappings[$propertyName])) {
3041 1
            return; // ignore non-persistent fields
3042
        }
3043
3044
        // Update changeset and mark document for synchronization
3045 2
        $this->documentChangeSets[$oid][$propertyName] = array($oldValue, $newValue);
3046 2
        if ( ! isset($this->scheduledForSynchronization[$class->name][$oid])) {
3047 2
            $this->scheduleForSynchronization($document);
3048 2
        }
3049 2
    }
3050
3051
    /**
3052
     * Gets the currently scheduled document insertions in this UnitOfWork.
3053
     *
3054
     * @return array
3055
     */
3056 5
    public function getScheduledDocumentInsertions()
3057
    {
3058 5
        return $this->documentInsertions;
3059
    }
3060
3061
    /**
3062
     * Gets the currently scheduled document upserts in this UnitOfWork.
3063
     *
3064
     * @return array
3065
     */
3066 3
    public function getScheduledDocumentUpserts()
3067
    {
3068 3
        return $this->documentUpserts;
3069
    }
3070
3071
    /**
3072
     * Gets the currently scheduled document updates in this UnitOfWork.
3073
     *
3074
     * @return array
3075
     */
3076 3
    public function getScheduledDocumentUpdates()
3077
    {
3078 3
        return $this->documentUpdates;
3079
    }
3080
3081
    /**
3082
     * Gets the currently scheduled document deletions in this UnitOfWork.
3083
     *
3084
     * @return array
3085
     */
3086
    public function getScheduledDocumentDeletions()
3087
    {
3088
        return $this->documentDeletions;
3089
    }
3090
3091
    /**
3092
     * Get the currently scheduled complete collection deletions
3093
     *
3094
     * @return array
3095
     */
3096
    public function getScheduledCollectionDeletions()
3097
    {
3098
        return $this->collectionDeletions;
3099
    }
3100
3101
    /**
3102
     * Gets the currently scheduled collection inserts, updates and deletes.
3103
     *
3104
     * @return array
3105
     */
3106
    public function getScheduledCollectionUpdates()
3107
    {
3108
        return $this->collectionUpdates;
3109
    }
3110
3111
    /**
3112
     * Helper method to initialize a lazy loading proxy or persistent collection.
3113
     *
3114
     * @param object
3115
     * @return void
3116
     */
3117
    public function initializeObject($obj)
3118
    {
3119
        if ($obj instanceof Proxy) {
3120
            $obj->__load();
3121
        } elseif ($obj instanceof PersistentCollection) {
3122
            $obj->initialize();
3123
        }
3124
    }
3125
3126 1
    private static function objToStr($obj)
3127
    {
3128 1
        return method_exists($obj, '__toString') ? (string)$obj : get_class($obj) . '@' . spl_object_hash($obj);
3129
    }
3130
}
3131