Complex classes like SchemaReader 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 SchemaReader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
41 | class SchemaReader |
||
42 | { |
||
43 | public const XSD_NS = 'http://www.w3.org/2001/XMLSchema'; |
||
44 | |||
45 | public const XML_NS = 'http://www.w3.org/XML/1998/namespace'; |
||
46 | |||
47 | /** |
||
48 | * @var DocumentationReader |
||
49 | */ |
||
50 | private $documentationReader; |
||
51 | |||
52 | /** |
||
53 | * @var Schema[] |
||
54 | */ |
||
55 | private $loadedFiles = array(); |
||
56 | |||
57 | /** |
||
58 | * @var Schema[][] |
||
59 | */ |
||
60 | private $loadedSchemas = array(); |
||
61 | |||
62 | /** |
||
63 | * @var string[] |
||
64 | */ |
||
65 | protected $knownLocationSchemas = [ |
||
66 | 'http://www.w3.org/2001/xml.xsd' => ( |
||
67 | __DIR__.'/Resources/xml.xsd' |
||
68 | ), |
||
69 | 'http://www.w3.org/2001/XMLSchema.xsd' => ( |
||
70 | __DIR__.'/Resources/XMLSchema.xsd' |
||
71 | ), |
||
72 | 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => ( |
||
73 | __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd' |
||
74 | ), |
||
75 | 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => ( |
||
76 | __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd' |
||
77 | ), |
||
78 | 'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => ( |
||
79 | __DIR__.'/Resources/xmldsig-core-schema.xsd' |
||
80 | ), |
||
81 | 'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => ( |
||
82 | __DIR__.'/Resources/xmldsig-core-schema.xsd' |
||
83 | ), |
||
84 | ]; |
||
85 | |||
86 | /** |
||
87 | * @var string[] |
||
88 | */ |
||
89 | protected static $globalSchemaInfo = array( |
||
90 | self::XML_NS => 'http://www.w3.org/2001/xml.xsd', |
||
91 | self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd', |
||
92 | ); |
||
93 | |||
94 | 58 | public function __construct(DocumentationReader $documentationReader = null) |
|
95 | { |
||
96 | 58 | if (null === $documentationReader) { |
|
97 | 58 | $documentationReader = new StandardDocumentationReader(); |
|
98 | } |
||
99 | 58 | $this->documentationReader = $documentationReader; |
|
100 | 58 | } |
|
101 | |||
102 | public function addKnownSchemaLocation(string $remote, string $local): void |
||
103 | { |
||
104 | $this->knownLocationSchemas[$remote] = $local; |
||
105 | } |
||
106 | |||
107 | 49 | private function loadAttributeGroup( |
|
108 | Schema $schema, |
||
109 | DOMElement $node |
||
110 | ): Closure { |
||
111 | 49 | $attGroup = new AttributeGroup($schema, $node->getAttribute('name')); |
|
112 | 49 | $attGroup->setDoc($this->getDocumentation($node)); |
|
113 | 49 | $schema->addAttributeGroup($attGroup); |
|
114 | |||
115 | 49 | return function () use ($schema, $node, $attGroup) { |
|
116 | 49 | SchemaReader::againstDOMNodeList( |
|
117 | 49 | $node, |
|
118 | 49 | function ( |
|
119 | DOMElement $node, |
||
120 | DOMElement $childNode |
||
121 | ) use ( |
||
122 | 49 | $schema, |
|
123 | 49 | $attGroup |
|
124 | ) { |
||
125 | 49 | switch ($childNode->localName) { |
|
126 | 49 | case 'attribute': |
|
127 | 49 | $attribute = $this->getAttributeFromAttributeOrRef( |
|
128 | 49 | $childNode, |
|
129 | 49 | $schema, |
|
130 | 49 | $node |
|
131 | ); |
||
132 | 49 | $attGroup->addAttribute($attribute); |
|
133 | 49 | break; |
|
134 | 49 | case 'attributeGroup': |
|
135 | 1 | $this->findSomethingLikeAttributeGroup( |
|
136 | 1 | $schema, |
|
137 | 1 | $node, |
|
138 | 1 | $childNode, |
|
139 | 1 | $attGroup |
|
140 | ); |
||
141 | 1 | break; |
|
142 | } |
||
143 | 49 | } |
|
144 | ); |
||
145 | 49 | }; |
|
146 | } |
||
147 | |||
148 | 49 | private function getAttributeFromAttributeOrRef( |
|
164 | |||
165 | 49 | private function loadAttribute( |
|
166 | Schema $schema, |
||
167 | DOMElement $node |
||
168 | ): Attribute { |
||
169 | 49 | $attribute = new Attribute($schema, $node->getAttribute('name')); |
|
170 | 49 | $attribute->setDoc($this->getDocumentation($node)); |
|
171 | 49 | $this->fillItem($attribute, $node); |
|
172 | |||
173 | 49 | if ($node->hasAttribute('nillable')) { |
|
174 | 1 | $attribute->setNil($node->getAttribute('nillable') == 'true'); |
|
175 | } |
||
176 | 49 | if ($node->hasAttribute('form')) { |
|
177 | 1 | $attribute->setQualified($node->getAttribute('form') == 'qualified'); |
|
178 | } |
||
179 | 49 | if ($node->hasAttribute('use')) { |
|
180 | 49 | $attribute->setUse($node->getAttribute('use')); |
|
181 | } |
||
182 | |||
183 | 49 | return $attribute; |
|
184 | } |
||
185 | |||
186 | 49 | private function loadAttributeOrElementDef( |
|
187 | Schema $schema, |
||
188 | DOMElement $node, |
||
189 | bool $attributeDef |
||
190 | ): Closure { |
||
191 | 49 | $name = $node->getAttribute('name'); |
|
192 | 49 | if ($attributeDef) { |
|
193 | 49 | $attribute = new AttributeDef($schema, $name); |
|
194 | 49 | $schema->addAttribute($attribute); |
|
195 | } else { |
||
196 | 49 | $attribute = new ElementDef($schema, $name); |
|
197 | 49 | $schema->addElement($attribute); |
|
198 | } |
||
199 | |||
200 | 49 | return function () use ($attribute, $node) { |
|
201 | 49 | $this->fillItem($attribute, $node); |
|
202 | 49 | }; |
|
203 | } |
||
204 | |||
205 | 49 | private function loadAttributeDef(Schema $schema, DOMElement $node): Closure |
|
209 | |||
210 | 49 | private function getDocumentation(DOMElement $node): string |
|
211 | { |
||
212 | 49 | return $this->documentationReader->get($node); |
|
213 | } |
||
214 | |||
215 | /** |
||
216 | * @return Closure[] |
||
217 | */ |
||
218 | 49 | private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array |
|
267 | |||
268 | 49 | private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef |
|
269 | { |
||
270 | 49 | $ref = new GroupRef($referenced); |
|
271 | 49 | $ref->setDoc($this->getDocumentation($node)); |
|
272 | |||
273 | 49 | self::maybeSetMax($ref, $node); |
|
274 | 49 | self::maybeSetMin($ref, $node); |
|
275 | |||
276 | 49 | return $ref; |
|
277 | } |
||
278 | |||
279 | 49 | private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): InterfaceSetMinMax |
|
280 | { |
||
281 | if ( |
||
282 | 49 | $node->hasAttribute('maxOccurs') |
|
283 | ) { |
||
284 | 49 | $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs')); |
|
285 | } |
||
286 | |||
287 | 49 | return $ref; |
|
288 | } |
||
289 | |||
290 | 49 | private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): InterfaceSetMinMax |
|
291 | { |
||
292 | 49 | if ($node->hasAttribute('minOccurs')) { |
|
293 | 49 | $ref->setMin((int) $node->getAttribute('minOccurs')); |
|
294 | } |
||
295 | |||
296 | 49 | return $ref; |
|
297 | } |
||
298 | |||
299 | 49 | private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null): void |
|
300 | { |
||
301 | $max = |
||
302 | ( |
||
303 | 49 | (is_int($max) && (bool) $max) || |
|
304 | 49 | $node->getAttribute('maxOccurs') == 'unbounded' || |
|
305 | 49 | $node->getAttribute('maxOccurs') > 1 |
|
306 | ) |
||
307 | 49 | ? 2 |
|
308 | 49 | : null; |
|
309 | |||
310 | 49 | static::againstDOMNodeList( |
|
311 | 49 | $node, |
|
312 | 49 | function ( |
|
313 | DOMElement $node, |
||
314 | DOMElement $childNode |
||
315 | ) use ( |
||
316 | 49 | $elementContainer, |
|
317 | 49 | $max |
|
318 | ) { |
||
319 | 49 | $this->loadSequenceChildNode( |
|
320 | 49 | $elementContainer, |
|
321 | 49 | $node, |
|
322 | 49 | $childNode, |
|
323 | 49 | $max |
|
324 | ); |
||
325 | 49 | } |
|
326 | ); |
||
327 | 49 | } |
|
328 | |||
329 | /** |
||
330 | * @param int|null $max |
||
331 | */ |
||
332 | 49 | private function loadSequenceChildNode( |
|
366 | |||
367 | /** |
||
368 | * @param int|null $max |
||
369 | */ |
||
370 | 49 | private function loadSequenceChildNodeLoadElement( |
|
406 | |||
407 | 49 | private function addGroupAsElement( |
|
408 | Schema $schema, |
||
409 | DOMElement $node, |
||
410 | DOMElement $childNode, |
||
411 | ElementContainer $elementContainer |
||
412 | ): void { |
||
413 | 49 | $referencedGroup = $this->findGroup( |
|
414 | 49 | $schema, |
|
415 | 49 | $node, |
|
416 | 49 | $childNode->getAttribute('ref') |
|
417 | ); |
||
418 | |||
419 | 49 | $group = $this->loadGroupRef($referencedGroup, $childNode); |
|
420 | 49 | $elementContainer->addElement($group); |
|
421 | 49 | } |
|
422 | |||
423 | 49 | private function loadGroup(Schema $schema, DOMElement $node): Closure |
|
424 | { |
||
425 | 49 | $group = new Group($schema, $node->getAttribute('name')); |
|
426 | 49 | $group->setDoc($this->getDocumentation($node)); |
|
427 | |||
428 | 49 | if ($node->hasAttribute('maxOccurs')) { |
|
429 | /** |
||
430 | * @var GroupRef |
||
431 | */ |
||
432 | $group = self::maybeSetMax(new GroupRef($group), $node); |
||
433 | } |
||
434 | 49 | if ($node->hasAttribute('minOccurs')) { |
|
435 | /** |
||
436 | * @var GroupRef |
||
437 | */ |
||
438 | $group = self::maybeSetMin( |
||
439 | $group instanceof GroupRef ? $group : new GroupRef($group), |
||
440 | $node |
||
441 | ); |
||
442 | } |
||
443 | |||
444 | 49 | $schema->addGroup($group); |
|
445 | |||
446 | 49 | return function () use ($group, $node) { |
|
447 | 49 | static::againstDOMNodeList( |
|
448 | 49 | $node, |
|
449 | 49 | function (DOMelement $node, DOMElement $childNode) use ($group) { |
|
450 | 49 | switch ($childNode->localName) { |
|
451 | 49 | case 'sequence': |
|
452 | 49 | case 'choice': |
|
453 | 49 | case 'all': |
|
454 | 49 | $this->loadSequence($group, $childNode); |
|
455 | 49 | break; |
|
456 | } |
||
457 | 49 | } |
|
458 | ); |
||
459 | 49 | }; |
|
460 | } |
||
461 | |||
462 | 49 | private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure |
|
519 | |||
520 | 49 | private function loadComplexTypeFromChildNode( |
|
521 | BaseComplexType $type, |
||
522 | DOMElement $node, |
||
523 | DOMElement $childNode, |
||
524 | Schema $schema |
||
525 | ): void { |
||
526 | 49 | switch ($childNode->localName) { |
|
527 | 49 | case 'sequence': |
|
528 | 49 | case 'choice': |
|
529 | 49 | case 'all': |
|
530 | 49 | if ($type instanceof ElementContainer) { |
|
531 | 49 | $this->loadSequence( |
|
532 | 49 | $type, |
|
533 | 49 | $childNode |
|
534 | ); |
||
535 | } |
||
536 | 49 | break; |
|
537 | 49 | case 'attribute': |
|
538 | 49 | $this->addAttributeFromAttributeOrRef( |
|
539 | 49 | $type, |
|
540 | 49 | $childNode, |
|
541 | 49 | $schema, |
|
542 | 49 | $node |
|
543 | ); |
||
544 | 49 | break; |
|
545 | 49 | case 'attributeGroup': |
|
546 | 2 | $this->findSomethingLikeAttributeGroup( |
|
547 | 2 | $schema, |
|
548 | 2 | $node, |
|
549 | 2 | $childNode, |
|
550 | 2 | $type |
|
551 | ); |
||
552 | 2 | break; |
|
553 | 49 | case 'group': |
|
554 | if ( |
||
555 | 1 | $type instanceof ComplexType |
|
556 | ) { |
||
557 | 1 | $this->addGroupAsElement( |
|
558 | 1 | $schema, |
|
559 | 1 | $node, |
|
560 | 1 | $childNode, |
|
561 | 1 | $type |
|
562 | ); |
||
563 | } |
||
564 | 1 | break; |
|
565 | } |
||
566 | 49 | } |
|
567 | |||
568 | 49 | private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure |
|
569 | { |
||
570 | 49 | $type = new SimpleType($schema, $node->getAttribute('name')); |
|
571 | 49 | $type->setDoc($this->getDocumentation($node)); |
|
572 | 49 | if ($node->getAttribute('name')) { |
|
573 | 49 | $schema->addType($type); |
|
574 | } |
||
575 | |||
576 | 49 | return function () use ($type, $node, $callback) { |
|
577 | 49 | $this->fillTypeNode($type, $node, true); |
|
578 | |||
579 | 49 | static::againstDOMNodeList( |
|
580 | 49 | $node, |
|
581 | 49 | function (DOMElement $node, DOMElement $childNode) use ($type) { |
|
582 | 49 | switch ($childNode->localName) { |
|
583 | 49 | case 'union': |
|
584 | 49 | $this->loadUnion($type, $childNode); |
|
585 | 49 | break; |
|
586 | 49 | case 'list': |
|
587 | 49 | $this->loadList($type, $childNode); |
|
588 | 49 | break; |
|
589 | } |
||
590 | 49 | } |
|
591 | ); |
||
592 | |||
593 | 49 | if ($callback instanceof Closure) { |
|
594 | 49 | call_user_func($callback, $type); |
|
595 | } |
||
596 | 49 | }; |
|
597 | } |
||
598 | |||
599 | 49 | private function loadList(SimpleType $type, DOMElement $node): void |
|
600 | { |
||
601 | 49 | if ($node->hasAttribute('itemType')) { |
|
602 | /** |
||
603 | * @var SimpleType |
||
604 | */ |
||
605 | 49 | $listType = $this->findSomeType($type, $node, 'itemType'); |
|
606 | 49 | $type->setList($listType); |
|
607 | } else { |
||
608 | 49 | self::againstDOMNodeList( |
|
609 | 49 | $node, |
|
610 | 49 | function ( |
|
611 | DOMElement $node, |
||
612 | DOMElement $childNode |
||
613 | ) use ( |
||
614 | 49 | $type |
|
615 | ) { |
||
616 | 49 | $this->loadTypeWithCallback( |
|
617 | 49 | $type->getSchema(), |
|
618 | 49 | $childNode, |
|
619 | 49 | function (SimpleType $list) use ($type) { |
|
620 | 49 | $type->setList($list); |
|
621 | 49 | } |
|
622 | ); |
||
623 | 49 | } |
|
624 | ); |
||
625 | } |
||
626 | 49 | } |
|
627 | |||
628 | 49 | private function findSomeType( |
|
639 | |||
640 | 49 | private function findSomeTypeFromAttribute( |
|
641 | SchemaItem $fromThis, |
||
642 | DOMElement $node, |
||
643 | string $attributeName |
||
644 | ): SchemaItem { |
||
645 | 49 | $out = $this->findType( |
|
646 | 49 | $fromThis->getSchema(), |
|
647 | 49 | $node, |
|
648 | 49 | $attributeName |
|
649 | ); |
||
650 | |||
651 | 49 | return $out; |
|
652 | } |
||
653 | |||
654 | 49 | private function loadUnion(SimpleType $type, DOMElement $node): void |
|
655 | { |
||
656 | 49 | if ($node->hasAttribute('memberTypes')) { |
|
657 | 49 | $types = preg_split('/\s+/', $node->getAttribute('memberTypes')); |
|
658 | 49 | foreach ($types as $typeName) { |
|
659 | /** |
||
660 | * @var SimpleType |
||
661 | */ |
||
662 | 49 | $unionType = $this->findSomeTypeFromAttribute( |
|
663 | 49 | $type, |
|
664 | 49 | $node, |
|
665 | 49 | $typeName |
|
666 | ); |
||
667 | 49 | $type->addUnion($unionType); |
|
668 | } |
||
669 | } |
||
670 | 49 | self::againstDOMNodeList( |
|
671 | 49 | $node, |
|
672 | 49 | function ( |
|
673 | DOMElement $node, |
||
674 | DOMElement $childNode |
||
675 | ) use ( |
||
676 | 49 | $type |
|
677 | ) { |
||
678 | 49 | $this->loadTypeWithCallback( |
|
679 | 49 | $type->getSchema(), |
|
680 | 49 | $childNode, |
|
681 | 49 | function (SimpleType $unType) use ($type) { |
|
682 | 49 | $type->addUnion($unType); |
|
683 | 49 | } |
|
684 | ); |
||
685 | 49 | } |
|
686 | ); |
||
687 | 49 | } |
|
688 | |||
689 | 49 | private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void |
|
715 | |||
716 | 49 | private function loadExtension(BaseComplexType $type, DOMElement $node): void |
|
717 | { |
||
718 | 49 | $extension = new Extension(); |
|
719 | 49 | $type->setExtension($extension); |
|
720 | |||
721 | 49 | if ($node->hasAttribute('base')) { |
|
722 | 49 | $this->findAndSetSomeBase( |
|
723 | 49 | $type, |
|
724 | 49 | $extension, |
|
725 | 49 | $node |
|
726 | ); |
||
727 | } |
||
728 | 49 | $this->loadExtensionChildNodes($type, $node); |
|
729 | 49 | } |
|
730 | |||
731 | 49 | private function findAndSetSomeBase( |
|
742 | |||
743 | 49 | private function loadExtensionChildNodes( |
|
744 | BaseComplexType $type, |
||
745 | DOMElement $node |
||
746 | ) { |
||
747 | 49 | static::againstDOMNodeList( |
|
748 | 49 | $node, |
|
749 | 49 | function ( |
|
750 | DOMElement $node, |
||
751 | DOMElement $childNode |
||
752 | ) use ( |
||
753 | 49 | $type |
|
754 | ) { |
||
755 | 49 | switch ($childNode->localName) { |
|
756 | 49 | case 'sequence': |
|
757 | 49 | case 'choice': |
|
758 | 49 | case 'all': |
|
759 | 49 | if ($type instanceof ElementContainer) { |
|
760 | 49 | $this->loadSequence( |
|
786 | |||
787 | 49 | private function loadRestriction(Type $type, DOMElement $node): void |
|
852 | |||
853 | /** |
||
854 | * @return mixed[] |
||
855 | */ |
||
856 | 49 | private static function splitParts(DOMElement $node, string $typeName): array |
|
872 | |||
873 | 49 | private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem |
|
896 | |||
897 | 49 | private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup |
|
920 | |||
921 | 49 | private function findElement(Schema $schema, DOMElement $node, string $typeName): ElementDef |
|
944 | |||
945 | 49 | private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group |
|
968 | |||
969 | 49 | private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem |
|
992 | |||
993 | /** |
||
994 | * @return Closure |
||
995 | */ |
||
996 | 49 | private function loadElementDef(Schema $schema, DOMElement $node): Closure |
|
1000 | |||
1001 | 49 | private function fillItem(Item $element, DOMElement $node) |
|
1043 | |||
1044 | 49 | private function fillItemNonLocalType(Item $element, DOMElement $node): void |
|
1064 | |||
1065 | 49 | private function loadImport( |
|
1097 | |||
1098 | 1 | private function createOrUseSchemaForNs( |
|
1112 | |||
1113 | private function loadImportFresh( |
||
1136 | |||
1137 | /** |
||
1138 | * @var Schema|null |
||
1139 | */ |
||
1140 | protected $globalSchema; |
||
1141 | |||
1142 | /** |
||
1143 | * @return Schema |
||
1144 | */ |
||
1145 | 49 | public function getGlobalSchema(): Schema |
|
1196 | |||
1197 | 1 | public function readNodes(array $nodes, string $file = null) |
|
1227 | |||
1228 | 48 | public function readNode(DOMElement $node, string $file = null): Schema |
|
1247 | |||
1248 | /** |
||
1249 | * @throws IOException |
||
1250 | */ |
||
1251 | 46 | public function readString(string $content, string $file = 'schema.xsd'): Schema |
|
1261 | |||
1262 | 1 | public function readFile(string $file): Schema |
|
1268 | |||
1269 | /** |
||
1270 | * @throws IOException |
||
1271 | */ |
||
1272 | 49 | private function getDOM(string $file): DOMDocument |
|
1281 | |||
1282 | 49 | private static function againstDOMNodeList( |
|
1301 | |||
1302 | 49 | private function loadTypeWithCallback( |
|
1325 | |||
1326 | 49 | private function loadElement( |
|
1354 | |||
1355 | 49 | private function addAttributeFromAttributeOrRef( |
|
1369 | |||
1370 | 49 | private function findSomethingLikeAttributeGroup( |
|
1379 | |||
1380 | 49 | private function setLoadedFile(string $key, Schema $schema): void |
|
1384 | |||
1385 | 49 | private function setLoadedSchema(DOMElement $node, Schema $schema): void |
|
1391 | |||
1392 | 49 | private function setSchemaThingsFromNode( |
|
1408 | } |
||
1409 |
Let’s assume you have a class which uses late-static binding:
}
The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the
getSomeVariable()
on that sub-class, you will receive a runtime error:In the case above, it makes sense to update
SomeClass
to useself
instead: