Complex classes like DoctrineObject 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 DoctrineObject, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class DoctrineObject extends AbstractHydrator |
||
28 | { |
||
29 | /** |
||
30 | * @var ObjectManager |
||
31 | */ |
||
32 | protected $objectManager; |
||
33 | |||
34 | /** |
||
35 | * @var ClassMetadata |
||
36 | */ |
||
37 | protected $metadata; |
||
38 | |||
39 | /** |
||
40 | * @var bool |
||
41 | */ |
||
42 | protected $byValue = true; |
||
43 | |||
44 | /** |
||
45 | * @var string |
||
46 | */ |
||
47 | protected $defaultByValueStrategy = __NAMESPACE__ . '\Strategy\AllowRemoveByValue'; |
||
48 | |||
49 | /** |
||
50 | * @var string |
||
51 | */ |
||
52 | protected $defaultByReferenceStrategy = __NAMESPACE__ . '\Strategy\AllowRemoveByReference'; |
||
53 | |||
54 | |||
55 | /** |
||
56 | * Constructor |
||
57 | * |
||
58 | * @param ObjectManager $objectManager The ObjectManager to use |
||
59 | * @param bool $byValue If set to true, hydrator will always use entity's public API |
||
60 | */ |
||
61 | 73 | public function __construct(ObjectManager $objectManager, $byValue = true) |
|
68 | |||
69 | /** |
||
70 | * @return string |
||
71 | */ |
||
72 | 14 | public function getDefaultByValueStrategy() |
|
76 | |||
77 | /** |
||
78 | * @param string $defaultByValueStrategy |
||
79 | * @return DoctrineObject |
||
80 | */ |
||
81 | 1 | public function setDefaultByValueStrategy($defaultByValueStrategy) |
|
86 | |||
87 | /** |
||
88 | * @return string |
||
89 | */ |
||
90 | 9 | public function getDefaultByReferenceStrategy() |
|
94 | |||
95 | /** |
||
96 | * @param string $defaultByReferenceStrategy |
||
97 | * @return DoctrineObject |
||
98 | */ |
||
99 | 1 | public function setDefaultByReferenceStrategy($defaultByReferenceStrategy) |
|
104 | |||
105 | /** |
||
106 | * Extract values from an object |
||
107 | * |
||
108 | * @param object $object |
||
109 | * @return array |
||
110 | */ |
||
111 | 17 | public function extract($object) |
|
121 | |||
122 | /** |
||
123 | * Hydrate $object with the provided $data. |
||
124 | * |
||
125 | * @param array $data |
||
126 | * @param object $object |
||
127 | * @return object |
||
128 | */ |
||
129 | 57 | public function hydrate(array $data, $object) |
|
139 | |||
140 | /** |
||
141 | * Prepare the hydrator by adding strategies to every collection valued associations |
||
142 | * |
||
143 | * @param object $object |
||
144 | * @return void |
||
145 | */ |
||
146 | 73 | protected function prepare($object) |
|
151 | |||
152 | /** |
||
153 | * Prepare strategies before the hydrator is used |
||
154 | * |
||
155 | * @throws \InvalidArgumentException |
||
156 | * @return void |
||
157 | */ |
||
158 | 73 | protected function prepareStrategies() |
|
192 | |||
193 | /** |
||
194 | * Extract values from an object using a by-value logic (this means that it uses the entity |
||
195 | * API, in this case, getters) |
||
196 | * |
||
197 | * @param object $object |
||
198 | * @throws RuntimeException |
||
199 | * @return array |
||
200 | */ |
||
201 | 10 | protected function extractByValue($object) |
|
234 | |||
235 | /** |
||
236 | * Extract values from an object using a by-reference logic (this means that values are |
||
237 | * directly fetched without using the public API of the entity, in this case, getters) |
||
238 | * |
||
239 | * @param object $object |
||
240 | * @return array |
||
241 | */ |
||
242 | 7 | protected function extractByReference($object) |
|
264 | |||
265 | /** |
||
266 | * Converts a value for hydration |
||
267 | * Apply strategies first, then the type conversions |
||
268 | * |
||
269 | * @inheritdoc |
||
270 | */ |
||
271 | 54 | public function hydrateValue($name, $value, $data = null) |
|
272 | { |
||
273 | 54 | $value = parent::hydrateValue($name, $value, $data); |
|
274 | |||
275 | 54 | if (is_null($value) && $this->isNullable($name)) { |
|
276 | return null; |
||
277 | } |
||
278 | |||
279 | 54 | return $this->handleTypeConversions($value, $this->metadata->getTypeOfField($name)); |
|
280 | } |
||
281 | |||
282 | /** |
||
283 | * Hydrate the object using a by-value logic (this means that it uses the entity API, in this |
||
284 | * case, setters) |
||
285 | * |
||
286 | * @param array $data |
||
287 | * @param object $object |
||
288 | * @throws RuntimeException |
||
289 | * @return object |
||
290 | */ |
||
291 | 44 | protected function hydrateByValue(array $data, $object) |
|
335 | |||
336 | /** |
||
337 | * Hydrate the object using a by-reference logic (this means that values are modified directly without |
||
338 | * using the public API, in this case setters, and hence override any logic that could be done in those |
||
339 | * setters) |
||
340 | * |
||
341 | * @param array $data |
||
342 | * @param object $object |
||
343 | * @return object |
||
344 | */ |
||
345 | 30 | protected function hydrateByReference(array $data, $object) |
|
382 | |||
383 | /** |
||
384 | * This function tries, given an array of data, to convert it to an object if the given array contains |
||
385 | * an identifier for the object. This is useful in a context of updating existing entities, without ugly |
||
386 | * tricks like setting manually the existing id directly into the entity |
||
387 | * |
||
388 | * @param array $data The data that may contain identifiers keys |
||
389 | * @param object $object |
||
390 | * @return object |
||
391 | */ |
||
392 | 57 | protected function tryConvertArrayToObject($data, $object) |
|
412 | |||
413 | /** |
||
414 | * Handle ToOne associations |
||
415 | * |
||
416 | * When $value is an array but is not the $target's identifiers, $value is |
||
417 | * most likely an array of fieldset data. The identifiers will be determined |
||
418 | * and a target instance will be initialized and then hydrated. The hydrated |
||
419 | * target will be returned. |
||
420 | * |
||
421 | * @param string $target |
||
422 | * @param mixed $value |
||
423 | * @return object |
||
424 | */ |
||
425 | 12 | protected function toOne($target, $value) |
|
441 | |||
442 | /** |
||
443 | * Handle ToMany associations. In proper Doctrine design, Collections should not be swapped, so |
||
444 | * collections are always handled by reference. Internally, every collection is handled using specials |
||
445 | * strategies that inherit from AbstractCollectionStrategy class, and that add or remove elements but without |
||
446 | * changing the collection of the object |
||
447 | * |
||
448 | * @param object $object |
||
449 | * @param mixed $collectionName |
||
450 | * @param string $target |
||
451 | * @param mixed $values |
||
452 | * |
||
453 | * @throws \InvalidArgumentException |
||
454 | * |
||
455 | * @return void |
||
456 | */ |
||
457 | 17 | protected function toMany($object, $collectionName, $target, $values) |
|
530 | |||
531 | /** |
||
532 | * Handle various type conversions that should be supported natively by Doctrine (like DateTime) |
||
533 | * See Documentation of Doctrine Mapping Types for defaults |
||
534 | * |
||
535 | * @link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#doctrine-mapping-types |
||
536 | * @param mixed $value |
||
537 | * @param string $typeOfField |
||
538 | * @return DateTime |
||
539 | */ |
||
540 | 54 | protected function handleTypeConversions($value, $typeOfField) |
|
592 | |||
593 | /** |
||
594 | * Find an object by a given target class and identifier |
||
595 | * |
||
596 | * @param mixed $identifiers |
||
597 | * @param string $targetClass |
||
598 | * |
||
599 | * @return object|null |
||
600 | */ |
||
601 | 26 | protected function find($identifiers, $targetClass) |
|
613 | |||
614 | /** |
||
615 | * Verifies if a provided identifier is to be considered null |
||
616 | * |
||
617 | * @param mixed $identifier |
||
618 | * |
||
619 | * @return bool |
||
620 | */ |
||
621 | 24 | private function isNullIdentifier($identifier) |
|
640 | |||
641 | /** |
||
642 | * Check the field is nullable |
||
643 | * |
||
644 | * @param $name |
||
645 | * @return bool |
||
646 | */ |
||
647 | 6 | private function isNullable($name) |
|
648 | { |
||
649 | //TODO: need update after updating isNullable method of Doctrine\ORM\Mapping\ClassMetadata |
||
650 | 6 | if ($this->metadata->hasField($name)) { |
|
651 | return method_exists($this->metadata, 'isNullable') && $this->metadata->isNullable($name); |
||
652 | 6 | } else if ($this->metadata->hasAssociation($name) && method_exists($this->metadata, 'getAssociationMapping')) { |
|
653 | $mapping = $this->metadata->getAssociationMapping($name); |
||
654 | |||
655 | return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable']; |
||
656 | } |
||
657 | |||
658 | 6 | return false; |
|
659 | } |
||
660 | |||
661 | /** |
||
662 | * Applies the naming strategy if there is one set |
||
663 | * |
||
664 | * @param string $field |
||
665 | * |
||
666 | * @return string |
||
667 | */ |
||
668 | 55 | protected function computeHydrateFieldName($field) |
|
675 | |||
676 | /** |
||
677 | * Applies the naming strategy if there is one set |
||
678 | * |
||
679 | * @param string $field |
||
680 | * |
||
681 | * @return string |
||
682 | */ |
||
683 | 17 | protected function computeExtractFieldName($field) |
|
690 | } |
||
691 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.