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
|
911 |
|
public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory) |
259
|
|
|
{ |
260
|
911 |
|
$this->dm = $dm; |
261
|
911 |
|
$this->evm = $evm; |
262
|
911 |
|
$this->hydratorFactory = $hydratorFactory; |
263
|
911 |
|
} |
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
|
658 |
|
public function getPersistenceBuilder() |
272
|
|
|
{ |
273
|
658 |
|
if ( ! $this->persistenceBuilder) { |
274
|
658 |
|
$this->persistenceBuilder = new PersistenceBuilder($this->dm, $this); |
275
|
658 |
|
} |
276
|
658 |
|
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
|
656 |
|
public function getDocumentPersister($documentName) |
319
|
|
|
{ |
320
|
656 |
|
if ( ! isset($this->persisters[$documentName])) { |
321
|
642 |
|
$class = $this->dm->getClassMetadata($documentName); |
322
|
642 |
|
$pb = $this->getPersistenceBuilder(); |
323
|
642 |
|
$this->persisters[$documentName] = new Persisters\DocumentPersister($pb, $this->dm, $this->evm, $this, $this->hydratorFactory, $class); |
324
|
642 |
|
} |
325
|
656 |
|
return $this->persisters[$documentName]; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Get the collection persister instance. |
330
|
|
|
* |
331
|
|
|
* @return \Doctrine\ODM\MongoDB\Persisters\CollectionPersister |
332
|
|
|
*/ |
333
|
656 |
|
public function getCollectionPersister() |
334
|
|
|
{ |
335
|
656 |
|
if ( ! isset($this->collectionPersister)) { |
336
|
656 |
|
$pb = $this->getPersistenceBuilder(); |
337
|
656 |
|
$this->collectionPersister = new Persisters\CollectionPersister($this->dm, $pb, $this); |
338
|
656 |
|
} |
339
|
656 |
|
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
|
545 |
|
public function commit($document = null, array $options = array()) |
368
|
|
|
{ |
369
|
|
|
// Raise preFlush |
370
|
545 |
|
if ($this->evm->hasListeners(Events::preFlush)) { |
371
|
|
|
$this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->dm)); |
372
|
|
|
} |
373
|
|
|
|
374
|
545 |
|
$defaultOptions = $this->dm->getConfiguration()->getDefaultCommitOptions(); |
375
|
545 |
|
if ($options) { |
|
|
|
|
376
|
|
|
$options = array_merge($defaultOptions, $options); |
377
|
|
|
} else { |
378
|
545 |
|
$options = $defaultOptions; |
379
|
|
|
} |
380
|
|
|
// Compute changes done since last commit. |
381
|
545 |
|
if ($document === null) { |
382
|
539 |
|
$this->computeChangeSets(); |
383
|
544 |
|
} 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
|
543 |
|
if ( ! ($this->documentInsertions || |
|
|
|
|
392
|
226 |
|
$this->documentUpserts || |
|
|
|
|
393
|
190 |
|
$this->documentDeletions || |
|
|
|
|
394
|
180 |
|
$this->documentUpdates || |
|
|
|
|
395
|
23 |
|
$this->collectionUpdates || |
|
|
|
|
396
|
23 |
|
$this->collectionDeletions || |
|
|
|
|
397
|
23 |
|
$this->orphanRemovals) |
|
|
|
|
398
|
543 |
|
) { |
399
|
23 |
|
return; // Nothing to do. |
400
|
|
|
} |
401
|
|
|
|
402
|
540 |
|
if ($this->orphanRemovals) { |
|
|
|
|
403
|
45 |
|
foreach ($this->orphanRemovals as $removal) { |
404
|
44 |
|
$this->remove($removal); |
405
|
44 |
|
} |
406
|
44 |
|
} |
407
|
|
|
|
408
|
|
|
// Raise onFlush |
409
|
540 |
|
if ($this->evm->hasListeners(Events::onFlush)) { |
410
|
7 |
|
$this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm)); |
411
|
7 |
|
} |
412
|
|
|
|
413
|
540 |
|
foreach ($this->getClassesForCommitAction($this->documentUpserts) as $classAndDocuments) { |
414
|
77 |
|
list($class, $documents) = $classAndDocuments; |
415
|
77 |
|
$this->executeUpserts($class, $documents, $options); |
|
|
|
|
416
|
540 |
|
} |
417
|
|
|
|
418
|
540 |
|
foreach ($this->getClassesForCommitAction($this->documentInsertions) as $classAndDocuments) { |
419
|
474 |
|
list($class, $documents) = $classAndDocuments; |
420
|
474 |
|
$this->executeInserts($class, $documents, $options); |
|
|
|
|
421
|
539 |
|
} |
422
|
|
|
|
423
|
539 |
|
foreach ($this->getClassesForCommitAction($this->documentUpdates) as $classAndDocuments) { |
424
|
206 |
|
list($class, $documents) = $classAndDocuments; |
425
|
206 |
|
$this->executeUpdates($class, $documents, $options); |
|
|
|
|
426
|
539 |
|
} |
427
|
|
|
|
428
|
539 |
|
foreach ($this->getClassesForCommitAction($this->documentDeletions, true) as $classAndDocuments) { |
429
|
61 |
|
list($class, $documents) = $classAndDocuments; |
430
|
61 |
|
$this->executeDeletions($class, $documents, $options); |
|
|
|
|
431
|
539 |
|
} |
432
|
|
|
|
433
|
|
|
// Raise postFlush |
434
|
539 |
|
if ($this->evm->hasListeners(Events::postFlush)) { |
435
|
|
|
$this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm)); |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
// Clear up |
439
|
539 |
|
$this->documentInsertions = |
440
|
539 |
|
$this->documentUpserts = |
441
|
539 |
|
$this->documentUpdates = |
442
|
539 |
|
$this->documentDeletions = |
443
|
539 |
|
$this->documentChangeSets = |
444
|
539 |
|
$this->collectionUpdates = |
445
|
539 |
|
$this->collectionDeletions = |
446
|
539 |
|
$this->visitedCollections = |
447
|
539 |
|
$this->scheduledForSynchronization = |
448
|
539 |
|
$this->orphanRemovals = |
449
|
539 |
|
$this->hasScheduledCollections = array(); |
450
|
539 |
|
} |
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
|
540 |
|
private function getClassesForCommitAction($documents, $includeEmbedded = false) |
460
|
|
|
{ |
461
|
540 |
|
if (empty($documents)) { |
462
|
540 |
|
return array(); |
463
|
|
|
} |
464
|
539 |
|
$divided = array(); |
465
|
539 |
|
$embeds = array(); |
466
|
539 |
|
foreach ($documents as $oid => $d) { |
467
|
539 |
|
$className = get_class($d); |
468
|
539 |
|
if (isset($embeds[$className])) { |
469
|
62 |
|
continue; |
470
|
|
|
} |
471
|
539 |
|
if (isset($divided[$className])) { |
472
|
132 |
|
$divided[$className][1][$oid] = $d; |
473
|
132 |
|
continue; |
474
|
|
|
} |
475
|
539 |
|
$class = $this->dm->getClassMetadata($className); |
476
|
539 |
|
if ($class->isEmbeddedDocument && ! $includeEmbedded) { |
477
|
159 |
|
$embeds[$className] = true; |
478
|
159 |
|
continue; |
479
|
|
|
} |
480
|
539 |
|
if (empty($divided[$class->name])) { |
481
|
539 |
|
$divided[$class->name] = array($class, array($oid => $d)); |
482
|
539 |
|
} else { |
483
|
4 |
|
$divided[$class->name][1][$oid] = $d; |
484
|
|
|
} |
485
|
539 |
|
} |
486
|
539 |
|
return $divided; |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
/** |
490
|
|
|
* Compute changesets of all documents scheduled for insertion. |
491
|
|
|
* |
492
|
|
|
* Embedded documents will not be processed. |
493
|
|
|
*/ |
494
|
547 |
View Code Duplication |
private function computeScheduleInsertsChangeSets() |
|
|
|
|
495
|
|
|
{ |
496
|
547 |
|
foreach ($this->documentInsertions as $document) { |
497
|
482 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
498
|
482 |
|
if ( ! $class->isEmbeddedDocument) { |
499
|
479 |
|
$this->computeChangeSet($class, $document); |
500
|
478 |
|
} |
501
|
546 |
|
} |
502
|
546 |
|
} |
503
|
|
|
|
504
|
|
|
/** |
505
|
|
|
* Compute changesets of all documents scheduled for upsert. |
506
|
|
|
* |
507
|
|
|
* Embedded documents will not be processed. |
508
|
|
|
*/ |
509
|
546 |
View Code Duplication |
private function computeScheduleUpsertsChangeSets() |
|
|
|
|
510
|
|
|
{ |
511
|
546 |
|
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
|
546 |
|
} |
517
|
546 |
|
} |
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__) { |
|
|
|
|
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]) |
|
|
|
|
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
|
530 |
|
public function getDocumentChangeSet($document) |
572
|
|
|
{ |
573
|
530 |
|
$oid = spl_object_hash($document); |
574
|
530 |
|
if (isset($this->documentChangeSets[$oid])) { |
575
|
530 |
|
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
|
544 |
|
public function getDocumentActualData($document) |
587
|
|
|
{ |
588
|
544 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
589
|
544 |
|
$actualData = array(); |
590
|
544 |
|
foreach ($class->reflFields as $name => $refProp) { |
591
|
544 |
|
$mapping = $class->fieldMappings[$name]; |
592
|
|
|
// skip not saved fields |
593
|
544 |
|
if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) { |
594
|
49 |
|
continue; |
595
|
|
|
} |
596
|
544 |
|
$value = $refProp->getValue($document); |
597
|
544 |
|
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
|
544 |
|
} elseif ((isset($mapping['association']) && $mapping['type'] === 'many') |
602
|
544 |
|
&& $value !== null && ! ($value instanceof PersistentCollection)) { |
603
|
|
|
// If $actualData[$name] is not a Collection then use an ArrayCollection. |
604
|
357 |
|
if ( ! $value instanceof Collection) { |
605
|
118 |
|
$value = new ArrayCollection($value); |
606
|
118 |
|
} |
607
|
|
|
|
608
|
|
|
// Inject PersistentCollection |
609
|
357 |
|
$coll = new PersistentCollection($value, $this->dm, $this); |
610
|
357 |
|
$coll->setOwner($document, $mapping); |
611
|
357 |
|
$coll->setDirty( ! $value->isEmpty()); |
612
|
357 |
|
$class->reflFields[$name]->setValue($document, $coll); |
613
|
357 |
|
$actualData[$name] = $coll; |
614
|
357 |
|
} else { |
615
|
544 |
|
$actualData[$name] = $value; |
616
|
|
|
} |
617
|
544 |
|
} |
618
|
544 |
|
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
|
544 |
|
public function computeChangeSet(ClassMetadata $class, $document) |
646
|
|
|
{ |
647
|
544 |
|
if ( ! $class->isInheritanceTypeNone()) { |
648
|
171 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
649
|
171 |
|
} |
650
|
|
|
|
651
|
|
|
// Fire PreFlush lifecycle callbacks |
652
|
544 |
|
if ( ! empty($class->lifecycleCallbacks[Events::preFlush])) { |
653
|
10 |
|
$class->invokeLifecycleCallbacks(Events::preFlush, $document); |
654
|
10 |
|
} |
655
|
|
|
|
656
|
544 |
|
$this->computeOrRecomputeChangeSet($class, $document); |
657
|
543 |
|
} |
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
|
544 |
|
private function computeOrRecomputeChangeSet(ClassMetadata $class, $document, $recompute = false) |
667
|
|
|
{ |
668
|
544 |
|
$oid = spl_object_hash($document); |
669
|
544 |
|
$actualData = $this->getDocumentActualData($document); |
670
|
544 |
|
$isNewDocument = ! isset($this->originalDocumentData[$oid]); |
671
|
544 |
|
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
|
544 |
|
$this->originalDocumentData[$oid] = $actualData; |
675
|
544 |
|
$changeSet = array(); |
676
|
544 |
|
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
|
544 |
|
if ($actualValue instanceof PersistentCollection && $actualValue->getOwner() !== $document) { |
681
|
|
|
$actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName); |
682
|
|
|
$actualValue = $actualData[$propName]; |
683
|
|
|
} |
684
|
544 |
|
$changeSet[$propName] = array(null, $actualValue); |
685
|
544 |
|
} |
686
|
544 |
|
$this->documentChangeSets[$oid] = $changeSet; |
687
|
544 |
|
} 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
|
12 |
|
: $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
|
544 |
|
$associationMappings = array_filter( |
791
|
544 |
|
$class->associationMappings, |
792
|
|
|
function ($assoc) { return empty($assoc['notSaved']); } |
793
|
544 |
|
); |
794
|
|
|
|
795
|
544 |
|
foreach ($associationMappings as $mapping) { |
796
|
419 |
|
$value = $class->reflFields[$mapping['fieldName']]->getValue($document); |
797
|
|
|
|
798
|
419 |
|
if ($value === null) { |
799
|
279 |
|
continue; |
800
|
|
|
} |
801
|
|
|
|
802
|
410 |
|
$this->computeAssociationChanges($document, $mapping, $value); |
803
|
|
|
|
804
|
409 |
|
if (isset($mapping['reference'])) { |
805
|
306 |
|
continue; |
806
|
|
|
} |
807
|
|
|
|
808
|
318 |
|
$values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value->unwrap(); |
809
|
|
|
|
810
|
318 |
|
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
|
318 |
|
} |
823
|
543 |
|
} |
824
|
543 |
|
} |
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
|
542 |
|
public function computeChangeSets() |
832
|
|
|
{ |
833
|
542 |
|
$this->computeScheduleInsertsChangeSets(); |
834
|
541 |
|
$this->computeScheduleUpsertsChangeSets(); |
835
|
|
|
|
836
|
|
|
// Compute changes for other MANAGED documents. Change tracking policies take effect here. |
837
|
541 |
|
foreach ($this->identityMap as $className => $documents) { |
838
|
541 |
|
$class = $this->dm->getClassMetadata($className); |
839
|
541 |
|
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
|
541 |
|
switch (true) { |
851
|
541 |
|
case ($class->isChangeTrackingDeferredImplicit()): |
852
|
540 |
|
$documentsToProcess = $documents; |
853
|
540 |
|
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
|
541 |
|
foreach ($documentsToProcess as $document) { |
865
|
|
|
// Ignore uninitialized proxy objects |
866
|
537 |
|
if ($document instanceof Proxy && ! $document->__isInitialized__) { |
|
|
|
|
867
|
10 |
|
continue; |
868
|
|
|
} |
869
|
|
|
// Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here. |
870
|
537 |
|
$oid = spl_object_hash($document); |
871
|
537 |
View Code Duplication |
if ( ! isset($this->documentInsertions[$oid]) |
|
|
|
|
872
|
537 |
|
&& ! isset($this->documentUpserts[$oid]) |
873
|
537 |
|
&& ! isset($this->documentDeletions[$oid]) |
874
|
537 |
|
&& isset($this->documentStates[$oid]) |
875
|
537 |
|
) { |
876
|
251 |
|
$this->computeChangeSet($class, $document); |
877
|
251 |
|
} |
878
|
541 |
|
} |
879
|
541 |
|
} |
880
|
541 |
|
} |
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
|
410 |
|
private function computeAssociationChanges($parentDocument, array $assoc, $value) |
891
|
|
|
{ |
892
|
410 |
|
$isNewParentDocument = isset($this->documentInsertions[spl_object_hash($parentDocument)]); |
893
|
410 |
|
$class = $this->dm->getClassMetadata(get_class($parentDocument)); |
894
|
410 |
|
$topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument); |
895
|
|
|
|
896
|
410 |
|
if ($value instanceof Proxy && ! $value->__isInitialized__) { |
|
|
|
|
897
|
8 |
|
return; |
898
|
|
|
} |
899
|
|
|
|
900
|
409 |
|
if ($value instanceof PersistentCollection && $value->isDirty() && $assoc['isOwningSide']) { |
901
|
217 |
|
if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) { |
902
|
213 |
|
$this->scheduleCollectionUpdate($value); |
903
|
213 |
|
} |
904
|
217 |
|
$topmostOwner = $this->getOwningDocument($value->getOwner()); |
905
|
217 |
|
$this->visitedCollections[spl_object_hash($topmostOwner)][] = $value; |
906
|
217 |
|
} |
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
|
409 |
|
$unwrappedValue = ($assoc['type'] === ClassMetadata::ONE) ? array($value) : $value->unwrap(); |
912
|
|
|
|
913
|
409 |
|
$count = 0; |
914
|
409 |
|
foreach ($unwrappedValue as $key => $entry) { |
915
|
314 |
|
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
|
313 |
|
$targetClass = $this->dm->getClassMetadata(get_class($entry)); |
922
|
|
|
|
923
|
313 |
|
$state = $this->getDocumentState($entry, self::STATE_NEW); |
924
|
|
|
|
925
|
|
|
// Handle "set" strategy for multi-level hierarchy |
926
|
313 |
|
$pathKey = ! isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key; |
927
|
313 |
|
$path = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name']; |
928
|
|
|
|
929
|
313 |
|
$count++; |
930
|
|
|
|
931
|
|
|
switch ($state) { |
932
|
313 |
|
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
|
309 |
|
case self::STATE_MANAGED: |
946
|
309 |
|
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
|
309 |
|
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; |
|
|
|
|
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
|
408 |
|
} |
985
|
408 |
|
} |
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__) { |
|
|
|
|
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
|
562 |
|
private function persistNew(ClassMetadata $class, $document) |
1027
|
|
|
{ |
1028
|
562 |
|
$oid = spl_object_hash($document); |
1029
|
562 |
|
if ( ! empty($class->lifecycleCallbacks[Events::prePersist])) { |
1030
|
155 |
|
$class->invokeLifecycleCallbacks(Events::prePersist, $document); |
1031
|
155 |
|
} |
1032
|
562 |
View Code Duplication |
if ($this->evm->hasListeners(Events::prePersist)) { |
|
|
|
|
1033
|
6 |
|
$this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($document, $this->dm)); |
1034
|
6 |
|
} |
1035
|
|
|
|
1036
|
562 |
|
$upsert = false; |
1037
|
562 |
|
if ($class->identifier) { |
1038
|
562 |
|
$idValue = $class->getIdentifierValue($document); |
1039
|
562 |
|
$upsert = ! $class->isEmbeddedDocument && $idValue !== null; |
1040
|
|
|
|
1041
|
562 |
|
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
|
561 |
|
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
|
560 |
|
if ($class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) { |
1057
|
490 |
|
$idValue = $class->idGenerator->generate($this->dm, $document); |
1058
|
490 |
|
$idValue = $class->getPHPIdentifierValue($class->getDatabaseIdentifierValue($idValue)); |
1059
|
490 |
|
$class->setIdentifierValue($document, $idValue); |
1060
|
490 |
|
} |
1061
|
|
|
|
1062
|
560 |
|
$this->documentIdentifiers[$oid] = $idValue; |
1063
|
560 |
|
} else { |
1064
|
|
|
// this is for embedded documents without identifiers |
1065
|
142 |
|
$this->documentIdentifiers[$oid] = $oid; |
1066
|
|
|
} |
1067
|
|
|
|
1068
|
560 |
|
$this->documentStates[$oid] = self::STATE_MANAGED; |
1069
|
|
|
|
1070
|
560 |
|
if ($upsert) { |
1071
|
80 |
|
$this->scheduleForUpsert($class, $document); |
1072
|
80 |
|
} else { |
1073
|
495 |
|
$this->scheduleForInsert($class, $document); |
1074
|
|
|
} |
1075
|
560 |
|
} |
1076
|
|
|
|
1077
|
|
|
/** |
1078
|
|
|
* Cascades the postPersist events to embedded documents. |
1079
|
|
|
* |
1080
|
|
|
* @param ClassMetadata $class |
1081
|
|
|
* @param object $document |
1082
|
|
|
*/ |
1083
|
538 |
|
private function cascadePostPersist(ClassMetadata $class, $document) |
1084
|
|
|
{ |
1085
|
538 |
|
$hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist); |
1086
|
|
|
|
1087
|
538 |
|
$embeddedMappings = array_filter( |
1088
|
538 |
|
$class->associationMappings, |
1089
|
|
|
function($assoc) { return ! empty($assoc['embedded']); } |
1090
|
538 |
|
); |
1091
|
|
|
|
1092
|
538 |
|
foreach ($embeddedMappings as $mapping) { |
1093
|
326 |
|
$value = $class->reflFields[$mapping['fieldName']]->getValue($document); |
1094
|
|
|
|
1095
|
326 |
|
if ($value === null) { |
1096
|
213 |
|
continue; |
1097
|
|
|
} |
1098
|
|
|
|
1099
|
308 |
|
$values = $mapping['type'] === ClassMetadata::ONE ? array($value) : $value; |
1100
|
|
|
|
1101
|
308 |
|
if (isset($mapping['targetDocument'])) { |
1102
|
297 |
|
$embeddedClass = $this->dm->getClassMetadata($mapping['targetDocument']); |
1103
|
297 |
|
} |
1104
|
|
|
|
1105
|
308 |
|
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); |
|
|
|
|
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
|
308 |
|
} |
1118
|
538 |
|
} |
1119
|
538 |
|
} |
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
|
474 |
View Code Duplication |
private function executeInserts(ClassMetadata $class, array $documents, array $options = array()) |
|
|
|
|
1129
|
|
|
{ |
1130
|
474 |
|
$persister = $this->getDocumentPersister($class->name); |
1131
|
|
|
|
1132
|
474 |
|
foreach ($documents as $oid => $document) { |
1133
|
474 |
|
$persister->addInsert($document); |
1134
|
474 |
|
unset($this->documentInsertions[$oid]); |
1135
|
474 |
|
} |
1136
|
|
|
|
1137
|
474 |
|
$persister->executeInserts($options); |
1138
|
|
|
|
1139
|
473 |
|
$hasPostPersistLifecycleCallbacks = ! empty($class->lifecycleCallbacks[Events::postPersist]); |
1140
|
473 |
|
$hasPostPersistListeners = $this->evm->hasListeners(Events::postPersist); |
1141
|
|
|
|
1142
|
473 |
|
foreach ($documents as $document) { |
1143
|
473 |
|
if ($hasPostPersistLifecycleCallbacks) { |
1144
|
9 |
|
$class->invokeLifecycleCallbacks(Events::postPersist, $document); |
1145
|
9 |
|
} |
1146
|
473 |
|
if ($hasPostPersistListeners) { |
1147
|
5 |
|
$this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($document, $this->dm)); |
1148
|
5 |
|
} |
1149
|
473 |
|
$this->cascadePostPersist($class, $document); |
1150
|
473 |
|
} |
1151
|
473 |
|
} |
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()) |
|
|
|
|
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])) { |
|
|
|
|
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
|
498 |
|
public function scheduleForInsert(ClassMetadata $class, $document) |
|
|
|
|
1399
|
|
|
{ |
1400
|
498 |
|
$oid = spl_object_hash($document); |
1401
|
|
|
|
1402
|
498 |
|
if (isset($this->documentUpdates[$oid])) { |
1403
|
|
|
throw new \InvalidArgumentException("Dirty document can not be scheduled for insertion."); |
1404
|
|
|
} |
1405
|
498 |
|
if (isset($this->documentDeletions[$oid])) { |
1406
|
|
|
throw new \InvalidArgumentException("Removed document can not be scheduled for insertion."); |
1407
|
|
|
} |
1408
|
498 |
|
if (isset($this->documentInsertions[$oid])) { |
1409
|
|
|
throw new \InvalidArgumentException("Document can not be scheduled for insertion twice."); |
1410
|
|
|
} |
1411
|
|
|
|
1412
|
498 |
|
$this->documentInsertions[$oid] = $document; |
1413
|
|
|
|
1414
|
498 |
|
if (isset($this->documentIdentifiers[$oid])) { |
1415
|
495 |
|
$this->addToIdentityMap($document); |
1416
|
495 |
|
} |
1417
|
498 |
|
} |
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 |
1516
|
|
|
*/ |
1517
|
|
|
public function isScheduledForDirtyCheck($document) |
1518
|
|
|
{ |
1519
|
|
|
return $this->isScheduledForSynchronization($document); |
1520
|
|
|
} |
1521
|
|
|
|
1522
|
|
|
/** |
1523
|
|
|
* INTERNAL: |
1524
|
|
|
* Schedules a document for deletion. |
1525
|
|
|
* |
1526
|
|
|
* @param object $document |
1527
|
|
|
*/ |
1528
|
66 |
|
public function scheduleForDelete($document) |
1529
|
|
|
{ |
1530
|
66 |
|
$oid = spl_object_hash($document); |
1531
|
|
|
|
1532
|
66 |
|
if (isset($this->documentInsertions[$oid])) { |
1533
|
2 |
|
if ($this->isInIdentityMap($document)) { |
1534
|
2 |
|
$this->removeFromIdentityMap($document); |
1535
|
2 |
|
} |
1536
|
2 |
|
unset($this->documentInsertions[$oid]); |
1537
|
2 |
|
return; // document has not been persisted yet, so nothing more to do. |
1538
|
|
|
} |
1539
|
|
|
|
1540
|
65 |
|
if ( ! $this->isInIdentityMap($document)) { |
1541
|
1 |
|
return; // ignore |
1542
|
|
|
} |
1543
|
|
|
|
1544
|
64 |
|
$this->removeFromIdentityMap($document); |
1545
|
64 |
|
$this->documentStates[$oid] = self::STATE_REMOVED; |
1546
|
|
|
|
1547
|
64 |
|
if (isset($this->documentUpdates[$oid])) { |
1548
|
|
|
unset($this->documentUpdates[$oid]); |
1549
|
|
|
} |
1550
|
64 |
|
if ( ! isset($this->documentDeletions[$oid])) { |
1551
|
64 |
|
$this->documentDeletions[$oid] = $document; |
1552
|
64 |
|
} |
1553
|
64 |
|
} |
1554
|
|
|
|
1555
|
|
|
/** |
1556
|
|
|
* Checks whether a document is registered as removed/deleted with the unit |
1557
|
|
|
* of work. |
1558
|
|
|
* |
1559
|
|
|
* @param object $document |
1560
|
|
|
* @return boolean |
1561
|
|
|
*/ |
1562
|
8 |
|
public function isScheduledForDelete($document) |
1563
|
|
|
{ |
1564
|
8 |
|
return isset($this->documentDeletions[spl_object_hash($document)]); |
1565
|
|
|
} |
1566
|
|
|
|
1567
|
|
|
/** |
1568
|
|
|
* Checks whether a document is scheduled for insertion, update or deletion. |
1569
|
|
|
* |
1570
|
|
|
* @param $document |
1571
|
|
|
* @return boolean |
1572
|
|
|
*/ |
1573
|
216 |
|
public function isDocumentScheduled($document) |
1574
|
|
|
{ |
1575
|
216 |
|
$oid = spl_object_hash($document); |
1576
|
216 |
|
return isset($this->documentInsertions[$oid]) || |
1577
|
113 |
|
isset($this->documentUpserts[$oid]) || |
1578
|
104 |
|
isset($this->documentUpdates[$oid]) || |
1579
|
216 |
|
isset($this->documentDeletions[$oid]); |
1580
|
|
|
} |
1581
|
|
|
|
1582
|
|
|
/** |
1583
|
|
|
* INTERNAL: |
1584
|
|
|
* Registers a document in the identity map. |
1585
|
|
|
* |
1586
|
|
|
* Note that documents in a hierarchy are registered with the class name of |
1587
|
|
|
* the root document. Identifiers are serialized before being used as array |
1588
|
|
|
* keys to allow differentiation of equal, but not identical, values. |
1589
|
|
|
* |
1590
|
|
|
* @ignore |
1591
|
|
|
* @param object $document The document to register. |
1592
|
|
|
* @return boolean TRUE if the registration was successful, FALSE if the identity of |
1593
|
|
|
* the document in question is already managed. |
1594
|
|
|
*/ |
1595
|
589 |
|
public function addToIdentityMap($document) |
1596
|
|
|
{ |
1597
|
589 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1598
|
589 |
|
$id = $this->getIdForIdentityMap($document); |
1599
|
|
|
|
1600
|
589 |
|
if (isset($this->identityMap[$class->name][$id])) { |
1601
|
47 |
|
return false; |
1602
|
|
|
} |
1603
|
|
|
|
1604
|
589 |
|
$this->identityMap[$class->name][$id] = $document; |
1605
|
|
|
|
1606
|
589 |
|
if ($document instanceof NotifyPropertyChanged && |
1607
|
589 |
|
( ! $document instanceof Proxy || $document->__isInitialized())) { |
1608
|
3 |
|
$document->addPropertyChangedListener($this); |
1609
|
3 |
|
} |
1610
|
|
|
|
1611
|
589 |
|
return true; |
1612
|
|
|
} |
1613
|
|
|
|
1614
|
|
|
/** |
1615
|
|
|
* Gets the state of a document with regard to the current unit of work. |
1616
|
|
|
* |
1617
|
|
|
* @param object $document |
1618
|
|
|
* @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED). |
1619
|
|
|
* This parameter can be set to improve performance of document state detection |
1620
|
|
|
* by potentially avoiding a database lookup if the distinction between NEW and DETACHED |
1621
|
|
|
* is either known or does not matter for the caller of the method. |
1622
|
|
|
* @return int The document state. |
1623
|
|
|
*/ |
1624
|
565 |
|
public function getDocumentState($document, $assume = null) |
1625
|
|
|
{ |
1626
|
565 |
|
$oid = spl_object_hash($document); |
1627
|
|
|
|
1628
|
565 |
|
if (isset($this->documentStates[$oid])) { |
1629
|
343 |
|
return $this->documentStates[$oid]; |
1630
|
|
|
} |
1631
|
|
|
|
1632
|
565 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1633
|
|
|
|
1634
|
565 |
|
if ($class->isEmbeddedDocument) { |
1635
|
169 |
|
return self::STATE_NEW; |
1636
|
|
|
} |
1637
|
|
|
|
1638
|
562 |
|
if ($assume !== null) { |
1639
|
559 |
|
return $assume; |
1640
|
|
|
} |
1641
|
|
|
|
1642
|
|
|
/* State can only be NEW or DETACHED, because MANAGED/REMOVED states are |
1643
|
|
|
* known. Note that you cannot remember the NEW or DETACHED state in |
1644
|
|
|
* _documentStates since the UoW does not hold references to such |
1645
|
|
|
* objects and the object hash can be reused. More generally, because |
1646
|
|
|
* the state may "change" between NEW/DETACHED without the UoW being |
1647
|
|
|
* aware of it. |
1648
|
|
|
*/ |
1649
|
4 |
|
$id = $class->getIdentifierObject($document); |
1650
|
|
|
|
1651
|
4 |
|
if ($id === null) { |
1652
|
2 |
|
return self::STATE_NEW; |
1653
|
|
|
} |
1654
|
|
|
|
1655
|
|
|
// Check for a version field, if available, to avoid a DB lookup. |
1656
|
2 |
|
if ($class->isVersioned) { |
1657
|
|
|
return ($class->getFieldValue($document, $class->versionField)) |
1658
|
|
|
? self::STATE_DETACHED |
1659
|
|
|
: self::STATE_NEW; |
1660
|
|
|
} |
1661
|
|
|
|
1662
|
|
|
// Last try before DB lookup: check the identity map. |
1663
|
2 |
|
if ($this->tryGetById($id, $class)) { |
1664
|
1 |
|
return self::STATE_DETACHED; |
1665
|
|
|
} |
1666
|
|
|
|
1667
|
|
|
// DB lookup |
1668
|
2 |
|
if ($this->getDocumentPersister($class->name)->exists($document)) { |
1669
|
1 |
|
return self::STATE_DETACHED; |
1670
|
|
|
} |
1671
|
|
|
|
1672
|
1 |
|
return self::STATE_NEW; |
1673
|
|
|
} |
1674
|
|
|
|
1675
|
|
|
/** |
1676
|
|
|
* INTERNAL: |
1677
|
|
|
* Removes a document from the identity map. This effectively detaches the |
1678
|
|
|
* document from the persistence management of Doctrine. |
1679
|
|
|
* |
1680
|
|
|
* @ignore |
1681
|
|
|
* @param object $document |
1682
|
|
|
* @throws \InvalidArgumentException |
1683
|
|
|
* @return boolean |
1684
|
|
|
*/ |
1685
|
75 |
|
public function removeFromIdentityMap($document) |
1686
|
|
|
{ |
1687
|
75 |
|
$oid = spl_object_hash($document); |
1688
|
|
|
|
1689
|
|
|
// Check if id is registered first |
1690
|
75 |
|
if ( ! isset($this->documentIdentifiers[$oid])) { |
1691
|
|
|
return false; |
1692
|
|
|
} |
1693
|
|
|
|
1694
|
75 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1695
|
75 |
|
$id = $this->getIdForIdentityMap($document); |
1696
|
|
|
|
1697
|
75 |
|
if (isset($this->identityMap[$class->name][$id])) { |
1698
|
75 |
|
unset($this->identityMap[$class->name][$id]); |
1699
|
75 |
|
$this->documentStates[$oid] = self::STATE_DETACHED; |
1700
|
75 |
|
return true; |
1701
|
|
|
} |
1702
|
|
|
|
1703
|
|
|
return false; |
1704
|
|
|
} |
1705
|
|
|
|
1706
|
|
|
/** |
1707
|
|
|
* INTERNAL: |
1708
|
|
|
* Gets a document in the identity map by its identifier hash. |
1709
|
|
|
* |
1710
|
|
|
* @ignore |
1711
|
|
|
* @param mixed $id Document identifier |
1712
|
|
|
* @param ClassMetadata $class Document class |
1713
|
|
|
* @return object |
1714
|
|
|
* @throws InvalidArgumentException if the class does not have an identifier |
1715
|
|
|
*/ |
1716
|
31 |
|
public function getById($id, ClassMetadata $class) |
1717
|
|
|
{ |
1718
|
31 |
|
if ( ! $class->identifier) { |
1719
|
|
|
throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name)); |
1720
|
|
|
} |
1721
|
|
|
|
1722
|
31 |
|
$serializedId = serialize($class->getDatabaseIdentifierValue($id)); |
1723
|
|
|
|
1724
|
31 |
|
return $this->identityMap[$class->name][$serializedId]; |
1725
|
|
|
} |
1726
|
|
|
|
1727
|
|
|
/** |
1728
|
|
|
* INTERNAL: |
1729
|
|
|
* Tries to get a document by its identifier hash. If no document is found |
1730
|
|
|
* for the given hash, FALSE is returned. |
1731
|
|
|
* |
1732
|
|
|
* @ignore |
1733
|
|
|
* @param mixed $id Document identifier |
1734
|
|
|
* @param ClassMetadata $class Document class |
1735
|
|
|
* @return mixed The found document or FALSE. |
1736
|
|
|
* @throws InvalidArgumentException if the class does not have an identifier |
1737
|
|
|
*/ |
1738
|
288 |
|
public function tryGetById($id, ClassMetadata $class) |
1739
|
|
|
{ |
1740
|
288 |
|
if ( ! $class->identifier) { |
1741
|
|
|
throw new \InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name)); |
1742
|
|
|
} |
1743
|
|
|
|
1744
|
288 |
|
$serializedId = serialize($class->getDatabaseIdentifierValue($id)); |
1745
|
|
|
|
1746
|
288 |
|
return isset($this->identityMap[$class->name][$serializedId]) ? |
1747
|
288 |
|
$this->identityMap[$class->name][$serializedId] : false; |
1748
|
|
|
} |
1749
|
|
|
|
1750
|
|
|
/** |
1751
|
|
|
* Schedules a document for dirty-checking at commit-time. |
1752
|
|
|
* |
1753
|
|
|
* @param object $document The document to schedule for dirty-checking. |
1754
|
|
|
*/ |
1755
|
2 |
|
public function scheduleForSynchronization($document) |
1756
|
|
|
{ |
1757
|
2 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1758
|
2 |
|
$this->scheduledForSynchronization[$class->name][spl_object_hash($document)] = $document; |
1759
|
2 |
|
} |
1760
|
|
|
|
1761
|
|
|
/** |
1762
|
|
|
* @deprecated |
1763
|
|
|
*/ |
1764
|
|
|
public function scheduleForDirtyCheck($document) |
1765
|
|
|
{ |
1766
|
|
|
$this->scheduleForSynchronization($document); |
1767
|
|
|
} |
1768
|
|
|
|
1769
|
|
|
/** |
1770
|
|
|
* Checks whether a document is registered in the identity map. |
1771
|
|
|
* |
1772
|
|
|
* @param object $document |
1773
|
|
|
* @return boolean |
1774
|
|
|
*/ |
1775
|
75 |
|
public function isInIdentityMap($document) |
1776
|
|
|
{ |
1777
|
75 |
|
$oid = spl_object_hash($document); |
1778
|
|
|
|
1779
|
75 |
|
if ( ! isset($this->documentIdentifiers[$oid])) { |
1780
|
4 |
|
return false; |
1781
|
|
|
} |
1782
|
|
|
|
1783
|
74 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1784
|
74 |
|
$id = $this->getIdForIdentityMap($document); |
1785
|
|
|
|
1786
|
74 |
|
return isset($this->identityMap[$class->name][$id]); |
1787
|
|
|
} |
1788
|
|
|
|
1789
|
|
|
/** |
1790
|
|
|
* @param object $document |
1791
|
|
|
* @return string |
1792
|
|
|
*/ |
1793
|
589 |
|
private function getIdForIdentityMap($document) |
1794
|
|
|
{ |
1795
|
589 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1796
|
|
|
|
1797
|
589 |
|
if ( ! $class->identifier) { |
1798
|
145 |
|
$id = spl_object_hash($document); |
1799
|
145 |
|
} else { |
1800
|
588 |
|
$id = $this->documentIdentifiers[spl_object_hash($document)]; |
1801
|
588 |
|
$id = serialize($class->getDatabaseIdentifierValue($id)); |
1802
|
|
|
} |
1803
|
|
|
|
1804
|
589 |
|
return $id; |
1805
|
|
|
} |
1806
|
|
|
|
1807
|
|
|
/** |
1808
|
|
|
* INTERNAL: |
1809
|
|
|
* Checks whether an identifier exists in the identity map. |
1810
|
|
|
* |
1811
|
|
|
* @ignore |
1812
|
|
|
* @param string $id |
1813
|
|
|
* @param string $rootClassName |
1814
|
|
|
* @return boolean |
1815
|
|
|
*/ |
1816
|
|
|
public function containsId($id, $rootClassName) |
1817
|
|
|
{ |
1818
|
|
|
return isset($this->identityMap[$rootClassName][serialize($id)]); |
1819
|
|
|
} |
1820
|
|
|
|
1821
|
|
|
/** |
1822
|
|
|
* Persists a document as part of the current unit of work. |
1823
|
|
|
* |
1824
|
|
|
* @param object $document The document to persist. |
1825
|
|
|
* @throws MongoDBException If trying to persist MappedSuperclass. |
1826
|
|
|
* @throws \InvalidArgumentException If there is something wrong with document's identifier. |
1827
|
|
|
*/ |
1828
|
560 |
|
public function persist($document) |
1829
|
|
|
{ |
1830
|
560 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1831
|
560 |
|
if ($class->isMappedSuperclass) { |
1832
|
1 |
|
throw MongoDBException::cannotPersistMappedSuperclass($class->name); |
1833
|
|
|
} |
1834
|
559 |
|
$visited = array(); |
1835
|
559 |
|
$this->doPersist($document, $visited); |
1836
|
555 |
|
} |
1837
|
|
|
|
1838
|
|
|
/** |
1839
|
|
|
* Saves a document as part of the current unit of work. |
1840
|
|
|
* This method is internally called during save() cascades as it tracks |
1841
|
|
|
* the already visited documents to prevent infinite recursions. |
1842
|
|
|
* |
1843
|
|
|
* NOTE: This method always considers documents that are not yet known to |
1844
|
|
|
* this UnitOfWork as NEW. |
1845
|
|
|
* |
1846
|
|
|
* @param object $document The document to persist. |
1847
|
|
|
* @param array $visited The already visited documents. |
1848
|
|
|
* @throws \InvalidArgumentException |
1849
|
|
|
* @throws MongoDBException |
1850
|
|
|
*/ |
1851
|
559 |
|
private function doPersist($document, array &$visited) |
1852
|
|
|
{ |
1853
|
559 |
|
$oid = spl_object_hash($document); |
1854
|
559 |
|
if (isset($visited[$oid])) { |
1855
|
24 |
|
return; // Prevent infinite recursion |
1856
|
|
|
} |
1857
|
|
|
|
1858
|
559 |
|
$visited[$oid] = $document; // Mark visited |
1859
|
|
|
|
1860
|
559 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1861
|
|
|
|
1862
|
559 |
|
$documentState = $this->getDocumentState($document, self::STATE_NEW); |
1863
|
|
|
switch ($documentState) { |
1864
|
559 |
|
case self::STATE_MANAGED: |
1865
|
|
|
// Nothing to do, except if policy is "deferred explicit" |
1866
|
43 |
|
if ($class->isChangeTrackingDeferredExplicit()) { |
1867
|
|
|
$this->scheduleForSynchronization($document); |
1868
|
|
|
} |
1869
|
43 |
|
break; |
1870
|
559 |
|
case self::STATE_NEW: |
1871
|
559 |
|
$this->persistNew($class, $document); |
1872
|
557 |
|
break; |
1873
|
|
|
|
1874
|
2 |
|
case self::STATE_REMOVED: |
1875
|
|
|
// Document becomes managed again |
1876
|
2 |
|
unset($this->documentDeletions[$oid]); |
1877
|
|
|
|
1878
|
2 |
|
$this->documentStates[$oid] = self::STATE_MANAGED; |
1879
|
2 |
|
break; |
1880
|
|
|
|
1881
|
|
|
case self::STATE_DETACHED: |
1882
|
|
|
throw new \InvalidArgumentException( |
1883
|
|
|
"Behavior of persist() for a detached document is not yet defined."); |
1884
|
|
|
break; |
|
|
|
|
1885
|
|
|
|
1886
|
|
|
default: |
1887
|
|
|
throw MongoDBException::invalidDocumentState($documentState); |
1888
|
|
|
} |
1889
|
|
|
|
1890
|
557 |
|
$this->cascadePersist($document, $visited); |
1891
|
555 |
|
} |
1892
|
|
|
|
1893
|
|
|
/** |
1894
|
|
|
* Deletes a document as part of the current unit of work. |
1895
|
|
|
* |
1896
|
|
|
* @param object $document The document to remove. |
1897
|
|
|
*/ |
1898
|
65 |
|
public function remove($document) |
1899
|
|
|
{ |
1900
|
65 |
|
$visited = array(); |
1901
|
65 |
|
$this->doRemove($document, $visited); |
1902
|
65 |
|
} |
1903
|
|
|
|
1904
|
|
|
/** |
1905
|
|
|
* Deletes a document as part of the current unit of work. |
1906
|
|
|
* |
1907
|
|
|
* This method is internally called during delete() cascades as it tracks |
1908
|
|
|
* the already visited documents to prevent infinite recursions. |
1909
|
|
|
* |
1910
|
|
|
* @param object $document The document to delete. |
1911
|
|
|
* @param array $visited The map of the already visited documents. |
1912
|
|
|
* @throws MongoDBException |
1913
|
|
|
*/ |
1914
|
65 |
|
private function doRemove($document, array &$visited) |
1915
|
|
|
{ |
1916
|
65 |
|
$oid = spl_object_hash($document); |
1917
|
65 |
|
if (isset($visited[$oid])) { |
1918
|
1 |
|
return; // Prevent infinite recursion |
1919
|
|
|
} |
1920
|
|
|
|
1921
|
65 |
|
$visited[$oid] = $document; // mark visited |
1922
|
|
|
|
1923
|
|
|
/* Cascade first, because scheduleForDelete() removes the entity from |
1924
|
|
|
* the identity map, which can cause problems when a lazy Proxy has to |
1925
|
|
|
* be initialized for the cascade operation. |
1926
|
|
|
*/ |
1927
|
65 |
|
$this->cascadeRemove($document, $visited); |
1928
|
|
|
|
1929
|
65 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1930
|
65 |
|
$documentState = $this->getDocumentState($document); |
1931
|
|
|
switch ($documentState) { |
1932
|
65 |
|
case self::STATE_NEW: |
1933
|
65 |
|
case self::STATE_REMOVED: |
1934
|
|
|
// nothing to do |
1935
|
1 |
|
break; |
1936
|
65 |
|
case self::STATE_MANAGED: |
1937
|
65 |
|
if ( ! empty($class->lifecycleCallbacks[Events::preRemove])) { |
1938
|
8 |
|
$class->invokeLifecycleCallbacks(Events::preRemove, $document); |
1939
|
8 |
|
} |
1940
|
65 |
View Code Duplication |
if ($this->evm->hasListeners(Events::preRemove)) { |
|
|
|
|
1941
|
1 |
|
$this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($document, $this->dm)); |
1942
|
1 |
|
} |
1943
|
65 |
|
$this->scheduleForDelete($document); |
1944
|
65 |
|
break; |
1945
|
|
|
case self::STATE_DETACHED: |
1946
|
|
|
throw MongoDBException::detachedDocumentCannotBeRemoved(); |
1947
|
|
|
default: |
1948
|
|
|
throw MongoDBException::invalidDocumentState($documentState); |
1949
|
|
|
} |
1950
|
65 |
|
} |
1951
|
|
|
|
1952
|
|
|
/** |
1953
|
|
|
* Merges the state of the given detached document into this UnitOfWork. |
1954
|
|
|
* |
1955
|
|
|
* @param object $document |
1956
|
|
|
* @return object The managed copy of the document. |
1957
|
|
|
*/ |
1958
|
13 |
|
public function merge($document) |
1959
|
|
|
{ |
1960
|
13 |
|
$visited = array(); |
1961
|
|
|
|
1962
|
13 |
|
return $this->doMerge($document, $visited); |
1963
|
|
|
} |
1964
|
|
|
|
1965
|
|
|
/** |
1966
|
|
|
* Executes a merge operation on a document. |
1967
|
|
|
* |
1968
|
|
|
* @param object $document |
1969
|
|
|
* @param array $visited |
1970
|
|
|
* @param object|null $prevManagedCopy |
1971
|
|
|
* @param array|null $assoc |
1972
|
|
|
* |
1973
|
|
|
* @return object The managed copy of the document. |
1974
|
|
|
* |
1975
|
|
|
* @throws InvalidArgumentException If the entity instance is NEW. |
1976
|
|
|
* @throws LockException If the document uses optimistic locking through a |
1977
|
|
|
* version attribute and the version check against the |
1978
|
|
|
* managed copy fails. |
1979
|
|
|
*/ |
1980
|
13 |
|
private function doMerge($document, array &$visited, $prevManagedCopy = null, $assoc = null) |
1981
|
|
|
{ |
1982
|
13 |
|
$oid = spl_object_hash($document); |
1983
|
|
|
|
1984
|
13 |
|
if (isset($visited[$oid])) { |
1985
|
1 |
|
return $visited[$oid]; // Prevent infinite recursion |
1986
|
|
|
} |
1987
|
|
|
|
1988
|
13 |
|
$visited[$oid] = $document; // mark visited |
1989
|
|
|
|
1990
|
13 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
1991
|
|
|
|
1992
|
|
|
/* First we assume DETACHED, although it can still be NEW but we can |
1993
|
|
|
* avoid an extra DB round trip this way. If it is not MANAGED but has |
1994
|
|
|
* an identity, we need to fetch it from the DB anyway in order to |
1995
|
|
|
* merge. MANAGED documents are ignored by the merge operation. |
1996
|
|
|
*/ |
1997
|
13 |
|
$managedCopy = $document; |
1998
|
|
|
|
1999
|
13 |
|
if ($this->getDocumentState($document, self::STATE_DETACHED) !== self::STATE_MANAGED) { |
2000
|
13 |
|
if ($document instanceof Proxy && ! $document->__isInitialized()) { |
2001
|
|
|
$document->__load(); |
2002
|
|
|
} |
2003
|
|
|
|
2004
|
|
|
// Try to look the document up in the identity map. |
2005
|
13 |
|
$id = $class->isEmbeddedDocument ? null : $class->getIdentifierObject($document); |
2006
|
|
|
|
2007
|
13 |
|
if ($id === null) { |
2008
|
|
|
// If there is no identifier, it is actually NEW. |
2009
|
5 |
|
$managedCopy = $class->newInstance(); |
2010
|
5 |
|
$this->persistNew($class, $managedCopy); |
2011
|
5 |
|
} else { |
2012
|
10 |
|
$managedCopy = $this->tryGetById($id, $class); |
2013
|
|
|
|
2014
|
10 |
|
if ($managedCopy) { |
2015
|
|
|
// We have the document in memory already, just make sure it is not removed. |
2016
|
5 |
|
if ($this->getDocumentState($managedCopy) === self::STATE_REMOVED) { |
2017
|
|
|
throw new \InvalidArgumentException('Removed entity detected during merge. Cannot merge with a removed entity.'); |
2018
|
|
|
} |
2019
|
5 |
|
} else { |
2020
|
|
|
// We need to fetch the managed copy in order to merge. |
2021
|
7 |
|
$managedCopy = $this->dm->find($class->name, $id); |
2022
|
|
|
} |
2023
|
|
|
|
2024
|
10 |
|
if ($managedCopy === null) { |
2025
|
|
|
// If the identifier is ASSIGNED, it is NEW |
2026
|
|
|
$managedCopy = $class->newInstance(); |
2027
|
|
|
$class->setIdentifierValue($managedCopy, $id); |
2028
|
|
|
$this->persistNew($class, $managedCopy); |
2029
|
|
|
} else { |
2030
|
10 |
|
if ($managedCopy instanceof Proxy && ! $managedCopy->__isInitialized__) { |
|
|
|
|
2031
|
|
|
$managedCopy->__load(); |
2032
|
|
|
} |
2033
|
|
|
} |
2034
|
|
|
} |
2035
|
|
|
|
2036
|
13 |
|
if ($class->isVersioned) { |
2037
|
|
|
$managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy); |
2038
|
|
|
$documentVersion = $class->reflFields[$class->versionField]->getValue($document); |
2039
|
|
|
|
2040
|
|
|
// Throw exception if versions don't match |
2041
|
|
|
if ($managedCopyVersion != $documentVersion) { |
2042
|
|
|
throw LockException::lockFailedVersionMissmatch($document, $documentVersion, $managedCopyVersion); |
2043
|
|
|
} |
2044
|
|
|
} |
2045
|
|
|
|
2046
|
|
|
// Merge state of $document into existing (managed) document |
2047
|
13 |
|
foreach ($class->reflClass->getProperties() as $prop) { |
2048
|
13 |
|
$name = $prop->name; |
2049
|
13 |
|
$prop->setAccessible(true); |
2050
|
13 |
|
if ( ! isset($class->associationMappings[$name])) { |
2051
|
13 |
|
if ( ! $class->isIdentifier($name)) { |
2052
|
13 |
|
$prop->setValue($managedCopy, $prop->getValue($document)); |
2053
|
13 |
|
} |
2054
|
13 |
|
} else { |
2055
|
13 |
|
$assoc2 = $class->associationMappings[$name]; |
2056
|
|
|
|
2057
|
13 |
|
if ($assoc2['type'] === 'one') { |
2058
|
5 |
|
$other = $prop->getValue($document); |
2059
|
|
|
|
2060
|
5 |
|
if ($other === null) { |
2061
|
2 |
|
$prop->setValue($managedCopy, null); |
2062
|
5 |
|
} elseif ($other instanceof Proxy && ! $other->__isInitialized__) { |
|
|
|
|
2063
|
|
|
// Do not merge fields marked lazy that have not been fetched |
2064
|
1 |
|
continue; |
2065
|
3 |
|
} elseif ( ! $assoc2['isCascadeMerge']) { |
2066
|
|
|
if ($this->getDocumentState($other) === self::STATE_DETACHED) { |
2067
|
|
|
$targetDocument = isset($assoc2['targetDocument']) ? $assoc2['targetDocument'] : get_class($other); |
2068
|
|
|
/* @var $targetClass \Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo */ |
2069
|
|
|
$targetClass = $this->dm->getClassMetadata($targetDocument); |
2070
|
|
|
$relatedId = $targetClass->getIdentifierObject($other); |
2071
|
|
|
|
2072
|
|
|
if ($targetClass->subClasses) { |
|
|
|
|
2073
|
|
|
$other = $this->dm->find($targetClass->name, $relatedId); |
2074
|
|
|
} else { |
2075
|
|
|
$other = $this |
2076
|
|
|
->dm |
2077
|
|
|
->getProxyFactory() |
2078
|
|
|
->getProxy($assoc2['targetDocument'], array($targetClass->identifier => $relatedId)); |
2079
|
|
|
$this->registerManaged($other, $relatedId, array()); |
|
|
|
|
2080
|
|
|
} |
2081
|
|
|
} |
2082
|
|
|
|
2083
|
|
|
$prop->setValue($managedCopy, $other); |
2084
|
|
|
} |
2085
|
4 |
|
} else { |
2086
|
10 |
|
$mergeCol = $prop->getValue($document); |
2087
|
|
|
|
2088
|
10 |
|
if ($mergeCol instanceof PersistentCollection && ! $mergeCol->isInitialized()) { |
2089
|
|
|
/* Do not merge fields marked lazy that have not |
2090
|
|
|
* been fetched. Keep the lazy persistent collection |
2091
|
|
|
* of the managed copy. |
2092
|
|
|
*/ |
2093
|
3 |
|
continue; |
2094
|
|
|
} |
2095
|
|
|
|
2096
|
7 |
|
$managedCol = $prop->getValue($managedCopy); |
2097
|
|
|
|
2098
|
7 |
|
if ( ! $managedCol) { |
2099
|
2 |
|
$managedCol = new PersistentCollection(new ArrayCollection(), $this->dm, $this); |
2100
|
2 |
|
$managedCol->setOwner($managedCopy, $assoc2); |
2101
|
2 |
|
$prop->setValue($managedCopy, $managedCol); |
2102
|
2 |
|
$this->originalDocumentData[$oid][$name] = $managedCol; |
2103
|
2 |
|
} |
2104
|
|
|
|
2105
|
|
|
/* Note: do not process association's target documents. |
2106
|
|
|
* They will be handled during the cascade. Initialize |
2107
|
|
|
* and, if necessary, clear $managedCol for now. |
2108
|
|
|
*/ |
2109
|
7 |
|
if ($assoc2['isCascadeMerge']) { |
2110
|
7 |
|
$managedCol->initialize(); |
2111
|
|
|
|
2112
|
|
|
// If $managedCol differs from the merged collection, clear and set dirty |
2113
|
7 |
|
if ( ! $managedCol->isEmpty() && $managedCol !== $mergeCol) { |
2114
|
2 |
|
$managedCol->unwrap()->clear(); |
2115
|
2 |
|
$managedCol->setDirty(true); |
2116
|
|
|
|
2117
|
2 |
|
if ($assoc2['isOwningSide'] && $class->isChangeTrackingNotify()) { |
2118
|
|
|
$this->scheduleForSynchronization($managedCopy); |
2119
|
|
|
} |
2120
|
2 |
|
} |
2121
|
7 |
|
} |
2122
|
|
|
} |
2123
|
|
|
} |
2124
|
|
|
|
2125
|
13 |
|
if ($class->isChangeTrackingNotify()) { |
2126
|
|
|
// Just treat all properties as changed, there is no other choice. |
2127
|
|
|
$this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy)); |
2128
|
|
|
} |
2129
|
13 |
|
} |
2130
|
|
|
|
2131
|
13 |
|
if ($class->isChangeTrackingDeferredExplicit()) { |
2132
|
|
|
$this->scheduleForSynchronization($document); |
2133
|
|
|
} |
2134
|
13 |
|
} |
2135
|
|
|
|
2136
|
13 |
|
if ($prevManagedCopy !== null) { |
2137
|
6 |
|
$assocField = $assoc['fieldName']; |
2138
|
6 |
|
$prevClass = $this->dm->getClassMetadata(get_class($prevManagedCopy)); |
2139
|
|
|
|
2140
|
6 |
|
if ($assoc['type'] === 'one') { |
2141
|
2 |
|
$prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy); |
2142
|
2 |
|
} else { |
2143
|
4 |
|
$prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy); |
2144
|
|
|
|
2145
|
4 |
|
if ($assoc['type'] === 'many' && isset($assoc['mappedBy'])) { |
2146
|
1 |
|
$class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy); |
2147
|
1 |
|
} |
2148
|
|
|
} |
2149
|
6 |
|
} |
2150
|
|
|
|
2151
|
|
|
// Mark the managed copy visited as well |
2152
|
13 |
|
$visited[spl_object_hash($managedCopy)] = true; |
2153
|
|
|
|
2154
|
13 |
|
$this->cascadeMerge($document, $managedCopy, $visited); |
2155
|
|
|
|
2156
|
13 |
|
return $managedCopy; |
2157
|
|
|
} |
2158
|
|
|
|
2159
|
|
|
/** |
2160
|
|
|
* Detaches a document from the persistence management. It's persistence will |
2161
|
|
|
* no longer be managed by Doctrine. |
2162
|
|
|
* |
2163
|
|
|
* @param object $document The document to detach. |
2164
|
|
|
*/ |
2165
|
9 |
|
public function detach($document) |
2166
|
|
|
{ |
2167
|
9 |
|
$visited = array(); |
2168
|
9 |
|
$this->doDetach($document, $visited); |
2169
|
9 |
|
} |
2170
|
|
|
|
2171
|
|
|
/** |
2172
|
|
|
* Executes a detach operation on the given document. |
2173
|
|
|
* |
2174
|
|
|
* @param object $document |
2175
|
|
|
* @param array $visited |
2176
|
|
|
* @internal This method always considers documents with an assigned identifier as DETACHED. |
2177
|
|
|
*/ |
2178
|
12 |
|
private function doDetach($document, array &$visited) |
2179
|
|
|
{ |
2180
|
12 |
|
$oid = spl_object_hash($document); |
2181
|
12 |
|
if (isset($visited[$oid])) { |
2182
|
4 |
|
return; // Prevent infinite recursion |
2183
|
|
|
} |
2184
|
|
|
|
2185
|
12 |
|
$visited[$oid] = $document; // mark visited |
2186
|
|
|
|
2187
|
12 |
|
switch ($this->getDocumentState($document, self::STATE_DETACHED)) { |
2188
|
12 |
|
case self::STATE_MANAGED: |
2189
|
12 |
|
$this->removeFromIdentityMap($document); |
2190
|
12 |
|
unset($this->documentInsertions[$oid], $this->documentUpdates[$oid], |
2191
|
12 |
|
$this->documentDeletions[$oid], $this->documentIdentifiers[$oid], |
2192
|
12 |
|
$this->documentStates[$oid], $this->originalDocumentData[$oid], |
2193
|
12 |
|
$this->parentAssociations[$oid], $this->documentUpserts[$oid], |
2194
|
12 |
|
$this->hasScheduledCollections[$oid]); |
2195
|
12 |
|
break; |
2196
|
4 |
|
case self::STATE_NEW: |
2197
|
4 |
|
case self::STATE_DETACHED: |
2198
|
4 |
|
return; |
2199
|
12 |
|
} |
2200
|
|
|
|
2201
|
12 |
|
$this->cascadeDetach($document, $visited); |
2202
|
12 |
|
} |
2203
|
|
|
|
2204
|
|
|
/** |
2205
|
|
|
* Refreshes the state of the given document from the database, overwriting |
2206
|
|
|
* any local, unpersisted changes. |
2207
|
|
|
* |
2208
|
|
|
* @param object $document The document to refresh. |
2209
|
|
|
* @throws \InvalidArgumentException If the document is not MANAGED. |
2210
|
|
|
*/ |
2211
|
12 |
|
public function refresh($document) |
2212
|
|
|
{ |
2213
|
12 |
|
$visited = array(); |
2214
|
12 |
|
$this->doRefresh($document, $visited); |
2215
|
11 |
|
} |
2216
|
|
|
|
2217
|
|
|
/** |
2218
|
|
|
* Executes a refresh operation on a document. |
2219
|
|
|
* |
2220
|
|
|
* @param object $document The document to refresh. |
2221
|
|
|
* @param array $visited The already visited documents during cascades. |
2222
|
|
|
* @throws \InvalidArgumentException If the document is not MANAGED. |
2223
|
|
|
*/ |
2224
|
12 |
|
private function doRefresh($document, array &$visited) |
2225
|
|
|
{ |
2226
|
12 |
|
$oid = spl_object_hash($document); |
2227
|
12 |
|
if (isset($visited[$oid])) { |
2228
|
|
|
return; // Prevent infinite recursion |
2229
|
|
|
} |
2230
|
|
|
|
2231
|
12 |
|
$visited[$oid] = $document; // mark visited |
2232
|
|
|
|
2233
|
12 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2234
|
|
|
|
2235
|
12 |
|
if ( ! $class->isEmbeddedDocument) { |
2236
|
12 |
|
if ($this->getDocumentState($document) == self::STATE_MANAGED) { |
2237
|
11 |
|
$id = $class->getDatabaseIdentifierValue($this->documentIdentifiers[$oid]); |
2238
|
11 |
|
$this->getDocumentPersister($class->name)->refresh($id, $document); |
2239
|
11 |
|
} else { |
2240
|
1 |
|
throw new \InvalidArgumentException("Document is not MANAGED."); |
2241
|
|
|
} |
2242
|
11 |
|
} |
2243
|
|
|
|
2244
|
11 |
|
$this->cascadeRefresh($document, $visited); |
2245
|
11 |
|
} |
2246
|
|
|
|
2247
|
|
|
/** |
2248
|
|
|
* Cascades a refresh operation to associated documents. |
2249
|
|
|
* |
2250
|
|
|
* @param object $document |
2251
|
|
|
* @param array $visited |
2252
|
|
|
*/ |
2253
|
11 |
|
private function cascadeRefresh($document, array &$visited) |
2254
|
|
|
{ |
2255
|
11 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2256
|
|
|
|
2257
|
11 |
|
$associationMappings = array_filter( |
2258
|
11 |
|
$class->associationMappings, |
2259
|
|
|
function ($assoc) { return $assoc['isCascadeRefresh']; } |
2260
|
11 |
|
); |
2261
|
|
|
|
2262
|
11 |
|
foreach ($associationMappings as $mapping) { |
2263
|
7 |
|
$relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); |
2264
|
7 |
|
if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) { |
2265
|
7 |
|
if ($relatedDocuments instanceof PersistentCollection) { |
2266
|
|
|
// Unwrap so that foreach() does not initialize |
2267
|
7 |
|
$relatedDocuments = $relatedDocuments->unwrap(); |
2268
|
7 |
|
} |
2269
|
7 |
|
foreach ($relatedDocuments as $relatedDocument) { |
2270
|
|
|
$this->doRefresh($relatedDocument, $visited); |
2271
|
7 |
|
} |
2272
|
7 |
|
} elseif ($relatedDocuments !== null) { |
2273
|
|
|
$this->doRefresh($relatedDocuments, $visited); |
2274
|
|
|
} |
2275
|
11 |
|
} |
2276
|
11 |
|
} |
2277
|
|
|
|
2278
|
|
|
/** |
2279
|
|
|
* Cascades a detach operation to associated documents. |
2280
|
|
|
* |
2281
|
|
|
* @param object $document |
2282
|
|
|
* @param array $visited |
2283
|
|
|
*/ |
2284
|
12 |
View Code Duplication |
private function cascadeDetach($document, array &$visited) |
|
|
|
|
2285
|
|
|
{ |
2286
|
12 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2287
|
12 |
|
foreach ($class->fieldMappings as $mapping) { |
2288
|
12 |
|
if ( ! $mapping['isCascadeDetach']) { |
2289
|
12 |
|
continue; |
2290
|
|
|
} |
2291
|
7 |
|
$relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); |
2292
|
7 |
|
if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { |
2293
|
7 |
|
if ($relatedDocuments instanceof PersistentCollection) { |
2294
|
|
|
// Unwrap so that foreach() does not initialize |
2295
|
6 |
|
$relatedDocuments = $relatedDocuments->unwrap(); |
2296
|
6 |
|
} |
2297
|
7 |
|
foreach ($relatedDocuments as $relatedDocument) { |
2298
|
5 |
|
$this->doDetach($relatedDocument, $visited); |
2299
|
7 |
|
} |
2300
|
7 |
|
} elseif ($relatedDocuments !== null) { |
2301
|
5 |
|
$this->doDetach($relatedDocuments, $visited); |
2302
|
5 |
|
} |
2303
|
12 |
|
} |
2304
|
12 |
|
} |
2305
|
|
|
/** |
2306
|
|
|
* Cascades a merge operation to associated documents. |
2307
|
|
|
* |
2308
|
|
|
* @param object $document |
2309
|
|
|
* @param object $managedCopy |
2310
|
|
|
* @param array $visited |
2311
|
|
|
*/ |
2312
|
13 |
|
private function cascadeMerge($document, $managedCopy, array &$visited) |
2313
|
|
|
{ |
2314
|
13 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2315
|
|
|
|
2316
|
13 |
|
$associationMappings = array_filter( |
2317
|
13 |
|
$class->associationMappings, |
2318
|
|
|
function ($assoc) { return $assoc['isCascadeMerge']; } |
2319
|
13 |
|
); |
2320
|
|
|
|
2321
|
13 |
|
foreach ($associationMappings as $assoc) { |
2322
|
12 |
|
$relatedDocuments = $class->reflFields[$assoc['fieldName']]->getValue($document); |
2323
|
|
|
|
2324
|
12 |
|
if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) { |
2325
|
8 |
|
if ($relatedDocuments === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) { |
2326
|
|
|
// Collections are the same, so there is nothing to do |
2327
|
|
|
continue; |
2328
|
|
|
} |
2329
|
|
|
|
2330
|
8 |
|
if ($relatedDocuments instanceof PersistentCollection) { |
2331
|
|
|
// Unwrap so that foreach() does not initialize |
2332
|
6 |
|
$relatedDocuments = $relatedDocuments->unwrap(); |
2333
|
6 |
|
} |
2334
|
|
|
|
2335
|
8 |
|
foreach ($relatedDocuments as $relatedDocument) { |
2336
|
4 |
|
$this->doMerge($relatedDocument, $visited, $managedCopy, $assoc); |
2337
|
8 |
|
} |
2338
|
12 |
|
} elseif ($relatedDocuments !== null) { |
2339
|
3 |
|
$this->doMerge($relatedDocuments, $visited, $managedCopy, $assoc); |
2340
|
3 |
|
} |
2341
|
13 |
|
} |
2342
|
13 |
|
} |
2343
|
|
|
|
2344
|
|
|
/** |
2345
|
|
|
* Cascades the save operation to associated documents. |
2346
|
|
|
* |
2347
|
|
|
* @param object $document |
2348
|
|
|
* @param array $visited |
2349
|
|
|
*/ |
2350
|
557 |
|
private function cascadePersist($document, array &$visited) |
2351
|
|
|
{ |
2352
|
557 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2353
|
|
|
|
2354
|
557 |
|
$associationMappings = array_filter( |
2355
|
557 |
|
$class->associationMappings, |
2356
|
|
|
function ($assoc) { return $assoc['isCascadePersist']; } |
2357
|
557 |
|
); |
2358
|
|
|
|
2359
|
557 |
|
foreach ($associationMappings as $fieldName => $mapping) { |
2360
|
381 |
|
$relatedDocuments = $class->reflFields[$fieldName]->getValue($document); |
2361
|
|
|
|
2362
|
381 |
|
if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) { |
2363
|
331 |
|
if ($relatedDocuments instanceof PersistentCollection) { |
2364
|
16 |
|
if ($relatedDocuments->getOwner() !== $document) { |
2365
|
2 |
|
$relatedDocuments = $this->fixPersistentCollectionOwnership($relatedDocuments, $document, $class, $mapping['fieldName']); |
2366
|
2 |
|
} |
2367
|
|
|
// Unwrap so that foreach() does not initialize |
2368
|
16 |
|
$relatedDocuments = $relatedDocuments->unwrap(); |
2369
|
16 |
|
} |
2370
|
|
|
|
2371
|
331 |
|
$count = 0; |
2372
|
331 |
|
foreach ($relatedDocuments as $relatedKey => $relatedDocument) { |
2373
|
180 |
|
if ( ! empty($mapping['embedded'])) { |
2374
|
107 |
|
list(, $knownParent, ) = $this->getParentAssociation($relatedDocument); |
2375
|
107 |
|
if ($knownParent && $knownParent !== $document) { |
2376
|
4 |
|
$relatedDocument = clone $relatedDocument; |
2377
|
4 |
|
$relatedDocuments[$relatedKey] = $relatedDocument; |
2378
|
4 |
|
} |
2379
|
107 |
|
$pathKey = ! isset($mapping['strategy']) || CollectionHelper::isList($mapping['strategy']) ? $count++ : $relatedKey; |
2380
|
107 |
|
$this->setParentAssociation($relatedDocument, $mapping, $document, $mapping['fieldName'] . '.' . $pathKey); |
2381
|
107 |
|
} |
2382
|
180 |
|
$this->doPersist($relatedDocument, $visited); |
2383
|
330 |
|
} |
2384
|
381 |
|
} elseif ($relatedDocuments !== null) { |
2385
|
120 |
|
if ( ! empty($mapping['embedded'])) { |
2386
|
66 |
|
list(, $knownParent, ) = $this->getParentAssociation($relatedDocuments); |
2387
|
66 |
|
if ($knownParent && $knownParent !== $document) { |
2388
|
5 |
|
$relatedDocuments = clone $relatedDocuments; |
2389
|
5 |
|
$class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments); |
2390
|
5 |
|
} |
2391
|
66 |
|
$this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']); |
2392
|
66 |
|
} |
2393
|
120 |
|
$this->doPersist($relatedDocuments, $visited); |
2394
|
119 |
|
} |
2395
|
556 |
|
} |
2396
|
555 |
|
} |
2397
|
|
|
|
2398
|
|
|
/** |
2399
|
|
|
* Cascades the delete operation to associated documents. |
2400
|
|
|
* |
2401
|
|
|
* @param object $document |
2402
|
|
|
* @param array $visited |
2403
|
|
|
*/ |
2404
|
65 |
View Code Duplication |
private function cascadeRemove($document, array &$visited) |
|
|
|
|
2405
|
|
|
{ |
2406
|
65 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2407
|
65 |
|
foreach ($class->fieldMappings as $mapping) { |
2408
|
65 |
|
if ( ! $mapping['isCascadeRemove']) { |
2409
|
65 |
|
continue; |
2410
|
|
|
} |
2411
|
33 |
|
if ($document instanceof Proxy && ! $document->__isInitialized__) { |
|
|
|
|
2412
|
2 |
|
$document->__load(); |
2413
|
2 |
|
} |
2414
|
|
|
|
2415
|
33 |
|
$relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); |
2416
|
33 |
|
if (($relatedDocuments instanceof Collection || is_array($relatedDocuments))) { |
2417
|
|
|
// If its a PersistentCollection initialization is intended! No unwrap! |
2418
|
24 |
|
foreach ($relatedDocuments as $relatedDocument) { |
2419
|
13 |
|
$this->doRemove($relatedDocument, $visited); |
2420
|
24 |
|
} |
2421
|
33 |
|
} elseif ($relatedDocuments !== null) { |
2422
|
12 |
|
$this->doRemove($relatedDocuments, $visited); |
2423
|
12 |
|
} |
2424
|
65 |
|
} |
2425
|
65 |
|
} |
2426
|
|
|
|
2427
|
|
|
/** |
2428
|
|
|
* Acquire a lock on the given document. |
2429
|
|
|
* |
2430
|
|
|
* @param object $document |
2431
|
|
|
* @param int $lockMode |
2432
|
|
|
* @param int $lockVersion |
2433
|
|
|
* @throws LockException |
2434
|
|
|
* @throws \InvalidArgumentException |
2435
|
|
|
*/ |
2436
|
9 |
|
public function lock($document, $lockMode, $lockVersion = null) |
2437
|
|
|
{ |
2438
|
9 |
|
if ($this->getDocumentState($document) != self::STATE_MANAGED) { |
2439
|
1 |
|
throw new \InvalidArgumentException("Document is not MANAGED."); |
2440
|
|
|
} |
2441
|
|
|
|
2442
|
8 |
|
$documentName = get_class($document); |
2443
|
8 |
|
$class = $this->dm->getClassMetadata($documentName); |
2444
|
|
|
|
2445
|
8 |
|
if ($lockMode == LockMode::OPTIMISTIC) { |
2446
|
3 |
|
if ( ! $class->isVersioned) { |
2447
|
1 |
|
throw LockException::notVersioned($documentName); |
2448
|
|
|
} |
2449
|
|
|
|
2450
|
2 |
|
if ($lockVersion != null) { |
|
|
|
|
2451
|
2 |
|
$documentVersion = $class->reflFields[$class->versionField]->getValue($document); |
2452
|
2 |
|
if ($documentVersion != $lockVersion) { |
2453
|
1 |
|
throw LockException::lockFailedVersionMissmatch($document, $lockVersion, $documentVersion); |
2454
|
|
|
} |
2455
|
1 |
|
} |
2456
|
6 |
|
} elseif (in_array($lockMode, array(LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE))) { |
2457
|
5 |
|
$this->getDocumentPersister($class->name)->lock($document, $lockMode); |
2458
|
5 |
|
} |
2459
|
6 |
|
} |
2460
|
|
|
|
2461
|
|
|
/** |
2462
|
|
|
* Releases a lock on the given document. |
2463
|
|
|
* |
2464
|
|
|
* @param object $document |
2465
|
|
|
* @throws \InvalidArgumentException |
2466
|
|
|
*/ |
2467
|
1 |
|
public function unlock($document) |
2468
|
|
|
{ |
2469
|
1 |
|
if ($this->getDocumentState($document) != self::STATE_MANAGED) { |
2470
|
|
|
throw new \InvalidArgumentException("Document is not MANAGED."); |
2471
|
|
|
} |
2472
|
1 |
|
$documentName = get_class($document); |
2473
|
1 |
|
$this->getDocumentPersister($documentName)->unlock($document); |
2474
|
1 |
|
} |
2475
|
|
|
|
2476
|
|
|
/** |
2477
|
|
|
* Clears the UnitOfWork. |
2478
|
|
|
* |
2479
|
|
|
* @param string|null $documentName if given, only documents of this type will get detached. |
2480
|
|
|
*/ |
2481
|
385 |
|
public function clear($documentName = null) |
2482
|
|
|
{ |
2483
|
385 |
|
if ($documentName === null) { |
2484
|
379 |
|
$this->identityMap = |
2485
|
379 |
|
$this->documentIdentifiers = |
2486
|
379 |
|
$this->originalDocumentData = |
2487
|
379 |
|
$this->documentChangeSets = |
2488
|
379 |
|
$this->documentStates = |
2489
|
379 |
|
$this->scheduledForSynchronization = |
2490
|
379 |
|
$this->documentInsertions = |
2491
|
379 |
|
$this->documentUpserts = |
2492
|
379 |
|
$this->documentUpdates = |
2493
|
379 |
|
$this->documentDeletions = |
2494
|
379 |
|
$this->collectionUpdates = |
2495
|
379 |
|
$this->collectionDeletions = |
2496
|
379 |
|
$this->parentAssociations = |
2497
|
379 |
|
$this->orphanRemovals = |
2498
|
379 |
|
$this->hasScheduledCollections = array(); |
2499
|
379 |
|
} else { |
2500
|
6 |
|
$visited = array(); |
2501
|
6 |
|
foreach ($this->identityMap as $className => $documents) { |
2502
|
6 |
|
if ($className === $documentName) { |
2503
|
3 |
|
foreach ($documents as $document) { |
2504
|
3 |
|
$this->doDetach($document, $visited, true); |
|
|
|
|
2505
|
3 |
|
} |
2506
|
3 |
|
} |
2507
|
6 |
|
} |
2508
|
|
|
} |
2509
|
|
|
|
2510
|
385 |
View Code Duplication |
if ($this->evm->hasListeners(Events::onClear)) { |
|
|
|
|
2511
|
|
|
$this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->dm, $documentName)); |
2512
|
|
|
} |
2513
|
385 |
|
} |
2514
|
|
|
|
2515
|
|
|
/** |
2516
|
|
|
* INTERNAL: |
2517
|
|
|
* Schedules an embedded document for removal. The remove() operation will be |
2518
|
|
|
* invoked on that document at the beginning of the next commit of this |
2519
|
|
|
* UnitOfWork. |
2520
|
|
|
* |
2521
|
|
|
* @ignore |
2522
|
|
|
* @param object $document |
2523
|
|
|
*/ |
2524
|
48 |
|
public function scheduleOrphanRemoval($document) |
2525
|
|
|
{ |
2526
|
48 |
|
$this->orphanRemovals[spl_object_hash($document)] = $document; |
2527
|
48 |
|
} |
2528
|
|
|
|
2529
|
|
|
/** |
2530
|
|
|
* INTERNAL: |
2531
|
|
|
* Unschedules an embedded or referenced object for removal. |
2532
|
|
|
* |
2533
|
|
|
* @ignore |
2534
|
|
|
* @param object $document |
2535
|
|
|
*/ |
2536
|
95 |
|
public function unscheduleOrphanRemoval($document) |
2537
|
|
|
{ |
2538
|
95 |
|
$oid = spl_object_hash($document); |
2539
|
95 |
|
if (isset($this->orphanRemovals[$oid])) { |
2540
|
3 |
|
unset($this->orphanRemovals[$oid]); |
2541
|
3 |
|
} |
2542
|
95 |
|
} |
2543
|
|
|
|
2544
|
|
|
/** |
2545
|
|
|
* Fixes PersistentCollection state if it wasn't used exactly as we had in mind: |
2546
|
|
|
* 1) sets owner if it was cloned |
2547
|
|
|
* 2) clones collection, sets owner, updates document's property and, if necessary, updates originalData |
2548
|
|
|
* 3) NOP if state is OK |
2549
|
|
|
* Returned collection should be used from now on (only important with 2nd point) |
2550
|
|
|
* |
2551
|
|
|
* @param PersistentCollection $coll |
2552
|
|
|
* @param object $document |
2553
|
|
|
* @param ClassMetadata $class |
2554
|
|
|
* @param string $propName |
2555
|
|
|
* @return PersistentCollection |
2556
|
|
|
*/ |
2557
|
8 |
|
private function fixPersistentCollectionOwnership(PersistentCollection $coll, $document, ClassMetadata $class, $propName) |
2558
|
|
|
{ |
2559
|
8 |
|
$owner = $coll->getOwner(); |
2560
|
8 |
|
if ($owner === null) { // cloned |
2561
|
6 |
|
$coll->setOwner($document, $class->fieldMappings[$propName]); |
2562
|
8 |
|
} elseif ($owner !== $document) { // no clone, we have to fix |
2563
|
2 |
|
if ( ! $coll->isInitialized()) { |
2564
|
1 |
|
$coll->initialize(); // we have to do this otherwise the cols share state |
2565
|
1 |
|
} |
2566
|
2 |
|
$newValue = clone $coll; |
2567
|
2 |
|
$newValue->setOwner($document, $class->fieldMappings[$propName]); |
2568
|
2 |
|
$class->reflFields[$propName]->setValue($document, $newValue); |
2569
|
2 |
|
if ($this->isScheduledForUpdate($document)) { |
2570
|
|
|
// @todo following line should be superfluous once collections are stored in change sets |
2571
|
|
|
$this->setOriginalDocumentProperty(spl_object_hash($document), $propName, $newValue); |
2572
|
|
|
} |
2573
|
2 |
|
return $newValue; |
2574
|
|
|
} |
2575
|
6 |
|
return $coll; |
2576
|
|
|
} |
2577
|
|
|
|
2578
|
|
|
/** |
2579
|
|
|
* INTERNAL: |
2580
|
|
|
* Schedules a complete collection for removal when this UnitOfWork commits. |
2581
|
|
|
* |
2582
|
|
|
* @param PersistentCollection $coll |
2583
|
|
|
*/ |
2584
|
41 |
|
public function scheduleCollectionDeletion(PersistentCollection $coll) |
2585
|
|
|
{ |
2586
|
41 |
|
$oid = spl_object_hash($coll); |
2587
|
41 |
|
unset($this->collectionUpdates[$oid]); |
2588
|
41 |
|
if ( ! isset($this->collectionDeletions[$oid])) { |
2589
|
41 |
|
$this->collectionDeletions[$oid] = $coll; |
2590
|
41 |
|
$this->scheduleCollectionOwner($coll); |
2591
|
41 |
|
} |
2592
|
41 |
|
} |
2593
|
|
|
|
2594
|
|
|
/** |
2595
|
|
|
* Checks whether a PersistentCollection is scheduled for deletion. |
2596
|
|
|
* |
2597
|
|
|
* @param PersistentCollection $coll |
2598
|
|
|
* @return boolean |
2599
|
|
|
*/ |
2600
|
102 |
|
public function isCollectionScheduledForDeletion(PersistentCollection $coll) |
2601
|
|
|
{ |
2602
|
102 |
|
return isset($this->collectionDeletions[spl_object_hash($coll)]); |
2603
|
|
|
} |
2604
|
|
|
|
2605
|
|
|
/** |
2606
|
|
|
* INTERNAL: |
2607
|
|
|
* Unschedules a collection from being deleted when this UnitOfWork commits. |
2608
|
|
|
* |
2609
|
|
|
* @param \Doctrine\ODM\MongoDB\PersistentCollection $coll |
2610
|
|
|
*/ |
2611
|
198 |
View Code Duplication |
public function unscheduleCollectionDeletion(PersistentCollection $coll) |
|
|
|
|
2612
|
|
|
{ |
2613
|
198 |
|
$oid = spl_object_hash($coll); |
2614
|
198 |
|
if (isset($this->collectionDeletions[$oid])) { |
2615
|
11 |
|
$topmostOwner = $this->getOwningDocument($coll->getOwner()); |
2616
|
11 |
|
unset($this->collectionDeletions[$oid]); |
2617
|
11 |
|
unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]); |
2618
|
11 |
|
} |
2619
|
198 |
|
} |
2620
|
|
|
|
2621
|
|
|
/** |
2622
|
|
|
* INTERNAL: |
2623
|
|
|
* Schedules a collection for update when this UnitOfWork commits. |
2624
|
|
|
* |
2625
|
|
|
* @param PersistentCollection $coll |
2626
|
|
|
*/ |
2627
|
213 |
|
public function scheduleCollectionUpdate(PersistentCollection $coll) |
2628
|
|
|
{ |
2629
|
213 |
|
$mapping = $coll->getMapping(); |
2630
|
213 |
|
if (CollectionHelper::usesSet($mapping['strategy'])) { |
2631
|
|
|
/* There is no need to $unset collection if it will be $set later |
2632
|
|
|
* This is NOP if collection is not scheduled for deletion |
2633
|
|
|
*/ |
2634
|
34 |
|
$this->unscheduleCollectionDeletion($coll); |
2635
|
34 |
|
} |
2636
|
213 |
|
$oid = spl_object_hash($coll); |
2637
|
213 |
|
if ( ! isset($this->collectionUpdates[$oid])) { |
2638
|
213 |
|
$this->collectionUpdates[$oid] = $coll; |
2639
|
213 |
|
$this->scheduleCollectionOwner($coll); |
2640
|
213 |
|
} |
2641
|
213 |
|
} |
2642
|
|
|
|
2643
|
|
|
/** |
2644
|
|
|
* INTERNAL: |
2645
|
|
|
* Unschedules a collection from being updated when this UnitOfWork commits. |
2646
|
|
|
* |
2647
|
|
|
* @param \Doctrine\ODM\MongoDB\PersistentCollection $coll |
2648
|
|
|
*/ |
2649
|
198 |
View Code Duplication |
public function unscheduleCollectionUpdate(PersistentCollection $coll) |
|
|
|
|
2650
|
|
|
{ |
2651
|
198 |
|
$oid = spl_object_hash($coll); |
2652
|
198 |
|
if (isset($this->collectionUpdates[$oid])) { |
2653
|
188 |
|
$topmostOwner = $this->getOwningDocument($coll->getOwner()); |
2654
|
188 |
|
unset($this->collectionUpdates[$oid]); |
2655
|
188 |
|
unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]); |
2656
|
188 |
|
} |
2657
|
198 |
|
} |
2658
|
|
|
|
2659
|
|
|
/** |
2660
|
|
|
* Checks whether a PersistentCollection is scheduled for update. |
2661
|
|
|
* |
2662
|
|
|
* @param PersistentCollection $coll |
2663
|
|
|
* @return boolean |
2664
|
|
|
*/ |
2665
|
114 |
|
public function isCollectionScheduledForUpdate(PersistentCollection $coll) |
2666
|
|
|
{ |
2667
|
114 |
|
return isset($this->collectionUpdates[spl_object_hash($coll)]); |
2668
|
|
|
} |
2669
|
|
|
|
2670
|
|
|
/** |
2671
|
|
|
* INTERNAL: |
2672
|
|
|
* Gets PersistentCollections that have been visited during computing change |
2673
|
|
|
* set of $document |
2674
|
|
|
* |
2675
|
|
|
* @param object $document |
2676
|
|
|
* @return PersistentCollection[] |
2677
|
|
|
*/ |
2678
|
525 |
|
public function getVisitedCollections($document) |
2679
|
|
|
{ |
2680
|
525 |
|
$oid = spl_object_hash($document); |
2681
|
525 |
|
return isset($this->visitedCollections[$oid]) |
2682
|
525 |
|
? $this->visitedCollections[$oid] |
2683
|
525 |
|
: array(); |
2684
|
|
|
} |
2685
|
|
|
|
2686
|
|
|
/** |
2687
|
|
|
* INTERNAL: |
2688
|
|
|
* Gets PersistentCollections that are scheduled to update and related to $document |
2689
|
|
|
* |
2690
|
|
|
* @param object $document |
2691
|
|
|
* @return array |
2692
|
|
|
*/ |
2693
|
525 |
|
public function getScheduledCollections($document) |
2694
|
|
|
{ |
2695
|
525 |
|
$oid = spl_object_hash($document); |
2696
|
525 |
|
return isset($this->hasScheduledCollections[$oid]) |
2697
|
525 |
|
? $this->hasScheduledCollections[$oid] |
2698
|
525 |
|
: array(); |
2699
|
|
|
} |
2700
|
|
|
|
2701
|
|
|
/** |
2702
|
|
|
* Checks whether the document is related to a PersistentCollection |
2703
|
|
|
* scheduled for update or deletion. |
2704
|
|
|
* |
2705
|
|
|
* @param object $document |
2706
|
|
|
* @return boolean |
2707
|
|
|
*/ |
2708
|
52 |
|
public function hasScheduledCollections($document) |
2709
|
|
|
{ |
2710
|
52 |
|
return isset($this->hasScheduledCollections[spl_object_hash($document)]); |
2711
|
|
|
} |
2712
|
|
|
|
2713
|
|
|
/** |
2714
|
|
|
* Marks the PersistentCollection's top-level owner as having a relation to |
2715
|
|
|
* a collection scheduled for update or deletion. |
2716
|
|
|
* |
2717
|
|
|
* If the owner is not scheduled for any lifecycle action, it will be |
2718
|
|
|
* scheduled for update to ensure that versioning takes place if necessary. |
2719
|
|
|
* |
2720
|
|
|
* If the collection is nested within atomic collection, it is immediately |
2721
|
|
|
* unscheduled and atomic one is scheduled for update instead. This makes |
2722
|
|
|
* calculating update data way easier. |
2723
|
|
|
* |
2724
|
|
|
* @param PersistentCollection $coll |
2725
|
|
|
*/ |
2726
|
215 |
|
private function scheduleCollectionOwner(PersistentCollection $coll) |
2727
|
|
|
{ |
2728
|
215 |
|
$document = $this->getOwningDocument($coll->getOwner()); |
2729
|
215 |
|
$this->hasScheduledCollections[spl_object_hash($document)][spl_object_hash($coll)] = $coll; |
2730
|
|
|
|
2731
|
215 |
|
if ($document !== $coll->getOwner()) { |
2732
|
24 |
|
$parent = $coll->getOwner(); |
2733
|
24 |
|
while (null !== ($parentAssoc = $this->getParentAssociation($parent))) { |
2734
|
24 |
|
list($mapping, $parent, ) = $parentAssoc; |
2735
|
24 |
|
} |
2736
|
24 |
|
if (isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) { |
2737
|
7 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2738
|
7 |
|
$atomicCollection = $class->getFieldValue($document, $mapping['fieldName']); |
2739
|
7 |
|
$this->scheduleCollectionUpdate($atomicCollection); |
2740
|
7 |
|
$this->unscheduleCollectionDeletion($coll); |
2741
|
7 |
|
$this->unscheduleCollectionUpdate($coll); |
2742
|
7 |
|
} |
2743
|
24 |
|
} |
2744
|
|
|
|
2745
|
215 |
|
if ( ! $this->isDocumentScheduled($document)) { |
2746
|
85 |
|
$this->scheduleForUpdate($document); |
2747
|
85 |
|
} |
2748
|
215 |
|
} |
2749
|
|
|
|
2750
|
|
|
/** |
2751
|
|
|
* Get the top-most owning document of a given document |
2752
|
|
|
* |
2753
|
|
|
* If a top-level document is provided, that same document will be returned. |
2754
|
|
|
* For an embedded document, we will walk through parent associations until |
2755
|
|
|
* we find a top-level document. |
2756
|
|
|
* |
2757
|
|
|
* @param object $document |
2758
|
|
|
* @throws \UnexpectedValueException when a top-level document could not be found |
2759
|
|
|
* @return object |
2760
|
|
|
*/ |
2761
|
217 |
|
public function getOwningDocument($document) |
2762
|
|
|
{ |
2763
|
217 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2764
|
217 |
|
while ($class->isEmbeddedDocument) { |
2765
|
38 |
|
$parentAssociation = $this->getParentAssociation($document); |
2766
|
|
|
|
2767
|
38 |
|
if ( ! $parentAssociation) { |
|
|
|
|
2768
|
|
|
throw new \UnexpectedValueException("Could not determine parent association for " . get_class($document)); |
2769
|
|
|
} |
2770
|
|
|
|
2771
|
38 |
|
list(, $document, ) = $parentAssociation; |
2772
|
38 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
2773
|
38 |
|
} |
2774
|
|
|
|
2775
|
217 |
|
return $document; |
2776
|
|
|
} |
2777
|
|
|
|
2778
|
|
|
/** |
2779
|
|
|
* Gets the class name for an association (embed or reference) with respect |
2780
|
|
|
* to any discriminator value. |
2781
|
|
|
* |
2782
|
|
|
* @param array $mapping Field mapping for the association |
2783
|
|
|
* @param array|null $data Data for the embedded document or reference |
2784
|
|
|
*/ |
2785
|
199 |
|
public function getClassNameForAssociation(array $mapping, $data) |
2786
|
|
|
{ |
2787
|
199 |
|
$discriminatorField = isset($mapping['discriminatorField']) ? $mapping['discriminatorField'] : null; |
2788
|
|
|
|
2789
|
199 |
|
$discriminatorValue = null; |
2790
|
199 |
|
if (isset($discriminatorField, $data[$discriminatorField])) { |
2791
|
21 |
|
$discriminatorValue = $data[$discriminatorField]; |
2792
|
199 |
|
} elseif (isset($mapping['defaultDiscriminatorValue'])) { |
2793
|
|
|
$discriminatorValue = $mapping['defaultDiscriminatorValue']; |
2794
|
|
|
} |
2795
|
|
|
|
2796
|
199 |
|
if ($discriminatorValue !== null) { |
2797
|
21 |
|
return isset($mapping['discriminatorMap'][$discriminatorValue]) |
2798
|
21 |
|
? $mapping['discriminatorMap'][$discriminatorValue] |
2799
|
21 |
|
: $discriminatorValue; |
2800
|
|
|
} |
2801
|
|
|
|
2802
|
179 |
|
$class = $this->dm->getClassMetadata($mapping['targetDocument']); |
2803
|
|
|
|
2804
|
179 |
View Code Duplication |
if (isset($class->discriminatorField, $data[$class->discriminatorField])) { |
|
|
|
|
2805
|
15 |
|
$discriminatorValue = $data[$class->discriminatorField]; |
2806
|
179 |
|
} elseif ($class->defaultDiscriminatorValue !== null) { |
2807
|
1 |
|
$discriminatorValue = $class->defaultDiscriminatorValue; |
2808
|
1 |
|
} |
2809
|
|
|
|
2810
|
179 |
|
if ($discriminatorValue !== null) { |
2811
|
16 |
|
return isset($class->discriminatorMap[$discriminatorValue]) |
2812
|
16 |
|
? $class->discriminatorMap[$discriminatorValue] |
2813
|
16 |
|
: $discriminatorValue; |
2814
|
|
|
} |
2815
|
|
|
|
2816
|
163 |
|
return $mapping['targetDocument']; |
2817
|
|
|
} |
2818
|
|
|
|
2819
|
|
|
/** |
2820
|
|
|
* INTERNAL: |
2821
|
|
|
* Creates a document. Used for reconstitution of documents during hydration. |
2822
|
|
|
* |
2823
|
|
|
* @ignore |
2824
|
|
|
* @param string $className The name of the document class. |
2825
|
|
|
* @param array $data The data for the document. |
2826
|
|
|
* @param array $hints Any hints to account for during reconstitution/lookup of the document. |
2827
|
|
|
* @param object The document to be hydrated into in case of creation |
2828
|
|
|
* @return object The document instance. |
2829
|
|
|
* @internal Highly performance-sensitive method. |
2830
|
|
|
*/ |
2831
|
380 |
|
public function getOrCreateDocument($className, $data, &$hints = array(), $document = null) |
2832
|
|
|
{ |
2833
|
380 |
|
$class = $this->dm->getClassMetadata($className); |
2834
|
|
|
|
2835
|
|
|
// @TODO figure out how to remove this |
2836
|
380 |
|
$discriminatorValue = null; |
2837
|
380 |
View Code Duplication |
if (isset($class->discriminatorField, $data[$class->discriminatorField])) { |
|
|
|
|
2838
|
17 |
|
$discriminatorValue = $data[$class->discriminatorField]; |
2839
|
380 |
|
} elseif (isset($class->defaultDiscriminatorValue)) { |
2840
|
2 |
|
$discriminatorValue = $class->defaultDiscriminatorValue; |
2841
|
2 |
|
} |
2842
|
|
|
|
2843
|
380 |
|
if ($discriminatorValue !== null) { |
2844
|
18 |
|
$className = isset($class->discriminatorMap[$discriminatorValue]) |
2845
|
18 |
|
? $class->discriminatorMap[$discriminatorValue] |
2846
|
18 |
|
: $discriminatorValue; |
2847
|
|
|
|
2848
|
18 |
|
$class = $this->dm->getClassMetadata($className); |
2849
|
|
|
|
2850
|
18 |
|
unset($data[$class->discriminatorField]); |
2851
|
18 |
|
} |
2852
|
|
|
|
2853
|
380 |
|
$id = $class->getDatabaseIdentifierValue($data['_id']); |
2854
|
380 |
|
$serializedId = serialize($id); |
2855
|
|
|
|
2856
|
380 |
|
if (isset($this->identityMap[$class->name][$serializedId])) { |
2857
|
89 |
|
$document = $this->identityMap[$class->name][$serializedId]; |
2858
|
89 |
|
$oid = spl_object_hash($document); |
2859
|
89 |
|
if ($document instanceof Proxy && ! $document->__isInitialized__) { |
|
|
|
|
2860
|
10 |
|
$document->__isInitialized__ = true; |
|
|
|
|
2861
|
10 |
|
$overrideLocalValues = true; |
2862
|
10 |
|
if ($document instanceof NotifyPropertyChanged) { |
2863
|
|
|
$document->addPropertyChangedListener($this); |
2864
|
|
|
} |
2865
|
10 |
|
} else { |
2866
|
85 |
|
$overrideLocalValues = ! empty($hints[Query::HINT_REFRESH]); |
2867
|
|
|
} |
2868
|
89 |
|
if ($overrideLocalValues) { |
2869
|
46 |
|
$data = $this->hydratorFactory->hydrate($document, $data, $hints); |
2870
|
46 |
|
$this->originalDocumentData[$oid] = $data; |
2871
|
46 |
|
} |
2872
|
89 |
|
} else { |
2873
|
352 |
|
if ($document === null) { |
2874
|
352 |
|
$document = $class->newInstance(); |
2875
|
352 |
|
} |
2876
|
352 |
|
$this->registerManaged($document, $id, $data); |
2877
|
352 |
|
$oid = spl_object_hash($document); |
2878
|
352 |
|
$this->documentStates[$oid] = self::STATE_MANAGED; |
2879
|
352 |
|
$this->identityMap[$class->name][$serializedId] = $document; |
2880
|
352 |
|
$data = $this->hydratorFactory->hydrate($document, $data, $hints); |
2881
|
352 |
|
$this->originalDocumentData[$oid] = $data; |
2882
|
|
|
} |
2883
|
380 |
|
return $document; |
2884
|
|
|
} |
2885
|
|
|
|
2886
|
|
|
/** |
2887
|
|
|
* Initializes (loads) an uninitialized persistent collection of a document. |
2888
|
|
|
* |
2889
|
|
|
* @param PersistentCollection $collection The collection to initialize. |
2890
|
|
|
*/ |
2891
|
149 |
|
public function loadCollection(PersistentCollection $collection) |
2892
|
|
|
{ |
2893
|
149 |
|
$this->getDocumentPersister(get_class($collection->getOwner()))->loadCollection($collection); |
2894
|
149 |
|
} |
2895
|
|
|
|
2896
|
|
|
/** |
2897
|
|
|
* Gets the identity map of the UnitOfWork. |
2898
|
|
|
* |
2899
|
|
|
* @return array |
2900
|
|
|
*/ |
2901
|
|
|
public function getIdentityMap() |
2902
|
|
|
{ |
2903
|
|
|
return $this->identityMap; |
2904
|
|
|
} |
2905
|
|
|
|
2906
|
|
|
/** |
2907
|
|
|
* Gets the original data of a document. The original data is the data that was |
2908
|
|
|
* present at the time the document was reconstituted from the database. |
2909
|
|
|
* |
2910
|
|
|
* @param object $document |
2911
|
|
|
* @return array |
2912
|
|
|
*/ |
2913
|
1 |
|
public function getOriginalDocumentData($document) |
2914
|
|
|
{ |
2915
|
1 |
|
$oid = spl_object_hash($document); |
2916
|
1 |
|
if (isset($this->originalDocumentData[$oid])) { |
2917
|
1 |
|
return $this->originalDocumentData[$oid]; |
2918
|
|
|
} |
2919
|
|
|
return array(); |
2920
|
|
|
} |
2921
|
|
|
|
2922
|
|
|
/** |
2923
|
|
|
* @ignore |
2924
|
|
|
*/ |
2925
|
42 |
|
public function setOriginalDocumentData($document, array $data) |
2926
|
|
|
{ |
2927
|
42 |
|
$this->originalDocumentData[spl_object_hash($document)] = $data; |
2928
|
42 |
|
} |
2929
|
|
|
|
2930
|
|
|
/** |
2931
|
|
|
* INTERNAL: |
2932
|
|
|
* Sets a property value of the original data array of a document. |
2933
|
|
|
* |
2934
|
|
|
* @ignore |
2935
|
|
|
* @param string $oid |
2936
|
|
|
* @param string $property |
2937
|
|
|
* @param mixed $value |
2938
|
|
|
*/ |
2939
|
3 |
|
public function setOriginalDocumentProperty($oid, $property, $value) |
2940
|
|
|
{ |
2941
|
3 |
|
$this->originalDocumentData[$oid][$property] = $value; |
2942
|
3 |
|
} |
2943
|
|
|
|
2944
|
|
|
/** |
2945
|
|
|
* Gets the identifier of a document. |
2946
|
|
|
* |
2947
|
|
|
* @param object $document |
2948
|
|
|
* @return mixed The identifier value |
2949
|
|
|
*/ |
2950
|
345 |
|
public function getDocumentIdentifier($document) |
2951
|
|
|
{ |
2952
|
345 |
|
return isset($this->documentIdentifiers[spl_object_hash($document)]) ? |
2953
|
345 |
|
$this->documentIdentifiers[spl_object_hash($document)] : null; |
2954
|
|
|
} |
2955
|
|
|
|
2956
|
|
|
/** |
2957
|
|
|
* Checks whether the UnitOfWork has any pending insertions. |
2958
|
|
|
* |
2959
|
|
|
* @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise. |
2960
|
|
|
*/ |
2961
|
|
|
public function hasPendingInsertions() |
2962
|
|
|
{ |
2963
|
|
|
return ! empty($this->documentInsertions); |
2964
|
|
|
} |
2965
|
|
|
|
2966
|
|
|
/** |
2967
|
|
|
* Calculates the size of the UnitOfWork. The size of the UnitOfWork is the |
2968
|
|
|
* number of documents in the identity map. |
2969
|
|
|
* |
2970
|
|
|
* @return integer |
2971
|
|
|
*/ |
2972
|
2 |
|
public function size() |
2973
|
|
|
{ |
2974
|
2 |
|
$count = 0; |
2975
|
2 |
|
foreach ($this->identityMap as $documentSet) { |
2976
|
2 |
|
$count += count($documentSet); |
2977
|
2 |
|
} |
2978
|
2 |
|
return $count; |
2979
|
|
|
} |
2980
|
|
|
|
2981
|
|
|
/** |
2982
|
|
|
* INTERNAL: |
2983
|
|
|
* Registers a document as managed. |
2984
|
|
|
* |
2985
|
|
|
* TODO: This method assumes that $id is a valid PHP identifier for the |
2986
|
|
|
* document class. If the class expects its database identifier to be a |
2987
|
|
|
* MongoId, and an incompatible $id is registered (e.g. an integer), the |
2988
|
|
|
* document identifiers map will become inconsistent with the identity map. |
2989
|
|
|
* In the future, we may want to round-trip $id through a PHP and database |
2990
|
|
|
* conversion and throw an exception if it's inconsistent. |
2991
|
|
|
* |
2992
|
|
|
* @param object $document The document. |
2993
|
|
|
* @param array $id The identifier values. |
2994
|
|
|
* @param array $data The original document data. |
2995
|
|
|
*/ |
2996
|
368 |
|
public function registerManaged($document, $id, array $data) |
2997
|
|
|
{ |
2998
|
368 |
|
$oid = spl_object_hash($document); |
2999
|
368 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
3000
|
|
|
|
3001
|
368 |
|
if ( ! $class->identifier || $id === null) { |
3002
|
102 |
|
$this->documentIdentifiers[$oid] = $oid; |
3003
|
102 |
|
} else { |
3004
|
362 |
|
$this->documentIdentifiers[$oid] = $class->getPHPIdentifierValue($id); |
3005
|
|
|
} |
3006
|
|
|
|
3007
|
368 |
|
$this->documentStates[$oid] = self::STATE_MANAGED; |
3008
|
368 |
|
$this->originalDocumentData[$oid] = $data; |
3009
|
368 |
|
$this->addToIdentityMap($document); |
3010
|
368 |
|
} |
3011
|
|
|
|
3012
|
|
|
/** |
3013
|
|
|
* INTERNAL: |
3014
|
|
|
* Clears the property changeset of the document with the given OID. |
3015
|
|
|
* |
3016
|
|
|
* @param string $oid The document's OID. |
3017
|
|
|
*/ |
3018
|
1 |
|
public function clearDocumentChangeSet($oid) |
3019
|
|
|
{ |
3020
|
1 |
|
$this->documentChangeSets[$oid] = array(); |
3021
|
1 |
|
} |
3022
|
|
|
|
3023
|
|
|
/* PropertyChangedListener implementation */ |
3024
|
|
|
|
3025
|
|
|
/** |
3026
|
|
|
* Notifies this UnitOfWork of a property change in a document. |
3027
|
|
|
* |
3028
|
|
|
* @param object $document The document that owns the property. |
3029
|
|
|
* @param string $propertyName The name of the property that changed. |
3030
|
|
|
* @param mixed $oldValue The old value of the property. |
3031
|
|
|
* @param mixed $newValue The new value of the property. |
3032
|
|
|
*/ |
3033
|
2 |
|
public function propertyChanged($document, $propertyName, $oldValue, $newValue) |
3034
|
|
|
{ |
3035
|
2 |
|
$oid = spl_object_hash($document); |
3036
|
2 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
3037
|
|
|
|
3038
|
2 |
|
if ( ! isset($class->fieldMappings[$propertyName])) { |
3039
|
1 |
|
return; // ignore non-persistent fields |
3040
|
|
|
} |
3041
|
|
|
|
3042
|
|
|
// Update changeset and mark document for synchronization |
3043
|
2 |
|
$this->documentChangeSets[$oid][$propertyName] = array($oldValue, $newValue); |
3044
|
2 |
|
if ( ! isset($this->scheduledForSynchronization[$class->name][$oid])) { |
3045
|
2 |
|
$this->scheduleForSynchronization($document); |
3046
|
2 |
|
} |
3047
|
2 |
|
} |
3048
|
|
|
|
3049
|
|
|
/** |
3050
|
|
|
* Gets the currently scheduled document insertions in this UnitOfWork. |
3051
|
|
|
* |
3052
|
|
|
* @return array |
3053
|
|
|
*/ |
3054
|
5 |
|
public function getScheduledDocumentInsertions() |
3055
|
|
|
{ |
3056
|
5 |
|
return $this->documentInsertions; |
3057
|
|
|
} |
3058
|
|
|
|
3059
|
|
|
/** |
3060
|
|
|
* Gets the currently scheduled document upserts in this UnitOfWork. |
3061
|
|
|
* |
3062
|
|
|
* @return array |
3063
|
|
|
*/ |
3064
|
3 |
|
public function getScheduledDocumentUpserts() |
3065
|
|
|
{ |
3066
|
3 |
|
return $this->documentUpserts; |
3067
|
|
|
} |
3068
|
|
|
|
3069
|
|
|
/** |
3070
|
|
|
* Gets the currently scheduled document updates in this UnitOfWork. |
3071
|
|
|
* |
3072
|
|
|
* @return array |
3073
|
|
|
*/ |
3074
|
3 |
|
public function getScheduledDocumentUpdates() |
3075
|
|
|
{ |
3076
|
3 |
|
return $this->documentUpdates; |
3077
|
|
|
} |
3078
|
|
|
|
3079
|
|
|
/** |
3080
|
|
|
* Gets the currently scheduled document deletions in this UnitOfWork. |
3081
|
|
|
* |
3082
|
|
|
* @return array |
3083
|
|
|
*/ |
3084
|
|
|
public function getScheduledDocumentDeletions() |
3085
|
|
|
{ |
3086
|
|
|
return $this->documentDeletions; |
3087
|
|
|
} |
3088
|
|
|
|
3089
|
|
|
/** |
3090
|
|
|
* Get the currently scheduled complete collection deletions |
3091
|
|
|
* |
3092
|
|
|
* @return array |
3093
|
|
|
*/ |
3094
|
|
|
public function getScheduledCollectionDeletions() |
3095
|
|
|
{ |
3096
|
|
|
return $this->collectionDeletions; |
3097
|
|
|
} |
3098
|
|
|
|
3099
|
|
|
/** |
3100
|
|
|
* Gets the currently scheduled collection inserts, updates and deletes. |
3101
|
|
|
* |
3102
|
|
|
* @return array |
3103
|
|
|
*/ |
3104
|
|
|
public function getScheduledCollectionUpdates() |
3105
|
|
|
{ |
3106
|
|
|
return $this->collectionUpdates; |
3107
|
|
|
} |
3108
|
|
|
|
3109
|
|
|
/** |
3110
|
|
|
* Helper method to initialize a lazy loading proxy or persistent collection. |
3111
|
|
|
* |
3112
|
|
|
* @param object |
3113
|
|
|
* @return void |
3114
|
|
|
*/ |
3115
|
|
|
public function initializeObject($obj) |
3116
|
|
|
{ |
3117
|
|
|
if ($obj instanceof Proxy) { |
3118
|
|
|
$obj->__load(); |
3119
|
|
|
} elseif ($obj instanceof PersistentCollection) { |
3120
|
|
|
$obj->initialize(); |
3121
|
|
|
} |
3122
|
|
|
} |
3123
|
|
|
|
3124
|
1 |
|
private static function objToStr($obj) |
3125
|
|
|
{ |
3126
|
1 |
|
return method_exists($obj, '__toString') ? (string)$obj : get_class($obj) . '@' . spl_object_hash($obj); |
3127
|
|
|
} |
3128
|
|
|
} |
3129
|
|
|
|
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.