Complex classes like DataParser 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 DataParser, and based on these observations, apply Extract Interface, too.
1 | <?php namespace Limoncello\Flute\Validation\JsonApi; |
||
44 | class DataParser implements JsonApiDataParserInterface |
||
45 | { |
||
46 | use RelationshipRulesTrait; |
||
47 | |||
48 | /** Rule description index */ |
||
49 | const RULE_INDEX = 0; |
||
50 | |||
51 | /** Rule description index */ |
||
52 | const RULE_ATTRIBUTES = self::RULE_INDEX + 1; |
||
53 | |||
54 | /** Rule description index */ |
||
55 | const RULE_TO_ONE = self::RULE_ATTRIBUTES + 1; |
||
56 | |||
57 | /** Rule description index */ |
||
58 | const RULE_TO_MANY = self::RULE_TO_ONE + 1; |
||
59 | |||
60 | /** Rule description index */ |
||
61 | const RULE_UNLISTED_ATTRIBUTE = self::RULE_TO_MANY + 1; |
||
62 | |||
63 | /** Rule description index */ |
||
64 | const RULE_UNLISTED_RELATIONSHIP = self::RULE_UNLISTED_ATTRIBUTE + 1; |
||
65 | |||
66 | /** |
||
67 | * NOTE: Despite the type it is just a string so only static methods can be called from the interface. |
||
68 | * |
||
69 | * @var JsonApiDataRulesSerializerInterface|string |
||
70 | */ |
||
71 | private $serializerClass; |
||
72 | |||
73 | /** |
||
74 | * @var int|null |
||
75 | */ |
||
76 | private $errorStatus; |
||
77 | |||
78 | /** |
||
79 | * @var ContextStorageInterface |
||
80 | */ |
||
81 | private $context; |
||
82 | |||
83 | /** |
||
84 | * @var JsonApiErrorCollection |
||
85 | */ |
||
86 | private $jsonApiErrors; |
||
87 | |||
88 | /** |
||
89 | * @var array |
||
90 | */ |
||
91 | private $blocks; |
||
92 | |||
93 | /** |
||
94 | * @var array |
||
95 | */ |
||
96 | private $idRule; |
||
97 | |||
98 | /** |
||
99 | * @var array |
||
100 | */ |
||
101 | private $typeRule; |
||
102 | |||
103 | /** |
||
104 | * @var int[] |
||
105 | */ |
||
106 | private $attributeRules; |
||
107 | |||
108 | /** |
||
109 | * @var int[] |
||
110 | */ |
||
111 | private $toOneRules; |
||
112 | |||
113 | /** |
||
114 | * @var int[] |
||
115 | */ |
||
116 | private $toManyRules; |
||
117 | |||
118 | /** |
||
119 | * @var bool |
||
120 | */ |
||
121 | private $isIgnoreUnknowns; |
||
122 | |||
123 | /** |
||
124 | * @var FormatterInterface|null |
||
125 | */ |
||
126 | private $formatter; |
||
127 | |||
128 | /** |
||
129 | * @var FormatterFactoryInterface |
||
130 | */ |
||
131 | private $formatterFactory; |
||
132 | |||
133 | /** |
||
134 | * @var ErrorAggregatorInterface |
||
135 | */ |
||
136 | private $errorAggregator; |
||
137 | |||
138 | /** |
||
139 | * @var CaptureAggregatorInterface |
||
140 | */ |
||
141 | private $captureAggregator; |
||
142 | |||
143 | /** |
||
144 | * @param string $rulesClass |
||
145 | * @param string $serializerClass |
||
146 | * @param array $serializedData |
||
147 | * @param ContextStorageInterface $context |
||
148 | * @param JsonApiErrorCollection $jsonErrors |
||
149 | * @param FormatterFactoryInterface $formatterFactory |
||
150 | * |
||
151 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
152 | */ |
||
153 | 27 | public function __construct( |
|
154 | string $rulesClass, |
||
155 | string $serializerClass, |
||
156 | array $serializedData, |
||
157 | ContextStorageInterface $context, |
||
158 | JsonApiErrorCollection $jsonErrors, |
||
159 | FormatterFactoryInterface $formatterFactory |
||
160 | ) { |
||
161 | $this |
||
162 | 27 | ->setSerializerClass($serializerClass) |
|
163 | 27 | ->setContext($context) |
|
164 | 27 | ->setJsonApiErrors($jsonErrors) |
|
165 | 27 | ->setFormatterFactory($formatterFactory); |
|
166 | |||
167 | 27 | $this->blocks = $this->getSerializer()::readBlocks($serializedData); |
|
168 | 27 | $ruleSet = $this->getSerializer()::readRules($rulesClass, $serializedData); |
|
169 | 27 | $this->idRule = $this->getSerializer()::readIdRuleIndexes($ruleSet); |
|
170 | 27 | $this->typeRule = $this->getSerializer()::readTypeRuleIndexes($ruleSet); |
|
171 | 27 | $this->errorStatus = null; |
|
172 | |||
173 | $this |
||
174 | 27 | ->setAttributeRules($this->getSerializer()::readAttributeRulesIndexes($ruleSet)) |
|
175 | 27 | ->setToOneIndexes($this->getSerializer()::readToOneRulesIndexes($ruleSet)) |
|
176 | 27 | ->setToManyIndexes($this->getSerializer()::readToManyRulesIndexes($ruleSet)) |
|
177 | 27 | ->disableIgnoreUnknowns(); |
|
178 | |||
179 | 27 | $this->errorAggregator = new ErrorAggregator(); |
|
180 | 27 | $this->captureAggregator = new CaptureAggregator(); |
|
181 | } |
||
182 | |||
183 | /** |
||
184 | * @inheritdoc |
||
185 | */ |
||
186 | 12 | public function assert(array $jsonData): JsonApiDataParserInterface |
|
187 | { |
||
188 | 12 | if ($this->parse($jsonData) === false) { |
|
189 | 5 | throw new JsonApiException($this->getJsonApiErrorCollection(), $this->getErrorStatus()); |
|
190 | } |
||
191 | |||
192 | 7 | return $this; |
|
193 | } |
||
194 | |||
195 | /** |
||
196 | * @inheritdoc |
||
197 | * |
||
198 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
199 | */ |
||
200 | 16 | public function parse(array $input): bool |
|
201 | { |
||
202 | 16 | $this->resetAggregators(); |
|
203 | |||
204 | $this |
||
205 | 16 | ->validateType($input) |
|
206 | 16 | ->validateId($input) |
|
207 | 16 | ->validateAttributes($input) |
|
208 | 16 | ->validateRelationships($input); |
|
209 | |||
210 | 16 | $hasNoErrors = $this->getJsonApiErrorCollection()->count() <= 0; |
|
211 | |||
212 | 16 | return $hasNoErrors; |
|
213 | } |
||
214 | |||
215 | /** |
||
216 | * @inheritdoc |
||
217 | * |
||
218 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
219 | */ |
||
220 | 7 | public function parseRelationship(string $index, string $name, array $jsonData): bool |
|
221 | { |
||
222 | 7 | $this->resetAggregators(); |
|
223 | |||
224 | 7 | $isFoundInToOne = array_key_exists($name, $this->getSerializer()::readRulesIndexes($this->getToOneRules())); |
|
225 | 7 | $isFoundInToMany = $isFoundInToOne === false && |
|
226 | 7 | array_key_exists($name, $this->getSerializer()::readRulesIndexes($this->getToManyRules())); |
|
227 | |||
228 | 7 | if ($isFoundInToOne === false && $isFoundInToMany === false) { |
|
229 | 1 | $title = $this->formatMessage(ErrorCodes::INVALID_VALUE); |
|
230 | 1 | $details = $this->formatMessage(ErrorCodes::UNKNOWN_RELATIONSHIP); |
|
231 | 1 | $status = JsonApiResponse::HTTP_UNPROCESSABLE_ENTITY; |
|
232 | 1 | $this->getJsonApiErrorCollection()->addRelationshipError($name, $title, $details, $status); |
|
233 | 1 | $this->addErrorStatus($status); |
|
234 | } else { |
||
235 | 6 | assert($isFoundInToOne xor $isFoundInToMany); |
|
236 | 6 | $ruleIndexes = $this->getSerializer()::readSingleRuleIndexes( |
|
237 | 6 | $isFoundInToOne === true ? $this->getToOneRules() : $this->getToManyRules(), |
|
238 | 6 | $name |
|
239 | ); |
||
240 | |||
241 | // now execute validation rules |
||
242 | 6 | $this->executeStarts($this->getSerializer()::readRuleStartIndexes($ruleIndexes)); |
|
243 | 6 | $ruleIndex = $this->getSerializer()::readRuleIndex($ruleIndexes); |
|
244 | 6 | $isFoundInToOne === true ? |
|
245 | 2 | $this->validateAsToOneRelationship($ruleIndex, $name, $jsonData) : |
|
246 | 4 | $this->validateAsToManyRelationship($ruleIndex, $name, $jsonData); |
|
247 | 6 | $this->executeEnds($this->getSerializer()::readRuleEndIndexes($ruleIndexes)); |
|
248 | |||
249 | 6 | if (count($this->getErrorAggregator()) > 0) { |
|
250 | 1 | $status = JsonApiResponse::HTTP_CONFLICT; |
|
251 | 1 | foreach ($this->getErrorAggregator()->get() as $error) { |
|
252 | 1 | $this->getJsonApiErrorCollection()->addValidationRelationshipError($error, $status); |
|
253 | 1 | $this->addErrorStatus($status); |
|
254 | } |
||
255 | 1 | $this->getErrorAggregator()->clear(); |
|
256 | } |
||
257 | } |
||
258 | |||
259 | 7 | $hasNoErrors = count($this->getJsonApiErrorCollection()) <= 0; |
|
260 | |||
261 | 7 | return $hasNoErrors; |
|
262 | } |
||
263 | |||
264 | /** |
||
265 | * @inheritdoc |
||
266 | */ |
||
267 | 7 | public function assertRelationship( |
|
268 | string $index, |
||
269 | string $name, |
||
270 | array $jsonData |
||
271 | ): JsonApiDataParserInterface { |
||
272 | 7 | if ($this->parseRelationship($index, $name, $jsonData) === false) { |
|
273 | 2 | $status = JsonApiResponse::HTTP_UNPROCESSABLE_ENTITY; |
|
274 | 2 | throw new JsonApiException($this->getJsonApiErrorCollection(), $status); |
|
275 | } |
||
276 | |||
277 | 5 | return $this; |
|
278 | } |
||
279 | |||
280 | /** |
||
281 | * @inheritdoc |
||
282 | */ |
||
283 | 6 | public function getErrors(): array |
|
284 | { |
||
285 | 6 | return $this->getJsonApiErrorCollection()->getArrayCopy(); |
|
286 | } |
||
287 | |||
288 | /** |
||
289 | * @inheritdoc |
||
290 | */ |
||
291 | 15 | public function getCaptures(): array |
|
292 | { |
||
293 | 15 | return $this->getCaptureAggregator()->get(); |
|
294 | } |
||
295 | |||
296 | /** |
||
297 | * @return self |
||
298 | */ |
||
299 | 23 | protected function resetAggregators(): self |
|
300 | { |
||
301 | 23 | $this->getCaptureAggregator()->clear(); |
|
302 | 23 | $this->getErrorAggregator()->clear(); |
|
303 | 23 | $this->getContext()->clear(); |
|
304 | |||
305 | 23 | return $this; |
|
306 | } |
||
307 | |||
308 | /** |
||
309 | * @param string $serializerClass |
||
310 | * |
||
311 | * @return self |
||
312 | */ |
||
313 | 27 | protected function setSerializerClass(string $serializerClass): self |
|
324 | |||
325 | /** |
||
326 | * @return JsonApiDataRulesSerializerInterface|string |
||
327 | */ |
||
328 | 27 | protected function getSerializer() |
|
332 | |||
333 | /** |
||
334 | * @param array $jsonData |
||
335 | * |
||
336 | * @return self |
||
337 | * |
||
338 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
339 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
340 | */ |
||
341 | 16 | private function validateType(array $jsonData): self |
|
378 | |||
379 | /** |
||
380 | * @param array $jsonData |
||
381 | * |
||
382 | * @return self |
||
383 | * |
||
384 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
385 | */ |
||
386 | 16 | private function validateId(array $jsonData): self |
|
417 | |||
418 | /** |
||
419 | * @param array $jsonData |
||
420 | * |
||
421 | * @return self |
||
422 | * |
||
423 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
424 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
425 | */ |
||
426 | 16 | private function validateAttributes(array $jsonData): self |
|
472 | |||
473 | /** |
||
474 | * @param array $jsonData |
||
475 | * |
||
476 | * @return self |
||
477 | * |
||
478 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
479 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
480 | */ |
||
481 | 16 | private function validateRelationships(array $jsonData): self |
|
541 | |||
542 | /** |
||
543 | * @param int $index |
||
544 | * @param string $name |
||
545 | * @param mixed $mightBeRelationship |
||
546 | * |
||
547 | * @return void |
||
548 | * |
||
549 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
550 | */ |
||
551 | 10 | private function validateAsToOneRelationship(int $index, string $name, $mightBeRelationship): void |
|
567 | |||
568 | /** |
||
569 | * @param int $index |
||
570 | * @param string $name |
||
571 | * @param mixed $mightBeRelationship |
||
572 | * |
||
573 | * @return void |
||
574 | * |
||
575 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
576 | */ |
||
577 | 12 | private function validateAsToManyRelationship(int $index, string $name, $mightBeRelationship): void |
|
609 | |||
610 | /** |
||
611 | * @param mixed $data |
||
612 | * |
||
613 | * @return array|null|false Either `array` ($type => $id), or `null`, or `false` on error. |
||
|
|||
614 | * |
||
615 | * @SuppressWarnings(PHPMD.ElseExpression) |
||
616 | */ |
||
617 | 13 | private function parseSingleRelationship($data) |
|
634 | |||
635 | /** |
||
636 | * @param mixed $input |
||
637 | * @param int $index |
||
638 | * |
||
639 | * @return void |
||
640 | * |
||
641 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
642 | */ |
||
643 | 21 | private function executeBlock($input, int $index): void |
|
654 | |||
655 | /** |
||
656 | * @param array $indexes |
||
657 | * |
||
658 | * @return void |
||
659 | * |
||
660 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
661 | */ |
||
662 | 22 | private function executeStarts(array $indexes): void |
|
671 | |||
672 | /** |
||
673 | * @param array $indexes |
||
674 | * |
||
675 | * @return void |
||
676 | * |
||
677 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
678 | */ |
||
679 | 22 | private function executeEnds(array $indexes): void |
|
688 | |||
689 | /** |
||
690 | * @param ErrorInterface $error |
||
691 | * |
||
692 | * @return string |
||
693 | */ |
||
694 | 3 | private function getMessage(ErrorInterface $error): string |
|
702 | |||
703 | /** |
||
704 | * @return array |
||
705 | */ |
||
706 | 16 | protected function getIdRule(): array |
|
710 | |||
711 | /** |
||
712 | * @return array |
||
713 | */ |
||
714 | 16 | protected function getTypeRule(): array |
|
718 | |||
719 | /** |
||
720 | * @return ContextStorageInterface |
||
721 | */ |
||
722 | 23 | protected function getContext(): ContextStorageInterface |
|
726 | |||
727 | /** |
||
728 | * @param ContextStorageInterface $context |
||
729 | * |
||
730 | * @return self |
||
731 | */ |
||
732 | 27 | protected function setContext(ContextStorageInterface $context): self |
|
738 | |||
739 | /** |
||
740 | * @return JsonApiErrorCollection |
||
741 | */ |
||
742 | 23 | protected function getJsonApiErrorCollection(): JsonApiErrorCollection |
|
746 | |||
747 | /** |
||
748 | * @param JsonApiErrorCollection $errors |
||
749 | * |
||
750 | * @return self |
||
751 | */ |
||
752 | 27 | protected function setJsonApiErrors(JsonApiErrorCollection $errors): self |
|
758 | |||
759 | /** |
||
760 | * @return bool |
||
761 | */ |
||
762 | 1 | protected function isIgnoreUnknowns(): bool |
|
766 | |||
767 | /** |
||
768 | * @return self |
||
769 | */ |
||
770 | 1 | protected function enableIgnoreUnknowns(): self |
|
776 | |||
777 | /** |
||
778 | * @return self |
||
779 | */ |
||
780 | 27 | protected function disableIgnoreUnknowns(): self |
|
786 | |||
787 | /** |
||
788 | * @return int |
||
789 | */ |
||
790 | 5 | private function getErrorStatus(): int |
|
796 | |||
797 | /** |
||
798 | * @param int $status |
||
799 | */ |
||
800 | 11 | private function addErrorStatus(int $status): void |
|
822 | |||
823 | /** |
||
824 | * @param array $rules |
||
825 | * |
||
826 | * @return self |
||
827 | */ |
||
828 | 27 | private function setAttributeRules(array $rules): self |
|
836 | |||
837 | /** |
||
838 | * @param array $rules |
||
839 | * |
||
840 | * @return self |
||
841 | */ |
||
842 | 27 | private function setToOneIndexes(array $rules): self |
|
850 | |||
851 | /** |
||
852 | * @param array $rules |
||
853 | * |
||
854 | * @return self |
||
855 | */ |
||
856 | 27 | private function setToManyIndexes(array $rules): self |
|
864 | |||
865 | /** |
||
866 | * @return int[] |
||
867 | */ |
||
868 | 16 | protected function getAttributeRules(): array |
|
872 | |||
873 | /** |
||
874 | * @return int[] |
||
875 | */ |
||
876 | 23 | protected function getToOneRules(): array |
|
880 | |||
881 | /** |
||
882 | * @return int[] |
||
883 | */ |
||
884 | 21 | protected function getToManyRules(): array |
|
888 | |||
889 | /** |
||
890 | * @return array |
||
891 | */ |
||
892 | 25 | private function getBlocks(): array |
|
896 | |||
897 | /** |
||
898 | * @return FormatterInterface |
||
899 | */ |
||
900 | 9 | protected function getFormatter(): FormatterInterface |
|
908 | |||
909 | /** |
||
910 | * @param FormatterFactoryInterface $formatterFactory |
||
911 | * |
||
912 | * @return self |
||
913 | */ |
||
914 | 27 | protected function setFormatterFactory(FormatterFactoryInterface $formatterFactory): self |
|
920 | |||
921 | /** |
||
922 | * @param string $name |
||
923 | * |
||
924 | * @return int|null |
||
925 | * |
||
926 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
927 | */ |
||
928 | 10 | private function getAttributeIndex(string $name): ?int |
|
935 | |||
936 | /** |
||
937 | * @param int $messageId |
||
938 | * @param array $args |
||
939 | * |
||
940 | * @return string |
||
941 | */ |
||
942 | 9 | private function formatMessage(int $messageId, array $args = []): string |
|
948 | |||
949 | /** |
||
950 | * @return ErrorAggregatorInterface |
||
951 | */ |
||
952 | 23 | private function getErrorAggregator(): ErrorAggregatorInterface |
|
956 | |||
957 | /** |
||
958 | * @return CaptureAggregatorInterface |
||
959 | */ |
||
960 | 23 | private function getCaptureAggregator(): CaptureAggregatorInterface |
|
964 | |||
965 | /** |
||
966 | * @param array $rules |
||
967 | * |
||
968 | * @return bool |
||
969 | * |
||
970 | * @SuppressWarnings(PHPMD.StaticAccess) |
||
971 | */ |
||
972 | 27 | private function debugCheckIndexesExist(array $rules): bool |
|
988 | } |
||
989 |
This check looks for the generic type
array
as a return type and suggests a more specific type. This type is inferred from the actual code.