Complex classes like UnitOfWork often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use UnitOfWork, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
45 | class UnitOfWork implements PropertyChangedListener |
||
46 | { |
||
47 | /** |
||
48 | * A document is in MANAGED state when its persistence is managed by a DocumentManager. |
||
49 | */ |
||
50 | public const STATE_MANAGED = 1; |
||
51 | |||
52 | /** |
||
53 | * A document is new if it has just been instantiated (i.e. using the "new" operator) |
||
54 | * and is not (yet) managed by a DocumentManager. |
||
55 | */ |
||
56 | public const STATE_NEW = 2; |
||
57 | |||
58 | /** |
||
59 | * A detached document is an instance with a persistent identity that is not |
||
60 | * (or no longer) associated with a DocumentManager (and a UnitOfWork). |
||
61 | */ |
||
62 | public const STATE_DETACHED = 3; |
||
63 | |||
64 | /** |
||
65 | * A removed document instance is an instance with a persistent identity, |
||
66 | * associated with a DocumentManager, whose persistent state has been |
||
67 | * deleted (or is scheduled for deletion). |
||
68 | */ |
||
69 | public const STATE_REMOVED = 4; |
||
70 | |||
71 | /** |
||
72 | * The identity map holds references to all managed documents. |
||
73 | * |
||
74 | * Documents are grouped by their class name, and then indexed by the |
||
75 | * serialized string of their database identifier field or, if the class |
||
76 | * has no identifier, the SPL object hash. Serializing the identifier allows |
||
77 | * differentiation of values that may be equal (via type juggling) but not |
||
78 | * identical. |
||
79 | * |
||
80 | * Since all classes in a hierarchy must share the same identifier set, |
||
81 | * we always take the root class name of the hierarchy. |
||
82 | * |
||
83 | * @var array |
||
84 | */ |
||
85 | private $identityMap = []; |
||
86 | |||
87 | /** |
||
88 | * Map of all identifiers of managed documents. |
||
89 | * Keys are object ids (spl_object_hash). |
||
90 | * |
||
91 | * @var array |
||
92 | */ |
||
93 | private $documentIdentifiers = []; |
||
94 | |||
95 | /** |
||
96 | * Map of the original document data of managed documents. |
||
97 | * Keys are object ids (spl_object_hash). This is used for calculating changesets |
||
98 | * at commit time. |
||
99 | * |
||
100 | * @internal Note that PHPs "copy-on-write" behavior helps a lot with memory usage. |
||
101 | * A value will only really be copied if the value in the document is modified |
||
102 | * by the user. |
||
103 | * |
||
104 | * @var array |
||
105 | */ |
||
106 | private $originalDocumentData = []; |
||
107 | |||
108 | /** |
||
109 | * Map of document changes. Keys are object ids (spl_object_hash). |
||
110 | * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end. |
||
111 | * |
||
112 | * @var array |
||
113 | */ |
||
114 | private $documentChangeSets = []; |
||
115 | |||
116 | /** |
||
117 | * The (cached) states of any known documents. |
||
118 | * Keys are object ids (spl_object_hash). |
||
119 | * |
||
120 | * @var array |
||
121 | */ |
||
122 | private $documentStates = []; |
||
123 | |||
124 | /** |
||
125 | * Map of documents that are scheduled for dirty checking at commit time. |
||
126 | * |
||
127 | * Documents are grouped by their class name, and then indexed by their SPL |
||
128 | * object hash. This is only used for documents with a change tracking |
||
129 | * policy of DEFERRED_EXPLICIT. |
||
130 | * |
||
131 | * @var array |
||
132 | * @todo rename: scheduledForSynchronization |
||
133 | */ |
||
134 | private $scheduledForDirtyCheck = []; |
||
135 | |||
136 | /** |
||
137 | * A list of all pending document insertions. |
||
138 | * |
||
139 | * @var array |
||
140 | */ |
||
141 | private $documentInsertions = []; |
||
142 | |||
143 | /** |
||
144 | * A list of all pending document updates. |
||
145 | * |
||
146 | * @var array |
||
147 | */ |
||
148 | private $documentUpdates = []; |
||
149 | |||
150 | /** |
||
151 | * A list of all pending document upserts. |
||
152 | * |
||
153 | * @var array |
||
154 | */ |
||
155 | private $documentUpserts = []; |
||
156 | |||
157 | /** |
||
158 | * A list of all pending document deletions. |
||
159 | * |
||
160 | * @var array |
||
161 | */ |
||
162 | private $documentDeletions = []; |
||
163 | |||
164 | /** |
||
165 | * All pending collection deletions. |
||
166 | * |
||
167 | * @var array |
||
168 | */ |
||
169 | private $collectionDeletions = []; |
||
170 | |||
171 | /** |
||
172 | * All pending collection updates. |
||
173 | * |
||
174 | * @var array |
||
175 | */ |
||
176 | private $collectionUpdates = []; |
||
177 | |||
178 | /** |
||
179 | * A list of documents related to collections scheduled for update or deletion |
||
180 | * |
||
181 | * @var array |
||
182 | */ |
||
183 | private $hasScheduledCollections = []; |
||
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 = []; |
||
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 | * Additional documents that are scheduled for removal. |
||
210 | * |
||
211 | * @var array |
||
212 | */ |
||
213 | private $orphanRemovals = []; |
||
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 = []; |
||
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|null |
||
240 | */ |
||
241 | private $persistenceBuilder; |
||
242 | |||
243 | /** |
||
244 | * Array of parent associations between embedded documents. |
||
245 | * |
||
246 | * @var array |
||
247 | */ |
||
248 | private $parentAssociations = []; |
||
249 | |||
250 | /** @var LifecycleEventManager */ |
||
251 | private $lifecycleEventManager; |
||
252 | |||
253 | /** |
||
254 | * Array of embedded documents known to UnitOfWork. We need to hold them to prevent spl_object_hash |
||
255 | * collisions in case already managed object is lost due to GC (so now it won't). Embedded documents |
||
256 | * found during doDetach are removed from the registry, to empty it altogether clear() can be utilized. |
||
257 | * |
||
258 | * @var array |
||
259 | */ |
||
260 | private $embeddedDocumentsRegistry = []; |
||
261 | |||
262 | /** @var int */ |
||
263 | private $commitsInProgress = 0; |
||
264 | |||
265 | /** |
||
266 | * Initializes a new UnitOfWork instance, bound to the given DocumentManager. |
||
267 | */ |
||
268 | 1651 | public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory) |
|
269 | { |
||
270 | 1651 | $this->dm = $dm; |
|
271 | 1651 | $this->evm = $evm; |
|
272 | 1651 | $this->hydratorFactory = $hydratorFactory; |
|
273 | 1651 | $this->lifecycleEventManager = new LifecycleEventManager($dm, $this, $evm); |
|
274 | 1651 | } |
|
275 | |||
276 | /** |
||
277 | * Factory for returning new PersistenceBuilder instances used for preparing data into |
||
278 | * queries for insert persistence. |
||
279 | */ |
||
280 | 1130 | public function getPersistenceBuilder() : PersistenceBuilder |
|
287 | |||
288 | /** |
||
289 | * Sets the parent association for a given embedded document. |
||
290 | */ |
||
291 | 209 | public function setParentAssociation(object $document, array $mapping, ?object $parent, string $propertyPath) : void |
|
297 | |||
298 | /** |
||
299 | * Gets the parent association for a given embedded document. |
||
300 | * |
||
301 | * <code> |
||
302 | * list($mapping, $parent, $propertyPath) = $this->getParentAssociation($embeddedDocument); |
||
303 | * </code> |
||
304 | */ |
||
305 | 233 | public function getParentAssociation(object $document) : ?array |
|
311 | |||
312 | /** |
||
313 | * Get the document persister instance for the given document name |
||
314 | */ |
||
315 | 1128 | public function getDocumentPersister(string $documentName) : Persisters\DocumentPersister |
|
324 | |||
325 | /** |
||
326 | * Get the collection persister instance. |
||
327 | */ |
||
328 | 1128 | public function getCollectionPersister() : CollectionPersister |
|
336 | |||
337 | /** |
||
338 | * Set the document persister instance to use for the given document name |
||
339 | */ |
||
340 | 13 | public function setDocumentPersister(string $documentName, Persisters\DocumentPersister $persister) : void |
|
344 | |||
345 | /** |
||
346 | * Commits the UnitOfWork, executing all operations that have been postponed |
||
347 | * up to this point. The state of all managed documents will be synchronized with |
||
348 | * the database. |
||
349 | * |
||
350 | * The operations are executed in the following order: |
||
351 | * |
||
352 | * 1) All document insertions |
||
353 | * 2) All document updates |
||
354 | * 3) All document deletions |
||
355 | * |
||
356 | * @param array $options Array of options to be used with batchInsert(), update() and remove() |
||
357 | */ |
||
358 | 611 | public function commit(array $options = []) : void |
|
436 | |||
437 | /** |
||
438 | * Groups a list of scheduled documents by their class. |
||
439 | */ |
||
440 | 606 | private function getClassesForCommitAction(array $documents, bool $includeEmbedded = false) : array |
|
469 | |||
470 | /** |
||
471 | * Compute changesets of all documents scheduled for insertion. |
||
472 | * |
||
473 | * Embedded documents will not be processed. |
||
474 | */ |
||
475 | 616 | private function computeScheduleInsertsChangeSets() : void |
|
486 | |||
487 | /** |
||
488 | * Compute changesets of all documents scheduled for upsert. |
||
489 | * |
||
490 | * Embedded documents will not be processed. |
||
491 | */ |
||
492 | 615 | private function computeScheduleUpsertsChangeSets() : void |
|
503 | |||
504 | /** |
||
505 | * Gets the changeset for a document. |
||
506 | * |
||
507 | * @return array array('property' => array(0 => mixed|null, 1 => mixed|null)) |
||
508 | */ |
||
509 | 611 | public function getDocumentChangeSet(object $document) : array |
|
515 | |||
516 | /** |
||
517 | * INTERNAL: |
||
518 | * Sets the changeset for a document. |
||
519 | */ |
||
520 | 1 | public function setDocumentChangeSet(object $document, array $changeset) : void |
|
524 | |||
525 | /** |
||
526 | * Get a documents actual data, flattening all the objects to arrays. |
||
527 | * |
||
528 | * @return array |
||
529 | */ |
||
530 | 616 | public function getDocumentActualData(object $document) : array |
|
560 | |||
561 | /** |
||
562 | * Computes the changes that happened to a single document. |
||
563 | * |
||
564 | * Modifies/populates the following properties: |
||
565 | * |
||
566 | * {@link originalDocumentData} |
||
567 | * If the document is NEW or MANAGED but not yet fully persisted (only has an id) |
||
568 | * then it was not fetched from the database and therefore we have no original |
||
569 | * document data yet. All of the current document data is stored as the original document data. |
||
570 | * |
||
571 | * {@link documentChangeSets} |
||
572 | * The changes detected on all properties of the document are stored there. |
||
573 | * A change is a tuple array where the first entry is the old value and the second |
||
574 | * entry is the new value of the property. Changesets are used by persisters |
||
575 | * to INSERT/UPDATE the persistent document state. |
||
576 | * |
||
577 | * {@link documentUpdates} |
||
578 | * If the document is already fully MANAGED (has been fetched from the database before) |
||
579 | * and any changes to its properties are detected, then a reference to the document is stored |
||
580 | * there to mark it for an update. |
||
581 | */ |
||
582 | 612 | public function computeChangeSet(ClassMetadata $class, object $document) : void |
|
595 | |||
596 | /** |
||
597 | * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet |
||
598 | */ |
||
599 | 612 | private function computeOrRecomputeChangeSet(ClassMetadata $class, object $document, bool $recompute = false) : void |
|
789 | |||
790 | /** |
||
791 | * Computes all the changes that have been done to documents and collections |
||
792 | * since the last commit and stores these changes in the _documentChangeSet map |
||
793 | * temporarily for access by the persisters, until the UoW commit is finished. |
||
794 | */ |
||
795 | 616 | public function computeChangeSets() : void |
|
846 | |||
847 | /** |
||
848 | * Computes the changes of an association. |
||
849 | * |
||
850 | * @param mixed $value The value of the association. |
||
851 | * |
||
852 | * @throws InvalidArgumentException |
||
853 | */ |
||
854 | 452 | private function computeAssociationChanges(object $parentDocument, array $assoc, $value) : void |
|
855 | { |
||
856 | 452 | $isNewParentDocument = isset($this->documentInsertions[spl_object_hash($parentDocument)]); |
|
857 | 452 | $class = $this->dm->getClassMetadata(get_class($parentDocument)); |
|
858 | 452 | $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument); |
|
859 | |||
860 | 452 | if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) { |
|
861 | 7 | return; |
|
862 | } |
||
863 | |||
864 | 451 | if ($value instanceof PersistentCollectionInterface && $value->isDirty() && $value->getOwner() !== null && ($assoc['isOwningSide'] || isset($assoc['embedded']))) { |
|
865 | 258 | if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) { |
|
866 | 254 | $this->scheduleCollectionUpdate($value); |
|
867 | } |
||
868 | |||
869 | 258 | $topmostOwner = $this->getOwningDocument($value->getOwner()); |
|
870 | 258 | $this->visitedCollections[spl_object_hash($topmostOwner)][] = $value; |
|
871 | 258 | if (! empty($assoc['orphanRemoval']) || isset($assoc['embedded'])) { |
|
872 | 151 | $value->initialize(); |
|
873 | 151 | foreach ($value->getDeletedDocuments() as $orphan) { |
|
874 | 25 | $this->scheduleOrphanRemoval($orphan); |
|
875 | } |
||
876 | } |
||
877 | } |
||
878 | |||
879 | // Look through the documents, and in any of their associations, |
||
880 | // for transient (new) documents, recursively. ("Persistence by reachability") |
||
881 | // Unwrap. Uninitialized collections will simply be empty. |
||
882 | 451 | $unwrappedValue = $assoc['type'] === ClassMetadata::ONE ? [$value] : $value->unwrap(); |
|
883 | |||
884 | 451 | $count = 0; |
|
885 | 451 | foreach ($unwrappedValue as $key => $entry) { |
|
886 | 367 | if (! is_object($entry)) { |
|
887 | 1 | throw new InvalidArgumentException( |
|
888 | 1 | sprintf('Expected object, found "%s" in %s::%s', $entry, get_class($parentDocument), $assoc['name']) |
|
889 | ); |
||
890 | } |
||
891 | |||
892 | 366 | $targetClass = $this->dm->getClassMetadata(get_class($entry)); |
|
893 | |||
894 | 366 | $state = $this->getDocumentState($entry, self::STATE_NEW); |
|
895 | |||
896 | // Handle "set" strategy for multi-level hierarchy |
||
897 | 366 | $pathKey = ! isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key; |
|
898 | 366 | $path = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name']; |
|
899 | |||
900 | 366 | $count++; |
|
901 | |||
902 | switch ($state) { |
||
903 | 366 | case self::STATE_NEW: |
|
904 | 70 | if (! $assoc['isCascadePersist']) { |
|
905 | throw new InvalidArgumentException('A new document was found through a relationship that was not' |
||
906 | . ' configured to cascade persist operations: ' . $this->objToStr($entry) . '.' |
||
907 | . ' Explicitly persist the new document or configure cascading persist operations' |
||
908 | . ' on the relationship.'); |
||
909 | } |
||
910 | |||
911 | 70 | $this->persistNew($targetClass, $entry); |
|
912 | 70 | $this->setParentAssociation($entry, $assoc, $parentDocument, $path); |
|
913 | 70 | $this->computeChangeSet($targetClass, $entry); |
|
914 | 70 | break; |
|
915 | |||
916 | 362 | case self::STATE_MANAGED: |
|
917 | 362 | if ($targetClass->isEmbeddedDocument) { |
|
918 | 179 | [, $knownParent ] = $this->getParentAssociation($entry); |
|
919 | 179 | if ($knownParent && $knownParent !== $parentDocument) { |
|
920 | 6 | $entry = clone $entry; |
|
921 | 6 | if ($assoc['type'] === ClassMetadata::ONE) { |
|
922 | 3 | $class->setFieldValue($parentDocument, $assoc['fieldName'], $entry); |
|
923 | 3 | $this->setOriginalDocumentProperty(spl_object_hash($parentDocument), $assoc['fieldName'], $entry); |
|
924 | 3 | $poid = spl_object_hash($parentDocument); |
|
925 | 3 | if (isset($this->documentChangeSets[$poid][$assoc['fieldName']])) { |
|
926 | 3 | $this->documentChangeSets[$poid][$assoc['fieldName']][1] = $entry; |
|
927 | } |
||
928 | } else { |
||
929 | // must use unwrapped value to not trigger orphan removal |
||
930 | 4 | $unwrappedValue[$key] = $entry; |
|
931 | } |
||
932 | 6 | $this->persistNew($targetClass, $entry); |
|
933 | } |
||
934 | 179 | $this->setParentAssociation($entry, $assoc, $parentDocument, $path); |
|
935 | 179 | $this->computeChangeSet($targetClass, $entry); |
|
936 | } |
||
937 | 362 | break; |
|
938 | |||
939 | 1 | case self::STATE_REMOVED: |
|
940 | // Consume the $value as array (it's either an array or an ArrayAccess) |
||
941 | // and remove the element from Collection. |
||
942 | 1 | if ($assoc['type'] === ClassMetadata::MANY) { |
|
943 | unset($value[$key]); |
||
944 | } |
||
945 | 1 | break; |
|
946 | |||
947 | case self::STATE_DETACHED: |
||
948 | // Can actually not happen right now as we assume STATE_NEW, |
||
949 | // so the exception will be raised from the DBAL layer (constraint violation). |
||
950 | throw new InvalidArgumentException('A detached document was found through a ' |
||
951 | . 'relationship during cascading a persist operation.'); |
||
952 | |||
953 | default: |
||
954 | // MANAGED associated documents are already taken into account |
||
955 | // during changeset calculation anyway, since they are in the identity map. |
||
956 | } |
||
957 | } |
||
958 | 450 | } |
|
959 | |||
960 | /** |
||
961 | * INTERNAL: |
||
962 | * Computes the changeset of an individual document, independently of the |
||
963 | * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit(). |
||
964 | * |
||
965 | * The passed document must be a managed document. If the document already has a change set |
||
966 | * because this method is invoked during a commit cycle then the change sets are added. |
||
967 | * whereby changes detected in this method prevail. |
||
968 | * |
||
969 | * @throws InvalidArgumentException If the passed document is not MANAGED. |
||
970 | * |
||
971 | * @ignore |
||
972 | */ |
||
973 | 19 | public function recomputeSingleDocumentChangeSet(ClassMetadata $class, object $document) : void |
|
992 | |||
993 | /** |
||
994 | * @throws InvalidArgumentException If there is something wrong with document's identifier. |
||
995 | */ |
||
996 | 641 | private function persistNew(ClassMetadata $class, object $document) : void |
|
1039 | |||
1040 | /** |
||
1041 | * Executes all document insertions for documents of the specified type. |
||
1042 | */ |
||
1043 | 530 | private function executeInserts(ClassMetadata $class, array $documents, array $options = []) : void |
|
1058 | |||
1059 | /** |
||
1060 | * Executes all document upserts for documents of the specified type. |
||
1061 | */ |
||
1062 | 86 | private function executeUpserts(ClassMetadata $class, array $documents, array $options = []) : void |
|
1077 | |||
1078 | /** |
||
1079 | * Executes all document updates for documents of the specified type. |
||
1080 | */ |
||
1081 | 236 | private function executeUpdates(ClassMetadata $class, array $documents, array $options = []) : void |
|
1102 | |||
1103 | /** |
||
1104 | * Executes all document deletions for documents of the specified type. |
||
1105 | */ |
||
1106 | 79 | private function executeDeletions(ClassMetadata $class, array $documents, array $options = []) : void |
|
1142 | |||
1143 | /** |
||
1144 | * Schedules a document for insertion into the database. |
||
1145 | * If the document already has an identifier, it will be added to the |
||
1146 | * identity map. |
||
1147 | * |
||
1148 | * @throws InvalidArgumentException |
||
1149 | */ |
||
1150 | 572 | public function scheduleForInsert(ClassMetadata $class, object $document) : void |
|
1172 | |||
1173 | /** |
||
1174 | * Schedules a document for upsert into the database and adds it to the |
||
1175 | * identity map |
||
1176 | * |
||
1177 | * @throws InvalidArgumentException |
||
1178 | */ |
||
1179 | 92 | public function scheduleForUpsert(ClassMetadata $class, object $document) : void |
|
1200 | |||
1201 | /** |
||
1202 | * Checks whether a document is scheduled for insertion. |
||
1203 | */ |
||
1204 | 110 | public function isScheduledForInsert(object $document) : bool |
|
1208 | |||
1209 | /** |
||
1210 | * Checks whether a document is scheduled for upsert. |
||
1211 | */ |
||
1212 | 5 | public function isScheduledForUpsert(object $document) : bool |
|
1216 | |||
1217 | /** |
||
1218 | * Schedules a document for being updated. |
||
1219 | * |
||
1220 | * @throws InvalidArgumentException |
||
1221 | */ |
||
1222 | 245 | public function scheduleForUpdate(object $document) : void |
|
1241 | |||
1242 | /** |
||
1243 | * Checks whether a document is registered as dirty in the unit of work. |
||
1244 | * Note: Is not very useful currently as dirty documents are only registered |
||
1245 | * at commit time. |
||
1246 | */ |
||
1247 | 21 | public function isScheduledForUpdate(object $document) : bool |
|
1251 | |||
1252 | 1 | public function isScheduledForDirtyCheck(object $document) : bool |
|
1257 | |||
1258 | /** |
||
1259 | * INTERNAL: |
||
1260 | * Schedules a document for deletion. |
||
1261 | */ |
||
1262 | 84 | public function scheduleForDelete(object $document) : void |
|
1290 | |||
1291 | /** |
||
1292 | * Checks whether a document is registered as removed/deleted with the unit |
||
1293 | * of work. |
||
1294 | */ |
||
1295 | 5 | public function isScheduledForDelete(object $document) : bool |
|
1299 | |||
1300 | /** |
||
1301 | * Checks whether a document is scheduled for insertion, update or deletion. |
||
1302 | */ |
||
1303 | 257 | public function isDocumentScheduled(object $document) : bool |
|
1311 | |||
1312 | /** |
||
1313 | * INTERNAL: |
||
1314 | * Registers a document in the identity map. |
||
1315 | * |
||
1316 | * Note that documents in a hierarchy are registered with the class name of |
||
1317 | * the root document. Identifiers are serialized before being used as array |
||
1318 | * keys to allow differentiation of equal, but not identical, values. |
||
1319 | * |
||
1320 | * @ignore |
||
1321 | */ |
||
1322 | 680 | public function addToIdentityMap(object $document) : bool |
|
1340 | |||
1341 | /** |
||
1342 | * Gets the state of a document with regard to the current unit of work. |
||
1343 | * |
||
1344 | * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED). |
||
1345 | * This parameter can be set to improve performance of document state detection |
||
1346 | * by potentially avoiding a database lookup if the distinction between NEW and DETACHED |
||
1347 | * is either known or does not matter for the caller of the method. |
||
1348 | */ |
||
1349 | 645 | public function getDocumentState(object $document, ?int $assume = null) : int |
|
1399 | |||
1400 | /** |
||
1401 | * INTERNAL: |
||
1402 | * Removes a document from the identity map. This effectively detaches the |
||
1403 | * document from the persistence management of Doctrine. |
||
1404 | * |
||
1405 | * @throws InvalidArgumentException |
||
1406 | * |
||
1407 | * @ignore |
||
1408 | */ |
||
1409 | 96 | public function removeFromIdentityMap(object $document) : bool |
|
1429 | |||
1430 | /** |
||
1431 | * INTERNAL: |
||
1432 | * Gets a document in the identity map by its identifier hash. |
||
1433 | * |
||
1434 | * @param mixed $id Document identifier |
||
1435 | * |
||
1436 | * @throws InvalidArgumentException If the class does not have an identifier. |
||
1437 | * |
||
1438 | * @ignore |
||
1439 | */ |
||
1440 | 37 | public function getById($id, ClassMetadata $class) : object |
|
1450 | |||
1451 | /** |
||
1452 | * INTERNAL: |
||
1453 | * Tries to get a document by its identifier hash. If no document is found |
||
1454 | * for the given hash, FALSE is returned. |
||
1455 | * |
||
1456 | * @param mixed $id Document identifier |
||
1457 | * |
||
1458 | * @return mixed The found document or FALSE. |
||
1459 | * |
||
1460 | * @throws InvalidArgumentException If the class does not have an identifier. |
||
1461 | * |
||
1462 | * @ignore |
||
1463 | */ |
||
1464 | 306 | public function tryGetById($id, ClassMetadata $class) |
|
1474 | |||
1475 | /** |
||
1476 | * Schedules a document for dirty-checking at commit-time. |
||
1477 | * |
||
1478 | * @todo Rename: scheduleForSynchronization |
||
1479 | */ |
||
1480 | 3 | public function scheduleForDirtyCheck(object $document) : void |
|
1485 | |||
1486 | /** |
||
1487 | * Checks whether a document is registered in the identity map. |
||
1488 | */ |
||
1489 | 92 | public function isInIdentityMap(object $document) : bool |
|
1502 | |||
1503 | 680 | private function getIdForIdentityMap(object $document) : string |
|
1516 | |||
1517 | /** |
||
1518 | * INTERNAL: |
||
1519 | * Checks whether an identifier exists in the identity map. |
||
1520 | * |
||
1521 | * @ignore |
||
1522 | */ |
||
1523 | public function containsId($id, string $rootClassName) : bool |
||
1527 | |||
1528 | /** |
||
1529 | * Persists a document as part of the current unit of work. |
||
1530 | * |
||
1531 | * @throws MongoDBException If trying to persist MappedSuperclass. |
||
1532 | * @throws InvalidArgumentException If there is something wrong with document's identifier. |
||
1533 | */ |
||
1534 | 642 | public function persist(object $document) : void |
|
1543 | |||
1544 | /** |
||
1545 | * Saves a document as part of the current unit of work. |
||
1546 | * This method is internally called during save() cascades as it tracks |
||
1547 | * the already visited documents to prevent infinite recursions. |
||
1548 | * |
||
1549 | * NOTE: This method always considers documents that are not yet known to |
||
1550 | * this UnitOfWork as NEW. |
||
1551 | * |
||
1552 | * @throws InvalidArgumentException |
||
1553 | * @throws MongoDBException |
||
1554 | */ |
||
1555 | 641 | private function doPersist(object $document, array &$visited) : void |
|
1600 | |||
1601 | /** |
||
1602 | * Deletes a document as part of the current unit of work. |
||
1603 | */ |
||
1604 | 83 | public function remove(object $document) |
|
1609 | |||
1610 | /** |
||
1611 | * Deletes a document as part of the current unit of work. |
||
1612 | * |
||
1613 | * This method is internally called during delete() cascades as it tracks |
||
1614 | * the already visited documents to prevent infinite recursions. |
||
1615 | * |
||
1616 | * @throws MongoDBException |
||
1617 | */ |
||
1618 | 83 | private function doRemove(object $document, array &$visited) : void |
|
1650 | |||
1651 | /** |
||
1652 | * Merges the state of the given detached document into this UnitOfWork. |
||
1653 | */ |
||
1654 | 11 | public function merge(object $document) : object |
|
1660 | |||
1661 | /** |
||
1662 | * Executes a merge operation on a document. |
||
1663 | * |
||
1664 | * @throws InvalidArgumentException If the entity instance is NEW. |
||
1665 | * @throws LockException If the document uses optimistic locking through a |
||
1666 | * version attribute and the version check against the |
||
1667 | * managed copy fails. |
||
1668 | */ |
||
1669 | 11 | private function doMerge(object $document, array &$visited, ?object $prevManagedCopy = null, ?array $assoc = null) : object |
|
1845 | |||
1846 | /** |
||
1847 | * Detaches a document from the persistence management. It's persistence will |
||
1848 | * no longer be managed by Doctrine. |
||
1849 | */ |
||
1850 | 11 | public function detach(object $document) : void |
|
1855 | |||
1856 | /** |
||
1857 | * Executes a detach operation on the given document. |
||
1858 | * |
||
1859 | * @internal This method always considers documents with an assigned identifier as DETACHED. |
||
1860 | */ |
||
1861 | 17 | private function doDetach(object $document, array &$visited) : void |
|
1893 | |||
1894 | /** |
||
1895 | * Refreshes the state of the given document from the database, overwriting |
||
1896 | * any local, unpersisted changes. |
||
1897 | * |
||
1898 | * @throws InvalidArgumentException If the document is not MANAGED. |
||
1899 | */ |
||
1900 | 24 | public function refresh(object $document) : void |
|
1905 | |||
1906 | /** |
||
1907 | * Executes a refresh operation on a document. |
||
1908 | * |
||
1909 | * @throws InvalidArgumentException If the document is not MANAGED. |
||
1910 | */ |
||
1911 | 24 | private function doRefresh(object $document, array &$visited) : void |
|
1932 | |||
1933 | /** |
||
1934 | * Cascades a refresh operation to associated documents. |
||
1935 | */ |
||
1936 | 23 | private function cascadeRefresh(object $document, array &$visited) : void |
|
1937 | { |
||
1938 | 23 | $class = $this->dm->getClassMetadata(get_class($document)); |
|
1939 | |||
1940 | 23 | $associationMappings = array_filter( |
|
1941 | 23 | $class->associationMappings, |
|
1942 | static function ($assoc) { |
||
1943 | 18 | return $assoc['isCascadeRefresh']; |
|
1944 | 23 | } |
|
1945 | ); |
||
1946 | |||
1947 | 23 | foreach ($associationMappings as $mapping) { |
|
1948 | 15 | $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document); |
|
1949 | 15 | if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) { |
|
1950 | 15 | if ($relatedDocuments instanceof PersistentCollectionInterface) { |
|
1951 | // Unwrap so that foreach() does not initialize |
||
1952 | 15 | $relatedDocuments = $relatedDocuments->unwrap(); |
|
1953 | } |
||
1954 | 15 | foreach ($relatedDocuments as $relatedDocument) { |
|
1955 | $this->doRefresh($relatedDocument, $visited); |
||
1956 | } |
||
1957 | 10 | } elseif ($relatedDocuments !== null) { |
|
1958 | 2 | $this->doRefresh($relatedDocuments, $visited); |
|
1959 | } |
||
1960 | } |
||
1961 | 23 | } |
|
1962 | |||
1963 | /** |
||
1964 | * Cascades a detach operation to associated documents. |
||
1965 | */ |
||
1966 | 17 | private function cascadeDetach(object $document, array &$visited) : void |
|
1987 | /** |
||
1988 | * Cascades a merge operation to associated documents. |
||
1989 | */ |
||
1990 | 11 | private function cascadeMerge(object $document, object $managedCopy, array &$visited) : void |
|
2018 | |||
2019 | /** |
||
2020 | * Cascades the save operation to associated documents. |
||
2021 | */ |
||
2022 | 638 | private function cascadePersist(object $document, array &$visited) : void |
|
2071 | |||
2072 | /** |
||
2073 | * Cascades the delete operation to associated documents. |
||
2074 | */ |
||
2075 | 83 | private function cascadeRemove(object $document, array &$visited) : void |
|
2097 | |||
2098 | /** |
||
2099 | * Acquire a lock on the given document. |
||
2100 | * |
||
2101 | * @throws LockException |
||
2102 | * @throws InvalidArgumentException |
||
2103 | */ |
||
2104 | 8 | public function lock(object $document, int $lockMode, ?int $lockVersion = null) : void |
|
2128 | |||
2129 | /** |
||
2130 | * Releases a lock on the given document. |
||
2131 | * |
||
2132 | * @throws InvalidArgumentException |
||
2133 | */ |
||
2134 | 1 | public function unlock(object $document) : void |
|
2142 | |||
2143 | /** |
||
2144 | * Clears the UnitOfWork. |
||
2145 | */ |
||
2146 | 378 | public function clear(?string $documentName = null) : void |
|
2184 | |||
2185 | /** |
||
2186 | * INTERNAL: |
||
2187 | * Schedules an embedded document for removal. The remove() operation will be |
||
2188 | * invoked on that document at the beginning of the next commit of this |
||
2189 | * UnitOfWork. |
||
2190 | * |
||
2191 | * @ignore |
||
2192 | */ |
||
2193 | 58 | public function scheduleOrphanRemoval(object $document) : void |
|
2197 | |||
2198 | /** |
||
2199 | * INTERNAL: |
||
2200 | * Unschedules an embedded or referenced object for removal. |
||
2201 | * |
||
2202 | * @ignore |
||
2203 | */ |
||
2204 | 123 | public function unscheduleOrphanRemoval(object $document) : void |
|
2209 | |||
2210 | /** |
||
2211 | * Fixes PersistentCollection state if it wasn't used exactly as we had in mind: |
||
2212 | * 1) sets owner if it was cloned |
||
2213 | * 2) clones collection, sets owner, updates document's property and, if necessary, updates originalData |
||
2214 | * 3) NOP if state is OK |
||
2215 | * Returned collection should be used from now on (only important with 2nd point) |
||
2216 | */ |
||
2217 | 8 | private function fixPersistentCollectionOwnership(PersistentCollectionInterface $coll, object $document, ClassMetadata $class, string $propName) : PersistentCollectionInterface |
|
2237 | |||
2238 | /** |
||
2239 | * INTERNAL: |
||
2240 | * Schedules a complete collection for removal when this UnitOfWork commits. |
||
2241 | */ |
||
2242 | 47 | public function scheduleCollectionDeletion(PersistentCollectionInterface $coll) : void |
|
2253 | |||
2254 | /** |
||
2255 | * Checks whether a PersistentCollection is scheduled for deletion. |
||
2256 | */ |
||
2257 | 220 | public function isCollectionScheduledForDeletion(PersistentCollectionInterface $coll) : bool |
|
2261 | |||
2262 | /** |
||
2263 | * INTERNAL: |
||
2264 | * Unschedules a collection from being deleted when this UnitOfWork commits. |
||
2265 | */ |
||
2266 | 227 | public function unscheduleCollectionDeletion(PersistentCollectionInterface $coll) : void |
|
2281 | |||
2282 | /** |
||
2283 | * INTERNAL: |
||
2284 | * Schedules a collection for update when this UnitOfWork commits. |
||
2285 | */ |
||
2286 | 254 | public function scheduleCollectionUpdate(PersistentCollectionInterface $coll) : void |
|
2303 | |||
2304 | /** |
||
2305 | * INTERNAL: |
||
2306 | * Unschedules a collection from being updated when this UnitOfWork commits. |
||
2307 | */ |
||
2308 | 227 | public function unscheduleCollectionUpdate(PersistentCollectionInterface $coll) : void |
|
2323 | |||
2324 | /** |
||
2325 | * Checks whether a PersistentCollection is scheduled for update. |
||
2326 | */ |
||
2327 | 140 | public function isCollectionScheduledForUpdate(PersistentCollectionInterface $coll) : bool |
|
2331 | |||
2332 | /** |
||
2333 | * INTERNAL: |
||
2334 | * Gets PersistentCollections that have been visited during computing change |
||
2335 | * set of $document |
||
2336 | * |
||
2337 | * @return PersistentCollectionInterface[] |
||
2338 | */ |
||
2339 | 581 | public function getVisitedCollections(object $document) : array |
|
2345 | |||
2346 | /** |
||
2347 | * INTERNAL: |
||
2348 | * Gets PersistentCollections that are scheduled to update and related to $document |
||
2349 | * |
||
2350 | * @return PersistentCollectionInterface[] |
||
2351 | */ |
||
2352 | 581 | public function getScheduledCollections(object $document) : array |
|
2358 | |||
2359 | /** |
||
2360 | * Checks whether the document is related to a PersistentCollection |
||
2361 | * scheduled for update or deletion. |
||
2362 | */ |
||
2363 | 56 | public function hasScheduledCollections(object $document) : bool |
|
2367 | |||
2368 | /** |
||
2369 | * Marks the PersistentCollection's top-level owner as having a relation to |
||
2370 | * a collection scheduled for update or deletion. |
||
2371 | * |
||
2372 | * If the owner is not scheduled for any lifecycle action, it will be |
||
2373 | * scheduled for update to ensure that versioning takes place if necessary. |
||
2374 | * |
||
2375 | * If the collection is nested within atomic collection, it is immediately |
||
2376 | * unscheduled and atomic one is scheduled for update instead. This makes |
||
2377 | * calculating update data way easier. |
||
2378 | */ |
||
2379 | 256 | private function scheduleCollectionOwner(PersistentCollectionInterface $coll) : void |
|
2409 | |||
2410 | /** |
||
2411 | * Get the top-most owning document of a given document |
||
2412 | * |
||
2413 | * If a top-level document is provided, that same document will be returned. |
||
2414 | * For an embedded document, we will walk through parent associations until |
||
2415 | * we find a top-level document. |
||
2416 | * |
||
2417 | * @throws UnexpectedValueException When a top-level document could not be found. |
||
2418 | */ |
||
2419 | 258 | public function getOwningDocument(object $document) : object |
|
2435 | |||
2436 | /** |
||
2437 | * Gets the class name for an association (embed or reference) with respect |
||
2438 | * to any discriminator value. |
||
2439 | * |
||
2440 | * @param array|null $data |
||
2441 | */ |
||
2442 | 227 | public function getClassNameForAssociation(array $mapping, $data) : string |
|
2472 | |||
2473 | /** |
||
2474 | * INTERNAL: |
||
2475 | * Creates a document. Used for reconstitution of documents during hydration. |
||
2476 | * |
||
2477 | * @internal Highly performance-sensitive method. |
||
2478 | * |
||
2479 | * @ignore |
||
2480 | */ |
||
2481 | 402 | public function getOrCreateDocument(string $className, array $data, array &$hints = [], ?object $document = null) : object |
|
2554 | |||
2555 | /** |
||
2556 | * Initializes (loads) an uninitialized persistent collection of a document. |
||
2557 | */ |
||
2558 | 178 | public function loadCollection(PersistentCollectionInterface $collection) : void |
|
2567 | |||
2568 | /** |
||
2569 | * Gets the identity map of the UnitOfWork. |
||
2570 | */ |
||
2571 | public function getIdentityMap() : array |
||
2575 | |||
2576 | /** |
||
2577 | * Gets the original data of a document. The original data is the data that was |
||
2578 | * present at the time the document was reconstituted from the database. |
||
2579 | * |
||
2580 | * @return array |
||
2581 | */ |
||
2582 | 1 | public function getOriginalDocumentData(object $document) : array |
|
2588 | |||
2589 | 60 | public function setOriginalDocumentData(object $document, array $data) : void |
|
2595 | |||
2596 | /** |
||
2597 | * INTERNAL: |
||
2598 | * Sets a property value of the original data array of a document. |
||
2599 | * |
||
2600 | * @param mixed $value |
||
2601 | * |
||
2602 | * @ignore |
||
2603 | */ |
||
2604 | 3 | public function setOriginalDocumentProperty(string $oid, string $property, $value) : void |
|
2608 | |||
2609 | /** |
||
2610 | * Gets the identifier of a document. |
||
2611 | * |
||
2612 | * @return mixed The identifier value |
||
2613 | */ |
||
2614 | 452 | public function getDocumentIdentifier(object $document) |
|
2618 | |||
2619 | /** |
||
2620 | * Checks whether the UnitOfWork has any pending insertions. |
||
2621 | * |
||
2622 | * @return bool TRUE if this UnitOfWork has pending insertions, FALSE otherwise. |
||
2623 | */ |
||
2624 | public function hasPendingInsertions() : bool |
||
2628 | |||
2629 | /** |
||
2630 | * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the |
||
2631 | * number of documents in the identity map. |
||
2632 | */ |
||
2633 | 2 | public function size() : int |
|
2641 | |||
2642 | /** |
||
2643 | * INTERNAL: |
||
2644 | * Registers a document as managed. |
||
2645 | * |
||
2646 | * TODO: This method assumes that $id is a valid PHP identifier for the |
||
2647 | * document class. If the class expects its database identifier to be an |
||
2648 | * ObjectId, and an incompatible $id is registered (e.g. an integer), the |
||
2649 | * document identifiers map will become inconsistent with the identity map. |
||
2650 | * In the future, we may want to round-trip $id through a PHP and database |
||
2651 | * conversion and throw an exception if it's inconsistent. |
||
2652 | * |
||
2653 | * @param mixed $id The identifier values. |
||
2654 | */ |
||
2655 | 381 | public function registerManaged(object $document, $id, array $data) : void |
|
2670 | |||
2671 | /** |
||
2672 | * INTERNAL: |
||
2673 | * Clears the property changeset of the document with the given OID. |
||
2674 | */ |
||
2675 | public function clearDocumentChangeSet(string $oid) |
||
2679 | |||
2680 | /* PropertyChangedListener implementation */ |
||
2681 | |||
2682 | /** |
||
2683 | * Notifies this UnitOfWork of a property change in a document. |
||
2684 | * |
||
2685 | * @param object $document The document that owns the property. |
||
2686 | * @param string $propertyName The name of the property that changed. |
||
2687 | * @param mixed $oldValue The old value of the property. |
||
2688 | * @param mixed $newValue The new value of the property. |
||
2689 | */ |
||
2690 | 2 | public function propertyChanged($document, $propertyName, $oldValue, $newValue) |
|
2707 | |||
2708 | /** |
||
2709 | * Gets the currently scheduled document insertions in this UnitOfWork. |
||
2710 | */ |
||
2711 | 3 | public function getScheduledDocumentInsertions() : array |
|
2715 | |||
2716 | /** |
||
2717 | * Gets the currently scheduled document upserts in this UnitOfWork. |
||
2718 | */ |
||
2719 | 1 | public function getScheduledDocumentUpserts() : array |
|
2723 | |||
2724 | /** |
||
2725 | * Gets the currently scheduled document updates in this UnitOfWork. |
||
2726 | */ |
||
2727 | 2 | public function getScheduledDocumentUpdates() : array |
|
2731 | |||
2732 | /** |
||
2733 | * Gets the currently scheduled document deletions in this UnitOfWork. |
||
2734 | */ |
||
2735 | public function getScheduledDocumentDeletions() : array |
||
2739 | |||
2740 | /** |
||
2741 | * Get the currently scheduled complete collection deletions |
||
2742 | */ |
||
2743 | public function getScheduledCollectionDeletions() : array |
||
2747 | |||
2748 | /** |
||
2749 | * Gets the currently scheduled collection inserts, updates and deletes. |
||
2750 | */ |
||
2751 | public function getScheduledCollectionUpdates() : array |
||
2755 | |||
2756 | /** |
||
2757 | * Helper method to initialize a lazy loading proxy or persistent collection. |
||
2758 | */ |
||
2759 | public function initializeObject(object $obj) : void |
||
2767 | |||
2768 | private function objToStr(object $obj) : string |
||
2772 | } |
||
2773 |
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.