Complex classes like PersistentCollectionTrait 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 PersistentCollectionTrait, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | trait PersistentCollectionTrait |
||
25 | { |
||
26 | /** |
||
27 | * A snapshot of the collection at the moment it was fetched from the database. |
||
28 | * This is used to create a diff of the collection at commit time. |
||
29 | * |
||
30 | * @var array |
||
31 | */ |
||
32 | private $snapshot = []; |
||
33 | |||
34 | /** |
||
35 | * Collection's owning entity |
||
36 | * |
||
37 | * @var object|null |
||
38 | */ |
||
39 | private $owner; |
||
40 | |||
41 | /** @var array */ |
||
42 | private $mapping; |
||
43 | |||
44 | /** |
||
45 | * Whether the collection is dirty and needs to be synchronized with the database |
||
46 | * when the UnitOfWork that manages its persistent state commits. |
||
47 | * |
||
48 | * @var bool |
||
49 | */ |
||
50 | private $isDirty = false; |
||
51 | |||
52 | /** |
||
53 | * Whether the collection has already been initialized. |
||
54 | * |
||
55 | * @var bool |
||
56 | */ |
||
57 | private $initialized = true; |
||
58 | |||
59 | /** |
||
60 | * The wrapped Collection instance. |
||
61 | * |
||
62 | * @var BaseCollection |
||
63 | */ |
||
64 | private $coll; |
||
65 | |||
66 | /** |
||
67 | * The DocumentManager that manages the persistence of the collection. |
||
68 | * |
||
69 | * @var DocumentManager|null |
||
70 | */ |
||
71 | private $dm; |
||
72 | |||
73 | /** |
||
74 | * The UnitOfWork that manages the persistence of the collection. |
||
75 | * |
||
76 | * @var UnitOfWork |
||
77 | */ |
||
78 | private $uow; |
||
79 | |||
80 | /** |
||
81 | * The raw mongo data that will be used to initialize this collection. |
||
82 | * |
||
83 | * @var array |
||
84 | */ |
||
85 | private $mongoData = []; |
||
86 | |||
87 | /** |
||
88 | * Any hints to account for during reconstitution/lookup of the documents. |
||
89 | * |
||
90 | * @var array |
||
91 | */ |
||
92 | private $hints = []; |
||
93 | |||
94 | /** {@inheritdoc} */ |
||
95 | 4 | public function setDocumentManager(DocumentManager $dm) |
|
100 | |||
101 | /** {@inheritdoc} */ |
||
102 | 166 | public function setMongoData(array $mongoData) |
|
106 | |||
107 | /** {@inheritdoc} */ |
||
108 | 164 | public function getMongoData() |
|
112 | |||
113 | /** {@inheritdoc} */ |
||
114 | 247 | public function setHints(array $hints) |
|
118 | |||
119 | /** {@inheritdoc} */ |
||
120 | 164 | public function getHints() |
|
124 | |||
125 | /** {@inheritdoc} */ |
||
126 | 401 | public function initialize() |
|
162 | |||
163 | /** |
||
164 | * Marks this collection as changed/dirty. |
||
165 | */ |
||
166 | 211 | private function changed() |
|
167 | { |
||
168 | 211 | if ($this->isDirty) { |
|
169 | 133 | return; |
|
170 | } |
||
171 | |||
172 | 211 | $this->isDirty = true; |
|
173 | |||
174 | 211 | if (! $this->needsSchedulingForSynchronization() || $this->owner === null) { |
|
175 | 209 | return; |
|
176 | } |
||
177 | |||
178 | 2 | $this->uow->scheduleForSynchronization($this->owner); |
|
179 | 2 | } |
|
180 | |||
181 | /** {@inheritdoc} */ |
||
182 | 412 | public function isDirty() |
|
197 | |||
198 | /** {@inheritdoc} */ |
||
199 | 400 | public function setDirty($dirty) |
|
203 | |||
204 | /** {@inheritdoc} */ |
||
205 | 423 | public function setOwner(object $document, array $mapping) |
|
210 | |||
211 | /** {@inheritdoc} */ |
||
212 | 296 | public function takeSnapshot() |
|
224 | |||
225 | /** {@inheritdoc} */ |
||
226 | 29 | public function clearSnapshot() |
|
231 | |||
232 | /** {@inheritdoc} */ |
||
233 | 1 | public function getSnapshot() |
|
234 | { |
||
235 | 1 | return $this->snapshot; |
|
236 | } |
||
237 | |||
238 | /** {@inheritdoc} */ |
||
239 | 95 | public function getDeleteDiff() |
|
240 | { |
||
241 | 95 | return array_udiff_assoc( |
|
242 | 95 | $this->snapshot, |
|
243 | 95 | $this->coll->toArray(), |
|
244 | static function ($a, $b) { |
||
245 | 48 | return $a === $b ? 0 : 1; |
|
246 | 95 | } |
|
247 | ); |
||
248 | } |
||
249 | |||
250 | /** {@inheritdoc} */ |
||
251 | 157 | public function getDeletedDocuments() |
|
252 | { |
||
253 | $compare = static function ($a, $b) { |
||
254 | 109 | $compareA = is_object($a) ? spl_object_hash($a) : $a; |
|
255 | 109 | $compareb = is_object($b) ? spl_object_hash($b) : $b; |
|
256 | 109 | return $compareA === $compareb ? 0 : ($compareA > $compareb ? 1 : -1); |
|
257 | 157 | }; |
|
258 | 157 | return array_values(array_udiff( |
|
259 | 157 | $this->snapshot, |
|
260 | 157 | $this->coll->toArray(), |
|
261 | 157 | $compare |
|
262 | )); |
||
263 | } |
||
264 | |||
265 | /** {@inheritdoc} */ |
||
266 | 95 | public function getInsertDiff() |
|
267 | { |
||
268 | 95 | return array_udiff_assoc( |
|
269 | 95 | $this->coll->toArray(), |
|
270 | 95 | $this->snapshot, |
|
271 | static function ($a, $b) { |
||
272 | 48 | return $a === $b ? 0 : 1; |
|
273 | 95 | } |
|
274 | ); |
||
275 | } |
||
276 | |||
277 | /** {@inheritdoc} */ |
||
278 | 4 | public function getInsertedDocuments() |
|
279 | { |
||
280 | $compare = static function ($a, $b) { |
||
281 | 4 | $compareA = is_object($a) ? spl_object_hash($a) : $a; |
|
282 | 4 | $compareb = is_object($b) ? spl_object_hash($b) : $b; |
|
283 | 4 | return $compareA === $compareb ? 0 : ($compareA > $compareb ? 1 : -1); |
|
284 | 4 | }; |
|
285 | 4 | return array_values(array_udiff( |
|
286 | 4 | $this->coll->toArray(), |
|
287 | 4 | $this->snapshot, |
|
288 | 4 | $compare |
|
289 | )); |
||
290 | } |
||
291 | |||
292 | /** {@inheritdoc} */ |
||
293 | 410 | public function getOwner() : ?object |
|
297 | |||
298 | /** {@inheritdoc} */ |
||
299 | 291 | public function getMapping() |
|
303 | |||
304 | /** {@inheritdoc} */ |
||
305 | 5 | public function getTypeClass() |
|
306 | { |
||
307 | 5 | if ($this->dm === null) { |
|
308 | 1 | throw new MongoDBException('No DocumentManager is associated with this PersistentCollection, please set one using setDocumentManager method.'); |
|
321 | |||
322 | /** {@inheritdoc} */ |
||
323 | 250 | public function setInitialized($bool) |
|
327 | |||
328 | /** {@inheritdoc} */ |
||
329 | 18 | public function isInitialized() |
|
333 | |||
334 | /** {@inheritdoc} */ |
||
335 | 16 | public function first() |
|
340 | |||
341 | /** {@inheritdoc} */ |
||
342 | 2 | public function last() |
|
347 | |||
348 | /** |
||
349 | * {@inheritdoc} |
||
350 | */ |
||
351 | 6 | public function remove($key) |
|
355 | |||
356 | /** |
||
357 | * {@inheritdoc} |
||
358 | */ |
||
359 | 17 | public function removeElement($element) |
|
372 | |||
373 | /** |
||
374 | * {@inheritdoc} |
||
375 | */ |
||
376 | public function containsKey($key) |
||
381 | |||
382 | /** |
||
383 | * {@inheritdoc} |
||
384 | */ |
||
385 | 3 | public function contains($element) |
|
390 | |||
391 | /** |
||
392 | * {@inheritdoc} |
||
393 | */ |
||
394 | public function exists(Closure $p) |
||
399 | |||
400 | /** |
||
401 | * {@inheritdoc} |
||
402 | */ |
||
403 | 2 | public function indexOf($element) |
|
408 | |||
409 | /** |
||
410 | * {@inheritdoc} |
||
411 | */ |
||
412 | 20 | public function get($key) |
|
417 | |||
418 | /** |
||
419 | * {@inheritdoc} |
||
420 | */ |
||
421 | public function getKeys() |
||
426 | |||
427 | /** |
||
428 | * {@inheritdoc} |
||
429 | */ |
||
430 | public function getValues() |
||
435 | |||
436 | /** |
||
437 | * {@inheritdoc} |
||
438 | */ |
||
439 | 116 | public function count() |
|
446 | |||
447 | /** |
||
448 | * {@inheritdoc} |
||
449 | */ |
||
450 | 35 | public function set($key, $value) |
|
454 | |||
455 | /** |
||
456 | * {@inheritdoc} |
||
457 | */ |
||
458 | 170 | public function add($value) |
|
462 | |||
463 | /** |
||
464 | * {@inheritdoc} |
||
465 | */ |
||
466 | 382 | public function isEmpty() |
|
470 | |||
471 | /** |
||
472 | * {@inheritdoc} |
||
473 | */ |
||
474 | 325 | public function getIterator() |
|
479 | |||
480 | /** |
||
481 | * {@inheritdoc} |
||
482 | */ |
||
483 | 232 | public function map(Closure $func) |
|
488 | |||
489 | /** |
||
490 | * {@inheritdoc} |
||
491 | */ |
||
492 | public function filter(Closure $p) |
||
497 | |||
498 | /** |
||
499 | * {@inheritdoc} |
||
500 | */ |
||
501 | public function forAll(Closure $p) |
||
506 | |||
507 | /** |
||
508 | * {@inheritdoc} |
||
509 | */ |
||
510 | public function partition(Closure $p) |
||
515 | |||
516 | /** |
||
517 | * {@inheritdoc} |
||
518 | */ |
||
519 | 23 | public function toArray() |
|
524 | |||
525 | /** |
||
526 | * {@inheritdoc} |
||
527 | */ |
||
528 | 33 | public function clear() |
|
558 | |||
559 | /** |
||
560 | * {@inheritdoc} |
||
561 | */ |
||
562 | 1 | public function slice($offset, $length = null) |
|
567 | |||
568 | /** |
||
569 | * Called by PHP when this collection is serialized. Ensures that the |
||
570 | * internal state of the collection can be reproduced after serialization |
||
571 | * |
||
572 | * @internal Tried to implement Serializable first but that did not work well |
||
573 | * with circular references. This solution seems simpler and works well. |
||
574 | */ |
||
575 | 6 | public function __sleep() |
|
579 | |||
580 | /* ArrayAccess implementation */ |
||
581 | |||
582 | /** |
||
583 | * @see containsKey() |
||
584 | */ |
||
585 | 2 | public function offsetExists($offset) |
|
590 | |||
591 | /** |
||
592 | * @see get() |
||
593 | */ |
||
594 | 77 | public function offsetGet($offset) |
|
599 | |||
600 | /** |
||
601 | * @see add() |
||
602 | * @see set() |
||
603 | */ |
||
604 | 42 | public function offsetSet($offset, $value) |
|
612 | |||
613 | /** |
||
614 | * @see remove() |
||
615 | */ |
||
616 | 19 | public function offsetUnset($offset) |
|
620 | |||
621 | public function key() |
||
625 | |||
626 | /** |
||
627 | * Gets the element of the collection at the current iterator position. |
||
628 | */ |
||
629 | 1 | public function current() |
|
633 | |||
634 | /** |
||
635 | * Moves the internal iterator position to the next element. |
||
636 | */ |
||
637 | public function next() |
||
641 | |||
642 | /** |
||
643 | * {@inheritdoc} |
||
644 | */ |
||
645 | 411 | public function unwrap() |
|
649 | |||
650 | /** |
||
651 | * Cleanup internal state of cloned persistent collection. |
||
652 | * |
||
653 | * The following problems have to be prevented: |
||
654 | * 1. Added documents are added to old PersistentCollection |
||
655 | * 2. New collection is not dirty, if reused on other document nothing |
||
656 | * changes. |
||
657 | * 3. Snapshot leads to invalid diffs being generated. |
||
658 | * 4. Lazy loading grabs entities from old owner object. |
||
659 | * 5. New collection is connected to old owner and leads to duplicate keys. |
||
660 | */ |
||
661 | 8 | public function __clone() |
|
674 | |||
675 | /** |
||
676 | * Actual logic for adding an element to the collection. |
||
677 | * |
||
678 | * @param mixed $value |
||
679 | * @param bool $arrayAccess |
||
680 | * |
||
681 | * @return bool |
||
682 | */ |
||
683 | 186 | private function doAdd($value, $arrayAccess) |
|
701 | |||
702 | /** |
||
703 | * Actual logic for removing element by its key. |
||
704 | * |
||
705 | * @param mixed $offset |
||
706 | * @param bool $arrayAccess |
||
707 | * |
||
708 | * @return bool |
||
709 | */ |
||
710 | 25 | private function doRemove($offset, $arrayAccess) |
|
728 | |||
729 | /** |
||
730 | * Actual logic for setting an element in the collection. |
||
731 | * |
||
732 | * @param mixed $offset |
||
733 | * @param mixed $value |
||
734 | * @param bool $arrayAccess |
||
735 | */ |
||
736 | 36 | private function doSet($offset, $value, $arrayAccess) |
|
747 | |||
748 | /** |
||
749 | * Returns whether or not this collection has orphan removal enabled. |
||
750 | * |
||
751 | * Embedded documents are automatically considered as "orphan removal enabled" because they might have references |
||
752 | * that require to trigger cascade remove operations. |
||
753 | * |
||
754 | * @return bool |
||
755 | */ |
||
756 | 204 | private function isOrphanRemovalEnabled() |
|
772 | |||
773 | /** |
||
774 | * Checks whether collection owner needs to be scheduled for dirty change in case the collection is modified. |
||
775 | * |
||
776 | * @return bool |
||
777 | */ |
||
778 | 211 | private function needsSchedulingForSynchronization() |
|
783 | } |
||
784 |