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 | 2 | private function extractErrorMessage(): \Exception |
|
95 | { |
||
96 | 2 | $errors = array(); |
|
97 | |||
98 | 2 | foreach (libxml_get_errors() as $error) { |
|
99 | 1 | $errors[] = sprintf("Error[%s] code %s: %s in '%s' at position %s:%s", $error->level, $error->code, trim($error->message), $error->file, $error->line, $error->column); |
|
100 | } |
||
101 | 2 | $e = new \Exception(implode("; ", $errors)); |
|
102 | 2 | libxml_use_internal_errors(false); |
|
103 | 2 | return $e; |
|
104 | } |
||
105 | |||
106 | 69 | public function __construct(DocumentationReader $documentationReader = null) |
|
107 | { |
||
108 | 69 | if (null === $documentationReader) { |
|
109 | 69 | $documentationReader = new StandardDocumentationReader(); |
|
110 | } |
||
111 | 69 | $this->documentationReader = $documentationReader; |
|
112 | 69 | } |
|
113 | |||
114 | 1 | public function addKnownSchemaLocation(string $remote, string $local): void |
|
115 | { |
||
116 | 1 | $this->knownLocationSchemas[$remote] = $local; |
|
117 | 1 | } |
|
118 | |||
119 | 58 | private function loadAttributeGroup( |
|
120 | Schema $schema, |
||
121 | DOMElement $node |
||
122 | ): Closure { |
||
123 | 58 | $attGroup = new AttributeGroup($schema, $node->getAttribute('name')); |
|
124 | 58 | $attGroup->setDoc($this->getDocumentation($node)); |
|
125 | 58 | $schema->addAttributeGroup($attGroup); |
|
126 | |||
127 | 58 | return function () use ($schema, $node, $attGroup) { |
|
128 | 58 | SchemaReader::againstDOMNodeList( |
|
129 | 58 | $node, |
|
130 | 58 | function ( |
|
131 | DOMElement $node, |
||
132 | DOMElement $childNode |
||
133 | ) use ( |
||
134 | 58 | $schema, |
|
135 | 58 | $attGroup |
|
136 | ) { |
||
137 | 58 | switch ($childNode->localName) { |
|
138 | 58 | case 'attribute': |
|
139 | 58 | $attribute = $this->getAttributeFromAttributeOrRef( |
|
140 | 58 | $childNode, |
|
141 | 58 | $schema, |
|
142 | 58 | $node |
|
143 | ); |
||
144 | 58 | $attGroup->addAttribute($attribute); |
|
145 | 58 | break; |
|
146 | 58 | case 'attributeGroup': |
|
147 | 1 | $this->findSomethingLikeAttributeGroup( |
|
148 | 1 | $schema, |
|
149 | 1 | $node, |
|
150 | 1 | $childNode, |
|
151 | 1 | $attGroup |
|
152 | ); |
||
153 | 1 | break; |
|
154 | } |
||
155 | 58 | } |
|
156 | ); |
||
157 | 58 | }; |
|
158 | } |
||
159 | |||
160 | 58 | private function getAttributeFromAttributeOrRef( |
|
161 | DOMElement $childNode, |
||
162 | Schema $schema, |
||
163 | DOMElement $node |
||
164 | ): AttributeItem { |
||
165 | 58 | if ($childNode->hasAttribute('ref')) { |
|
166 | 58 | $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref')); |
|
167 | } else { |
||
168 | /** |
||
169 | * @var Attribute |
||
170 | */ |
||
171 | 58 | $attribute = $this->loadAttribute($schema, $childNode); |
|
172 | } |
||
173 | |||
174 | 58 | return $attribute; |
|
175 | } |
||
176 | |||
177 | 58 | private function loadAttribute( |
|
178 | Schema $schema, |
||
179 | DOMElement $node |
||
180 | ): Attribute { |
||
181 | 58 | $attribute = new Attribute($schema, $node->getAttribute('name')); |
|
182 | 58 | $attribute->setDoc($this->getDocumentation($node)); |
|
183 | 58 | $this->fillItem($attribute, $node); |
|
184 | |||
185 | 58 | if ($node->hasAttribute('nillable')) { |
|
186 | 1 | $attribute->setNil($node->getAttribute('nillable') == 'true'); |
|
187 | } |
||
188 | 58 | if ($node->hasAttribute('form')) { |
|
189 | 1 | $attribute->setQualified($node->getAttribute('form') == 'qualified'); |
|
190 | } |
||
191 | 58 | if ($node->hasAttribute('use')) { |
|
192 | 58 | $attribute->setUse($node->getAttribute('use')); |
|
193 | } |
||
194 | |||
195 | 58 | return $attribute; |
|
196 | } |
||
197 | |||
198 | 58 | private function loadAttributeOrElementDef( |
|
199 | Schema $schema, |
||
200 | DOMElement $node, |
||
201 | bool $attributeDef |
||
202 | ): Closure { |
||
203 | 58 | $name = $node->getAttribute('name'); |
|
204 | 58 | if ($attributeDef) { |
|
205 | 58 | $attribute = new AttributeDef($schema, $name); |
|
206 | 58 | $schema->addAttribute($attribute); |
|
207 | } else { |
||
208 | 58 | $attribute = new ElementDef($schema, $name); |
|
209 | 58 | $schema->addElement($attribute); |
|
210 | } |
||
211 | |||
212 | 58 | return function () use ($attribute, $node) { |
|
213 | 58 | $this->fillItem($attribute, $node); |
|
214 | 58 | }; |
|
215 | } |
||
216 | |||
217 | 58 | private function loadAttributeDef(Schema $schema, DOMElement $node): Closure |
|
218 | { |
||
219 | 58 | return $this->loadAttributeOrElementDef($schema, $node, true); |
|
220 | } |
||
221 | |||
222 | 58 | private function getDocumentation(DOMElement $node): string |
|
223 | { |
||
224 | 58 | return $this->documentationReader->get($node); |
|
225 | } |
||
226 | |||
227 | /** |
||
228 | * @return Closure[] |
||
229 | */ |
||
230 | 58 | private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array |
|
231 | { |
||
232 | 58 | $this->setSchemaThingsFromNode($schema, $node, $parent); |
|
233 | 58 | $functions = array(); |
|
234 | |||
235 | 58 | static::againstDOMNodeList( |
|
|
|||
236 | 58 | $node, |
|
237 | 58 | function ( |
|
238 | DOMElement $node, |
||
239 | DOMElement $childNode |
||
240 | ) use ( |
||
241 | 58 | $schema, |
|
242 | 58 | &$functions |
|
243 | ) { |
||
244 | 58 | $callback = null; |
|
245 | |||
246 | 58 | switch ($childNode->localName) { |
|
247 | 58 | case 'attributeGroup': |
|
248 | 58 | $callback = $this->loadAttributeGroup($schema, $childNode); |
|
249 | 58 | break; |
|
250 | 58 | case 'include': |
|
251 | 58 | case 'import': |
|
252 | 58 | $callback = $this->loadImport($schema, $childNode); |
|
253 | 58 | break; |
|
254 | 58 | case 'element': |
|
255 | 58 | $callback = $this->loadElementDef($schema, $childNode); |
|
256 | 58 | break; |
|
257 | 58 | case 'attribute': |
|
258 | 58 | $callback = $this->loadAttributeDef($schema, $childNode); |
|
259 | 58 | break; |
|
260 | 58 | case 'group': |
|
261 | 58 | $callback = $this->loadGroup($schema, $childNode); |
|
262 | 58 | break; |
|
263 | 58 | case 'complexType': |
|
264 | 58 | $callback = $this->loadComplexType($schema, $childNode); |
|
265 | 58 | break; |
|
266 | 58 | case 'simpleType': |
|
267 | 58 | $callback = $this->loadSimpleType($schema, $childNode); |
|
268 | 58 | break; |
|
269 | } |
||
270 | |||
271 | 58 | if ($callback instanceof Closure) { |
|
272 | 58 | $functions[] = $callback; |
|
273 | } |
||
274 | 58 | } |
|
275 | ); |
||
276 | |||
277 | 58 | return $functions; |
|
278 | } |
||
279 | |||
280 | 58 | private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef |
|
281 | { |
||
282 | 58 | $ref = new GroupRef($referenced); |
|
283 | 58 | $ref->setDoc($this->getDocumentation($node)); |
|
284 | |||
285 | 58 | self::maybeSetMax($ref, $node); |
|
286 | 58 | self::maybeSetMin($ref, $node); |
|
287 | |||
288 | 58 | return $ref; |
|
289 | } |
||
290 | |||
291 | 58 | private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): void |
|
292 | { |
||
293 | 58 | if ($node->hasAttribute('maxOccurs')) { |
|
294 | 58 | $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int)$node->getAttribute('maxOccurs')); |
|
295 | } |
||
296 | 58 | } |
|
297 | |||
298 | 58 | private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): void |
|
299 | { |
||
300 | 58 | if ($node->hasAttribute('minOccurs')) { |
|
301 | 58 | $ref->setMin((int)$node->getAttribute('minOccurs')); |
|
302 | 58 | if ($ref->getMin() > $ref->getMax()) { |
|
303 | 58 | $ref->setMax($ref->getMin()); |
|
304 | } |
||
305 | } |
||
306 | 58 | } |
|
307 | |||
308 | 58 | private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null): void |
|
309 | { |
||
310 | $max = |
||
311 | ( |
||
312 | 58 | (is_int($max) && (bool)$max) || |
|
313 | 58 | $node->getAttribute('maxOccurs') == 'unbounded' || |
|
314 | 58 | $node->getAttribute('maxOccurs') > 1 |
|
315 | ) |
||
316 | 58 | ? 2 |
|
317 | 58 | : null; |
|
318 | |||
319 | 58 | static::againstDOMNodeList( |
|
320 | 58 | $node, |
|
321 | 58 | function ( |
|
322 | DOMElement $node, |
||
323 | DOMElement $childNode |
||
324 | ) use ( |
||
325 | 58 | $elementContainer, |
|
326 | 58 | $max |
|
327 | ) { |
||
328 | 58 | $this->loadSequenceChildNode( |
|
329 | 58 | $elementContainer, |
|
330 | 58 | $node, |
|
331 | 58 | $childNode, |
|
332 | 58 | $max |
|
333 | ); |
||
334 | 58 | } |
|
335 | ); |
||
336 | 58 | } |
|
337 | |||
338 | /** |
||
339 | * @param int|null $max |
||
340 | */ |
||
341 | 58 | private function loadSequenceChildNode( |
|
342 | ElementContainer $elementContainer, |
||
343 | DOMElement $node, |
||
344 | DOMElement $childNode, |
||
345 | $max |
||
346 | ): void { |
||
347 | 58 | switch ($childNode->localName) { |
|
348 | 58 | case 'sequence': |
|
349 | 58 | case 'choice': |
|
350 | 58 | case 'all': |
|
351 | 58 | $this->loadSequence( |
|
352 | 58 | $elementContainer, |
|
353 | 58 | $childNode, |
|
354 | 58 | $max |
|
355 | ); |
||
356 | 58 | break; |
|
357 | 58 | case 'element': |
|
358 | 58 | $this->loadSequenceChildNodeLoadElement( |
|
359 | 58 | $elementContainer, |
|
360 | 58 | $node, |
|
361 | 58 | $childNode, |
|
362 | 58 | $max |
|
363 | ); |
||
364 | 58 | break; |
|
365 | 58 | case 'group': |
|
366 | 58 | $this->addGroupAsElement( |
|
367 | 58 | $elementContainer->getSchema(), |
|
368 | 58 | $node, |
|
369 | 58 | $childNode, |
|
370 | 58 | $elementContainer |
|
371 | ); |
||
372 | 58 | break; |
|
373 | } |
||
374 | 58 | } |
|
375 | |||
376 | /** |
||
377 | * @param int|null $max |
||
378 | */ |
||
379 | 58 | private function loadSequenceChildNodeLoadElement( |
|
380 | ElementContainer $elementContainer, |
||
381 | DOMElement $node, |
||
382 | DOMElement $childNode, |
||
383 | $max |
||
384 | ): void { |
||
385 | 58 | if ($childNode->hasAttribute('ref')) { |
|
386 | 58 | $element = new ElementRef( |
|
387 | 58 | $this->findElement($elementContainer->getSchema(), $node, $childNode->getAttribute('ref')) |
|
388 | ); |
||
389 | 58 | $element->setDoc($this->getDocumentation($childNode)); |
|
390 | |||
391 | 58 | self::maybeSetMax($element, $childNode); |
|
392 | 58 | self::maybeSetMin($element, $childNode); |
|
393 | 58 | if ($childNode->hasAttribute('nillable')) { |
|
394 | $element->setNil($childNode->getAttribute('nillable') == 'true'); |
||
395 | } |
||
396 | 58 | if ($childNode->hasAttribute('form')) { |
|
397 | 58 | $element->setQualified($childNode->getAttribute('form') == 'qualified'); |
|
398 | } |
||
399 | } else { |
||
400 | 58 | $element = $this->loadElement( |
|
401 | 58 | $elementContainer->getSchema(), |
|
402 | 58 | $childNode |
|
403 | ); |
||
404 | } |
||
405 | 58 | if ($max > 1) { |
|
406 | /* |
||
407 | * although one might think the typecast is not needed with $max being `? int $max` after passing > 1, |
||
408 | * phpstan@a4f89fa still thinks it's possibly null. |
||
409 | * see https://github.com/phpstan/phpstan/issues/577 for related issue |
||
410 | */ |
||
411 | 58 | $element->setMax((int)$max); |
|
412 | } |
||
413 | 58 | $elementContainer->addElement($element); |
|
414 | 58 | } |
|
415 | |||
416 | 58 | private function addGroupAsElement( |
|
417 | Schema $schema, |
||
418 | DOMElement $node, |
||
419 | DOMElement $childNode, |
||
420 | ElementContainer $elementContainer |
||
421 | ): void { |
||
422 | 58 | $referencedGroup = $this->findGroup( |
|
423 | 58 | $schema, |
|
424 | 58 | $node, |
|
425 | 58 | $childNode->getAttribute('ref') |
|
426 | ); |
||
427 | |||
428 | 58 | $group = $this->loadGroupRef($referencedGroup, $childNode); |
|
429 | 58 | $elementContainer->addElement($group); |
|
430 | 58 | } |
|
431 | |||
432 | 58 | private function loadGroup(Schema $schema, DOMElement $node): Closure |
|
433 | { |
||
434 | 58 | $group = new Group($schema, $node->getAttribute('name')); |
|
435 | 58 | $group->setDoc($this->getDocumentation($node)); |
|
436 | 58 | $groupOriginal = $group; |
|
437 | |||
438 | 58 | if ($node->hasAttribute('maxOccurs') || $node->hasAttribute('maxOccurs')) { |
|
439 | 1 | $group = new GroupRef($group); |
|
440 | |||
441 | 1 | if ($node->hasAttribute('maxOccurs')) { |
|
442 | 1 | self::maybeSetMax($group, $node); |
|
443 | } |
||
444 | 1 | if ($node->hasAttribute('minOccurs')) { |
|
445 | 1 | self::maybeSetMin($group, $node); |
|
446 | } |
||
447 | } |
||
448 | |||
449 | 58 | $schema->addGroup($group); |
|
450 | |||
451 | 58 | return function () use ($groupOriginal, $node) { |
|
452 | 58 | static::againstDOMNodeList( |
|
453 | 58 | $node, |
|
454 | 58 | function (DOMelement $node, DOMElement $childNode) use ($groupOriginal) { |
|
455 | 58 | switch ($childNode->localName) { |
|
456 | 58 | case 'sequence': |
|
457 | 58 | case 'choice': |
|
458 | 58 | case 'all': |
|
459 | 58 | $this->loadSequence($groupOriginal, $childNode); |
|
460 | 58 | break; |
|
461 | } |
||
462 | 58 | } |
|
463 | ); |
||
464 | 58 | }; |
|
465 | } |
||
466 | |||
467 | 58 | private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure |
|
468 | { |
||
469 | /** |
||
470 | * @var bool |
||
471 | */ |
||
472 | 58 | $isSimple = false; |
|
473 | |||
474 | 58 | static::againstDOMNodeList( |
|
475 | 58 | $node, |
|
476 | 58 | function ( |
|
477 | DOMElement $node, |
||
478 | DOMElement $childNode |
||
479 | ) use ( |
||
480 | 58 | &$isSimple |
|
481 | ) { |
||
482 | 58 | if ($isSimple) { |
|
483 | 1 | return; |
|
484 | } |
||
485 | 58 | if ($childNode->localName === 'simpleContent') { |
|
486 | 2 | $isSimple = true; |
|
487 | } |
||
488 | 58 | } |
|
489 | ); |
||
490 | |||
491 | 58 | $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name')); |
|
492 | |||
493 | 58 | $type->setDoc($this->getDocumentation($node)); |
|
494 | 58 | if ($node->getAttribute('name')) { |
|
495 | 58 | $schema->addType($type); |
|
496 | } |
||
497 | |||
498 | 58 | return function () use ($type, $node, $schema, $callback) { |
|
499 | 58 | $this->fillTypeNode($type, $node, true); |
|
500 | |||
501 | 58 | static::againstDOMNodeList( |
|
502 | 58 | $node, |
|
503 | 58 | function ( |
|
504 | DOMElement $node, |
||
505 | DOMElement $childNode |
||
506 | ) use ( |
||
507 | 58 | $schema, |
|
508 | 58 | $type |
|
509 | ) { |
||
510 | 58 | $this->loadComplexTypeFromChildNode( |
|
511 | 58 | $type, |
|
512 | 58 | $node, |
|
513 | 58 | $childNode, |
|
514 | 58 | $schema |
|
515 | ); |
||
516 | 58 | } |
|
517 | ); |
||
518 | |||
519 | 58 | if ($callback instanceof Closure) { |
|
520 | 58 | call_user_func($callback, $type); |
|
521 | } |
||
522 | 58 | }; |
|
523 | } |
||
524 | |||
525 | 58 | private function loadComplexTypeFromChildNode( |
|
526 | BaseComplexType $type, |
||
527 | DOMElement $node, |
||
528 | DOMElement $childNode, |
||
529 | Schema $schema |
||
530 | ): void { |
||
531 | 58 | switch ($childNode->localName) { |
|
532 | 58 | case 'sequence': |
|
533 | 58 | case 'choice': |
|
534 | 58 | case 'all': |
|
535 | 58 | if ($type instanceof ElementContainer) { |
|
536 | 58 | $this->loadSequence( |
|
537 | 58 | $type, |
|
538 | 58 | $childNode |
|
539 | ); |
||
540 | } |
||
541 | 58 | break; |
|
542 | 58 | case 'attribute': |
|
543 | 58 | $this->addAttributeFromAttributeOrRef( |
|
544 | 58 | $type, |
|
545 | 58 | $childNode, |
|
546 | 58 | $schema, |
|
547 | 58 | $node |
|
548 | ); |
||
549 | 58 | break; |
|
550 | 58 | case 'attributeGroup': |
|
551 | 2 | $this->findSomethingLikeAttributeGroup( |
|
552 | 2 | $schema, |
|
553 | 2 | $node, |
|
554 | 2 | $childNode, |
|
555 | 2 | $type |
|
556 | ); |
||
557 | 2 | break; |
|
558 | 58 | case 'group': |
|
559 | if ( |
||
560 | 1 | $type instanceof ComplexType |
|
561 | ) { |
||
562 | 1 | $this->addGroupAsElement( |
|
563 | 1 | $schema, |
|
564 | 1 | $node, |
|
565 | 1 | $childNode, |
|
566 | 1 | $type |
|
567 | ); |
||
568 | } |
||
569 | 1 | break; |
|
570 | } |
||
571 | 58 | } |
|
572 | |||
573 | 58 | private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure |
|
574 | { |
||
575 | 58 | $type = new SimpleType($schema, $node->getAttribute('name')); |
|
576 | 58 | $type->setDoc($this->getDocumentation($node)); |
|
577 | 58 | if ($node->getAttribute('name')) { |
|
578 | 58 | $schema->addType($type); |
|
579 | } |
||
580 | |||
581 | 58 | return function () use ($type, $node, $callback) { |
|
582 | 58 | $this->fillTypeNode($type, $node, true); |
|
583 | |||
584 | 58 | static::againstDOMNodeList( |
|
585 | 58 | $node, |
|
586 | 58 | function (DOMElement $node, DOMElement $childNode) use ($type) { |
|
587 | 58 | switch ($childNode->localName) { |
|
588 | 58 | case 'union': |
|
589 | 58 | $this->loadUnion($type, $childNode); |
|
590 | 58 | break; |
|
591 | 58 | case 'list': |
|
592 | 58 | $this->loadList($type, $childNode); |
|
593 | 58 | break; |
|
594 | } |
||
595 | 58 | } |
|
596 | ); |
||
597 | |||
598 | 58 | if ($callback instanceof Closure) { |
|
599 | 58 | call_user_func($callback, $type); |
|
600 | } |
||
601 | 58 | }; |
|
602 | } |
||
603 | |||
604 | 58 | private function loadList(SimpleType $type, DOMElement $node): void |
|
605 | { |
||
606 | 58 | if ($node->hasAttribute('itemType')) { |
|
607 | /** |
||
608 | * @var SimpleType |
||
609 | */ |
||
610 | 58 | $listType = $this->findSomeType($type, $node, 'itemType'); |
|
611 | 58 | $type->setList($listType); |
|
612 | } else { |
||
613 | 58 | self::againstDOMNodeList( |
|
614 | 58 | $node, |
|
615 | 58 | function ( |
|
616 | DOMElement $node, |
||
617 | DOMElement $childNode |
||
618 | ) use ( |
||
619 | 58 | $type |
|
620 | ) { |
||
621 | 58 | $this->loadTypeWithCallback( |
|
622 | 58 | $type->getSchema(), |
|
623 | 58 | $childNode, |
|
624 | 58 | function (SimpleType $list) use ($type) { |
|
625 | 58 | $type->setList($list); |
|
626 | 58 | } |
|
627 | ); |
||
628 | 58 | } |
|
629 | ); |
||
630 | } |
||
631 | 58 | } |
|
632 | |||
633 | 58 | private function findSomeType( |
|
634 | SchemaItem $fromThis, |
||
635 | DOMElement $node, |
||
636 | string $attributeName |
||
637 | ): SchemaItem { |
||
638 | 58 | return $this->findSomeTypeFromAttribute( |
|
639 | 58 | $fromThis, |
|
640 | 58 | $node, |
|
641 | 58 | $node->getAttribute($attributeName) |
|
642 | ); |
||
643 | } |
||
644 | |||
645 | 58 | private function findSomeTypeFromAttribute( |
|
646 | SchemaItem $fromThis, |
||
647 | DOMElement $node, |
||
648 | string $attributeName |
||
649 | ): SchemaItem { |
||
650 | 58 | $out = $this->findType( |
|
651 | 58 | $fromThis->getSchema(), |
|
652 | 58 | $node, |
|
653 | 58 | $attributeName |
|
654 | ); |
||
655 | |||
656 | 58 | return $out; |
|
657 | } |
||
658 | |||
659 | 58 | private function loadUnion(SimpleType $type, DOMElement $node): void |
|
660 | { |
||
661 | 58 | if ($node->hasAttribute('memberTypes')) { |
|
662 | 58 | $types = preg_split('/\s+/', $node->getAttribute('memberTypes')); |
|
663 | 58 | foreach ($types as $typeName) { |
|
664 | /** |
||
665 | * @var SimpleType |
||
666 | */ |
||
667 | 58 | $unionType = $this->findSomeTypeFromAttribute( |
|
668 | 58 | $type, |
|
669 | 58 | $node, |
|
670 | 58 | $typeName |
|
671 | ); |
||
672 | 58 | $type->addUnion($unionType); |
|
673 | } |
||
674 | } |
||
675 | 58 | self::againstDOMNodeList( |
|
676 | 58 | $node, |
|
677 | 58 | function ( |
|
678 | DOMElement $node, |
||
679 | DOMElement $childNode |
||
680 | ) use ( |
||
681 | 58 | $type |
|
682 | ) { |
||
683 | 58 | $this->loadTypeWithCallback( |
|
684 | 58 | $type->getSchema(), |
|
685 | 58 | $childNode, |
|
686 | 58 | function (SimpleType $unType) use ($type) { |
|
687 | 58 | $type->addUnion($unType); |
|
688 | 58 | } |
|
689 | ); |
||
690 | 58 | } |
|
691 | ); |
||
692 | 58 | } |
|
693 | |||
694 | 58 | private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void |
|
695 | { |
||
696 | 58 | if ($checkAbstract) { |
|
697 | 58 | $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1'); |
|
698 | } |
||
699 | |||
700 | 58 | static::againstDOMNodeList( |
|
701 | 58 | $node, |
|
702 | 58 | function (DOMElement $node, DOMElement $childNode) use ($type) { |
|
703 | 58 | switch ($childNode->localName) { |
|
704 | 58 | case 'restriction': |
|
705 | 58 | $this->loadRestriction($type, $childNode); |
|
706 | 58 | break; |
|
707 | 58 | case 'extension': |
|
708 | 58 | if ($type instanceof BaseComplexType) { |
|
709 | 58 | $this->loadExtension($type, $childNode); |
|
710 | } |
||
711 | 58 | break; |
|
712 | 58 | case 'simpleContent': |
|
713 | 58 | case 'complexContent': |
|
714 | 58 | $this->fillTypeNode($type, $childNode); |
|
715 | 58 | break; |
|
716 | } |
||
717 | 58 | } |
|
718 | ); |
||
719 | 58 | } |
|
720 | |||
721 | 58 | private function loadExtension(BaseComplexType $type, DOMElement $node): void |
|
722 | { |
||
723 | 58 | $extension = new Extension(); |
|
724 | 58 | $type->setExtension($extension); |
|
725 | |||
726 | 58 | if ($node->hasAttribute('base')) { |
|
727 | 58 | $this->findAndSetSomeBase( |
|
728 | 58 | $type, |
|
729 | 58 | $extension, |
|
730 | 58 | $node |
|
731 | ); |
||
732 | } |
||
733 | 58 | $this->loadExtensionChildNodes($type, $node); |
|
734 | 58 | } |
|
735 | |||
736 | 58 | private function findAndSetSomeBase( |
|
737 | Type $type, |
||
738 | Base $setBaseOnThis, |
||
739 | DOMElement $node |
||
740 | ): void { |
||
741 | /** |
||
742 | * @var Type |
||
743 | */ |
||
744 | 58 | $parent = $this->findSomeType($type, $node, 'base'); |
|
745 | 58 | $setBaseOnThis->setBase($parent); |
|
746 | 58 | } |
|
747 | |||
748 | 58 | private function loadExtensionChildNodes( |
|
749 | BaseComplexType $type, |
||
750 | DOMElement $node |
||
751 | ) { |
||
752 | 58 | static::againstDOMNodeList( |
|
753 | 58 | $node, |
|
754 | 58 | function ( |
|
755 | DOMElement $node, |
||
756 | DOMElement $childNode |
||
757 | ) use ( |
||
758 | 58 | $type |
|
759 | ) { |
||
760 | 58 | switch ($childNode->localName) { |
|
761 | 58 | case 'sequence': |
|
762 | 58 | case 'choice': |
|
763 | 58 | case 'all': |
|
764 | 58 | if ($type instanceof ElementContainer) { |
|
765 | 58 | $this->loadSequence( |
|
766 | 58 | $type, |
|
767 | 58 | $childNode |
|
768 | ); |
||
769 | } |
||
770 | 58 | break; |
|
771 | 58 | case 'attribute': |
|
772 | 58 | $this->addAttributeFromAttributeOrRef( |
|
773 | 58 | $type, |
|
774 | 58 | $childNode, |
|
775 | 58 | $type->getSchema(), |
|
776 | 58 | $node |
|
777 | ); |
||
778 | 58 | break; |
|
779 | 58 | case 'attributeGroup': |
|
780 | 58 | $this->findSomethingLikeAttributeGroup( |
|
781 | 58 | $type->getSchema(), |
|
782 | 58 | $node, |
|
783 | 58 | $childNode, |
|
784 | 58 | $type |
|
785 | ); |
||
786 | 58 | break; |
|
787 | } |
||
788 | 58 | } |
|
789 | ); |
||
790 | 58 | } |
|
791 | |||
792 | 58 | private function loadRestriction(Type $type, DOMElement $node): void |
|
793 | { |
||
794 | 58 | $restriction = new Restriction(); |
|
795 | 58 | $type->setRestriction($restriction); |
|
796 | 58 | if ($node->hasAttribute('base')) { |
|
797 | 58 | $this->findAndSetSomeBase($type, $restriction, $node); |
|
798 | } else { |
||
799 | 58 | self::againstDOMNodeList( |
|
800 | 58 | $node, |
|
801 | 58 | function ( |
|
802 | DOMElement $node, |
||
803 | DOMElement $childNode |
||
804 | ) use ( |
||
805 | 58 | $type, |
|
806 | 58 | $restriction |
|
807 | ) { |
||
808 | 58 | $this->loadTypeWithCallback( |
|
809 | 58 | $type->getSchema(), |
|
810 | 58 | $childNode, |
|
811 | 58 | function (Type $restType) use ($restriction) { |
|
812 | 58 | $restriction->setBase($restType); |
|
813 | 58 | } |
|
814 | ); |
||
815 | 58 | } |
|
816 | ); |
||
817 | } |
||
818 | 58 | self::againstDOMNodeList( |
|
819 | 58 | $node, |
|
820 | 58 | function ( |
|
821 | DOMElement $node, |
||
822 | DOMElement $childNode |
||
823 | ) use ( |
||
824 | 58 | $restriction |
|
825 | ) { |
||
826 | if ( |
||
827 | 58 | in_array( |
|
828 | 58 | $childNode->localName, |
|
829 | [ |
||
830 | 58 | 'enumeration', |
|
831 | 'pattern', |
||
832 | 'length', |
||
833 | 'minLength', |
||
834 | 'maxLength', |
||
835 | 'minInclusive', |
||
836 | 'maxInclusive', |
||
837 | 'minExclusive', |
||
838 | 'maxExclusive', |
||
839 | 'fractionDigits', |
||
840 | 'totalDigits', |
||
841 | 'whiteSpace', |
||
842 | ], |
||
843 | 58 | true |
|
844 | ) |
||
845 | ) { |
||
846 | 58 | $restriction->addCheck( |
|
847 | 58 | $childNode->localName, |
|
848 | [ |
||
849 | 58 | 'value' => $childNode->getAttribute('value'), |
|
850 | 58 | 'doc' => $this->getDocumentation($childNode), |
|
851 | ] |
||
852 | ); |
||
853 | } |
||
854 | 58 | } |
|
855 | ); |
||
856 | 58 | } |
|
857 | |||
858 | /** |
||
859 | * @return mixed[] |
||
860 | */ |
||
861 | 58 | private static function splitParts(DOMElement $node, string $typeName): array |
|
862 | { |
||
863 | 58 | $prefix = null; |
|
864 | 58 | $name = $typeName; |
|
865 | 58 | if (strpos($typeName, ':') !== false) { |
|
866 | 58 | list($prefix, $name) = explode(':', $typeName); |
|
867 | } |
||
868 | |||
869 | 58 | $namespace = $node->lookupNamespaceUri($prefix ?: ''); |
|
870 | |||
871 | return array( |
||
872 | 58 | $name, |
|
873 | 58 | $namespace, |
|
874 | 58 | $prefix, |
|
875 | ); |
||
876 | } |
||
877 | |||
878 | 58 | private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem |
|
879 | { |
||
880 | 58 | list($name, $namespace) = static::splitParts($node, $typeName); |
|
881 | |||
882 | 58 | $namespace = $namespace ?: $schema->getTargetNamespace(); |
|
883 | |||
884 | try { |
||
885 | 58 | return $schema->findAttribute($name, $namespace); |
|
886 | } catch (TypeNotFoundException $e) { |
||
887 | throw new TypeException( |
||
888 | sprintf( |
||
889 | "Can't find %s named {%s}#%s, at line %d in %s ", |
||
890 | 'attribute', |
||
891 | $namespace, |
||
892 | $name, |
||
893 | $node->getLineNo(), |
||
894 | $node->ownerDocument->documentURI |
||
895 | ), |
||
896 | 0, |
||
897 | $e |
||
898 | ); |
||
899 | } |
||
900 | } |
||
901 | |||
902 | 58 | private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup |
|
903 | { |
||
904 | 58 | list($name, $namespace) = static::splitParts($node, $typeName); |
|
905 | |||
906 | 58 | $namespace = $namespace ?: $schema->getTargetNamespace(); |
|
907 | |||
908 | try { |
||
909 | 58 | return $schema->findAttributeGroup($name, $namespace); |
|
910 | } catch (TypeNotFoundException $e) { |
||
911 | throw new TypeException( |
||
912 | sprintf( |
||
913 | "Can't find %s named {%s}#%s, at line %d in %s ", |
||
914 | 'attributegroup', |
||
915 | $namespace, |
||
916 | $name, |
||
917 | $node->getLineNo(), |
||
918 | $node->ownerDocument->documentURI |
||
919 | ), |
||
920 | 0, |
||
921 | $e |
||
922 | ); |
||
923 | } |
||
924 | } |
||
925 | |||
926 | 58 | private function findElement(Schema $schema, DOMElement $node, string $typeName): ElementDef |
|
927 | { |
||
928 | 58 | list($name, $namespace) = static::splitParts($node, $typeName); |
|
929 | |||
930 | 58 | $namespace = $namespace ?: $schema->getTargetNamespace(); |
|
931 | |||
932 | try { |
||
933 | 58 | return $schema->findElement($name, $namespace); |
|
934 | } catch (TypeNotFoundException $e) { |
||
935 | throw new TypeException( |
||
936 | sprintf( |
||
937 | "Can't find %s named {%s}#%s, at line %d in %s ", |
||
938 | 'element', |
||
939 | $namespace, |
||
940 | $name, |
||
941 | $node->getLineNo(), |
||
942 | $node->ownerDocument->documentURI |
||
943 | ), |
||
944 | 0, |
||
945 | $e |
||
946 | ); |
||
947 | } |
||
948 | } |
||
949 | |||
950 | 58 | private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group |
|
951 | { |
||
952 | 58 | list($name, $namespace) = static::splitParts($node, $typeName); |
|
953 | |||
954 | 58 | $namespace = $namespace ?: $schema->getTargetNamespace(); |
|
955 | |||
956 | try { |
||
957 | 58 | return $schema->findGroup($name, $namespace); |
|
958 | } catch (TypeNotFoundException $e) { |
||
959 | throw new TypeException( |
||
960 | sprintf( |
||
961 | "Can't find %s named {%s}#%s, at line %d in %s ", |
||
962 | 'group', |
||
963 | $namespace, |
||
964 | $name, |
||
965 | $node->getLineNo(), |
||
966 | $node->ownerDocument->documentURI |
||
967 | ), |
||
968 | 0, |
||
969 | $e |
||
970 | ); |
||
971 | } |
||
972 | } |
||
973 | |||
974 | 58 | private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem |
|
975 | { |
||
976 | 58 | list($name, $namespace) = static::splitParts($node, $typeName); |
|
977 | |||
978 | 58 | $namespace = $namespace ?: $schema->getTargetNamespace(); |
|
979 | |||
980 | try { |
||
981 | 58 | return $schema->findType($name, $namespace); |
|
982 | } catch (TypeNotFoundException $e) { |
||
983 | throw new TypeException( |
||
984 | sprintf( |
||
985 | "Can't find %s named {%s}#%s, at line %d in %s ", |
||
986 | 'type', |
||
987 | $namespace, |
||
988 | $name, |
||
989 | $node->getLineNo(), |
||
990 | $node->ownerDocument->documentURI |
||
991 | ), |
||
992 | 0, |
||
993 | $e |
||
994 | ); |
||
995 | } |
||
996 | } |
||
997 | |||
998 | /** |
||
999 | * @return Closure |
||
1000 | */ |
||
1001 | 58 | private function loadElementDef(Schema $schema, DOMElement $node): Closure |
|
1002 | { |
||
1003 | 58 | return $this->loadAttributeOrElementDef($schema, $node, false); |
|
1004 | } |
||
1005 | |||
1006 | 58 | private function fillItem(Item $element, DOMElement $node) |
|
1007 | { |
||
1008 | /** |
||
1009 | * @var bool |
||
1010 | */ |
||
1011 | 58 | $skip = false; |
|
1012 | 58 | static::againstDOMNodeList( |
|
1013 | 58 | $node, |
|
1014 | 58 | function ( |
|
1015 | DOMElement $node, |
||
1016 | DOMElement $childNode |
||
1017 | ) use ( |
||
1018 | 58 | $element, |
|
1019 | 58 | &$skip |
|
1020 | ) { |
||
1021 | if ( |
||
1022 | 58 | !$skip && |
|
1023 | 58 | in_array( |
|
1024 | 58 | $childNode->localName, |
|
1025 | [ |
||
1026 | 58 | 'complexType', |
|
1027 | 'simpleType', |
||
1028 | ], |
||
1029 | 58 | true |
|
1030 | ) |
||
1031 | ) { |
||
1032 | 58 | $this->loadTypeWithCallback( |
|
1033 | 58 | $element->getSchema(), |
|
1034 | 58 | $childNode, |
|
1035 | 58 | function (Type $type) use ($element) { |
|
1036 | 58 | $element->setType($type); |
|
1037 | 58 | } |
|
1038 | ); |
||
1039 | 58 | $skip = true; |
|
1040 | } |
||
1041 | 58 | } |
|
1042 | ); |
||
1043 | 58 | if ($skip) { |
|
1044 | 58 | return; |
|
1045 | } |
||
1046 | 58 | $this->fillItemNonLocalType($element, $node); |
|
1047 | 58 | } |
|
1048 | |||
1049 | 58 | private function fillItemNonLocalType(Item $element, DOMElement $node): void |
|
1050 | { |
||
1051 | 58 | if ($node->getAttribute('type')) { |
|
1052 | /** |
||
1053 | * @var Type |
||
1054 | */ |
||
1055 | 58 | $type = $this->findSomeType($element, $node, 'type'); |
|
1056 | } else { |
||
1057 | /** |
||
1058 | * @var Type |
||
1059 | */ |
||
1060 | 58 | $type = $this->findSomeTypeFromAttribute( |
|
1061 | 58 | $element, |
|
1062 | 58 | $node, |
|
1063 | 58 | ($node->lookupPrefix(self::XSD_NS) . ':anyType') |
|
1064 | ); |
||
1065 | } |
||
1066 | |||
1067 | 58 | $element->setType($type); |
|
1068 | 58 | } |
|
1069 | |||
1070 | 58 | private function loadImport( |
|
1071 | Schema $schema, |
||
1072 | DOMElement $node |
||
1073 | ): Closure { |
||
1074 | 58 | $namespace = $node->getAttribute('namespace'); |
|
1075 | 58 | $schemaLocation = $node->getAttribute('schemaLocation'); |
|
1076 | |||
1077 | // postpone schema loading |
||
1078 | 58 | if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) { |
|
1079 | 2 | return function () use ($schema, $namespace) { |
|
1080 | 2 | if (!empty($this->loadedSchemas[$namespace])) { |
|
1081 | 2 | foreach ($this->loadedSchemas[$namespace] as $s) { |
|
1082 | 2 | $schema->addSchema($s, $namespace); |
|
1083 | } |
||
1084 | } |
||
1085 | 2 | }; |
|
1086 | 58 | } elseif ($namespace && !$schemaLocation && !empty($this->loadedSchemas[$namespace])) { |
|
1087 | 1 | foreach ($this->loadedSchemas[$namespace] as $s) { |
|
1088 | 1 | $schema->addSchema($s, $namespace); |
|
1089 | } |
||
1090 | } |
||
1091 | |||
1092 | 58 | $base = urldecode($node->ownerDocument->documentURI); |
|
1093 | 58 | $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation')); |
|
1094 | |||
1095 | 58 | if (isset($this->loadedFiles[$file])) { |
|
1096 | 58 | $schema->addSchema($this->loadedFiles[$file]); |
|
1097 | |||
1098 | 58 | return function () { |
|
1099 | 58 | }; |
|
1100 | } |
||
1101 | |||
1102 | 2 | return $this->loadImportFresh($namespace, $schema, $file); |
|
1103 | } |
||
1104 | |||
1105 | 2 | private function createOrUseSchemaForNs( |
|
1106 | Schema $schema, |
||
1107 | string $namespace |
||
1108 | ): Schema { |
||
1109 | 2 | if (('' !== trim($namespace))) { |
|
1110 | 1 | $newSchema = new Schema(); |
|
1111 | 1 | $newSchema->addSchema($this->getGlobalSchema()); |
|
1112 | 1 | $schema->addSchema($newSchema); |
|
1113 | } else { |
||
1114 | 1 | $newSchema = $schema; |
|
1115 | } |
||
1116 | |||
1117 | 2 | return $newSchema; |
|
1118 | } |
||
1119 | |||
1120 | private function loadImportFresh( |
||
1121 | string $namespace, |
||
1122 | Schema $schema, |
||
1123 | string $file |
||
1124 | ): Closure { |
||
1125 | 2 | return function () use ($namespace, $schema, $file) { |
|
1126 | 2 | $dom = $this->getDOM( |
|
1127 | 2 | isset($this->knownLocationSchemas[$file]) |
|
1128 | 1 | ? $this->knownLocationSchemas[$file] |
|
1129 | 2 | : $file |
|
1130 | ); |
||
1131 | |||
1132 | 2 | $schemaNew = $this->createOrUseSchemaForNs($schema, $namespace); |
|
1133 | |||
1134 | 2 | $this->setLoadedFile($file, $schemaNew); |
|
1135 | |||
1136 | 2 | $callbacks = $this->schemaNode($schemaNew, $dom->documentElement, $schema); |
|
1137 | |||
1138 | 2 | foreach ($callbacks as $callback) { |
|
1139 | 2 | $callback(); |
|
1140 | } |
||
1141 | 2 | }; |
|
1142 | } |
||
1143 | |||
1144 | /** |
||
1145 | * @var Schema|null |
||
1146 | */ |
||
1147 | protected $globalSchema; |
||
1148 | |||
1149 | /** |
||
1150 | * @return Schema |
||
1151 | */ |
||
1152 | 58 | public function getGlobalSchema(): Schema |
|
1153 | { |
||
1154 | 58 | if (!($this->globalSchema instanceof Schema)) { |
|
1155 | 58 | $callbacks = array(); |
|
1156 | 58 | $globalSchemas = array(); |
|
1157 | /** |
||
1158 | * @var string $namespace |
||
1159 | */ |
||
1160 | 58 | foreach (self::$globalSchemaInfo as $namespace => $uri) { |
|
1161 | 58 | $this->setLoadedFile( |
|
1162 | 58 | $uri, |
|
1163 | 58 | $globalSchemas[$namespace] = $schema = new Schema() |
|
1164 | ); |
||
1165 | 58 | $this->setLoadedSchema($namespace, $schema); |
|
1166 | 58 | if ($namespace === self::XSD_NS) { |
|
1167 | 58 | $this->globalSchema = $schema; |
|
1168 | } |
||
1169 | 58 | $xml = $this->getDOM($this->knownLocationSchemas[$uri]); |
|
1170 | 58 | $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement)); |
|
1171 | } |
||
1172 | |||
1173 | 58 | $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anySimpleType')); |
|
1174 | 58 | $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anyType')); |
|
1175 | |||
1176 | 58 | $globalSchemas[(string)static::XML_NS]->addSchema( |
|
1177 | 58 | $globalSchemas[(string)static::XSD_NS], |
|
1178 | 58 | (string)static::XSD_NS |
|
1179 | ); |
||
1180 | 58 | $globalSchemas[(string)static::XSD_NS]->addSchema( |
|
1181 | 58 | $globalSchemas[(string)static::XML_NS], |
|
1182 | 58 | (string)static::XML_NS |
|
1183 | ); |
||
1184 | |||
1185 | /** |
||
1186 | * @var Closure |
||
1187 | */ |
||
1188 | 58 | foreach ($callbacks as $callback) { |
|
1189 | 58 | $callback(); |
|
1190 | } |
||
1191 | } |
||
1192 | |||
1193 | /** |
||
1194 | * @var Schema |
||
1195 | */ |
||
1196 | 58 | $out = $this->globalSchema; |
|
1197 | |||
1198 | 58 | if (!($out instanceof Schema)) { |
|
1199 | throw new TypeException('Globa schema not discoverd'); |
||
1200 | } |
||
1201 | |||
1202 | 58 | return $out; |
|
1203 | } |
||
1204 | |||
1205 | 1 | public function readNodes(array $nodes, string $file = null) |
|
1206 | { |
||
1207 | 1 | $rootSchema = new Schema(); |
|
1208 | 1 | $rootSchema->addSchema($this->getGlobalSchema()); |
|
1209 | |||
1210 | 1 | if ($file !== null) { |
|
1211 | 1 | $this->setLoadedFile($file, $rootSchema); |
|
1212 | } |
||
1213 | |||
1214 | 1 | $all = array(); |
|
1215 | 1 | foreach ($nodes as $k => $node) { |
|
1216 | 1 | if (($node instanceof \DOMElement) && $node->namespaceURI === self::XSD_NS && $node->localName == 'schema') { |
|
1217 | 1 | $holderSchema = new Schema(); |
|
1218 | 1 | $holderSchema->addSchema($this->getGlobalSchema()); |
|
1219 | |||
1220 | 1 | $this->setLoadedSchemaFromElement($node, $holderSchema); |
|
1221 | |||
1222 | 1 | $rootSchema->addSchema($holderSchema); |
|
1223 | |||
1224 | 1 | $callbacks = $this->schemaNode($holderSchema, $node); |
|
1225 | 1 | $all = array_merge($callbacks, $all); |
|
1226 | } |
||
1227 | } |
||
1228 | |||
1229 | 1 | foreach ($all as $callback) { |
|
1230 | 1 | call_user_func($callback); |
|
1231 | } |
||
1232 | |||
1233 | 1 | return $rootSchema; |
|
1234 | } |
||
1235 | |||
1236 | 57 | public function readNode(DOMElement $node, string $file = null): Schema |
|
1237 | { |
||
1238 | 57 | $rootSchema = new Schema(); |
|
1239 | 57 | $rootSchema->addSchema($this->getGlobalSchema()); |
|
1240 | |||
1241 | 57 | if ($file !== null) { |
|
1242 | 56 | $this->setLoadedFile($file, $rootSchema); |
|
1243 | } |
||
1244 | |||
1245 | 57 | $this->setLoadedSchemaFromElement($node, $rootSchema); |
|
1246 | |||
1247 | 57 | $callbacks = $this->schemaNode($rootSchema, $node); |
|
1248 | |||
1249 | 57 | foreach ($callbacks as $callback) { |
|
1250 | 51 | call_user_func($callback); |
|
1251 | } |
||
1252 | |||
1253 | 57 | return $rootSchema; |
|
1254 | } |
||
1255 | |||
1256 | /** |
||
1257 | * @throws IOException |
||
1258 | */ |
||
1259 | 56 | public function readString(string $content, string $file = 'schema.xsd'): Schema |
|
1260 | { |
||
1261 | 56 | $xml = new DOMDocument('1.0', 'UTF-8'); |
|
1262 | 56 | libxml_use_internal_errors(true); |
|
1263 | 56 | if (!@$xml->loadXML($content)) { |
|
1264 | 1 | throw new IOException("Can't load the schema", 0, $this->extractErrorMessage()); |
|
1265 | } |
||
1266 | 55 | libxml_use_internal_errors(false); |
|
1267 | 55 | $xml->documentURI = $file; |
|
1268 | |||
1269 | 55 | return $this->readNode($xml->documentElement, $file); |
|
1270 | } |
||
1271 | |||
1272 | /** |
||
1273 | * @throws IOException |
||
1274 | */ |
||
1275 | 2 | public function readFile(string $file): Schema |
|
1276 | { |
||
1277 | 2 | $xml = $this->getDOM($file); |
|
1278 | |||
1279 | 1 | return $this->readNode($xml->documentElement, $file); |
|
1280 | } |
||
1281 | |||
1282 | /** |
||
1283 | * @throws IOException |
||
1284 | */ |
||
1285 | 59 | private function getDOM(string $file): DOMDocument |
|
1286 | { |
||
1287 | 59 | $xml = new DOMDocument('1.0', 'UTF-8'); |
|
1288 | 59 | libxml_use_internal_errors(true); |
|
1289 | 59 | if (!@$xml->load($file)) { |
|
1290 | 1 | libxml_use_internal_errors(false); |
|
1291 | 1 | throw new IOException("Can't load the file '$file'", 0, $this->extractErrorMessage()); |
|
1292 | } |
||
1293 | 58 | libxml_use_internal_errors(false); |
|
1294 | |||
1295 | 58 | return $xml; |
|
1296 | } |
||
1297 | |||
1298 | 58 | private static function againstDOMNodeList( |
|
1299 | DOMElement $node, |
||
1300 | Closure $againstNodeList |
||
1301 | ) { |
||
1302 | 58 | $limit = $node->childNodes->length; |
|
1303 | 58 | for ($i = 0; $i < $limit; $i += 1) { |
|
1304 | /** |
||
1305 | * @var DOMNode |
||
1306 | */ |
||
1307 | 58 | $childNode = $node->childNodes->item($i); |
|
1308 | |||
1309 | 58 | if ($childNode instanceof DOMElement) { |
|
1310 | 58 | $againstNodeList( |
|
1311 | 58 | $node, |
|
1312 | 58 | $childNode |
|
1313 | ); |
||
1314 | } |
||
1315 | } |
||
1316 | 58 | } |
|
1317 | |||
1318 | 58 | private function loadTypeWithCallback( |
|
1319 | Schema $schema, |
||
1320 | DOMElement $childNode, |
||
1321 | Closure $callback |
||
1322 | ) { |
||
1323 | /** |
||
1324 | * @var Closure|null $func |
||
1325 | */ |
||
1326 | 58 | $func = null; |
|
1327 | |||
1328 | 58 | switch ($childNode->localName) { |
|
1329 | 58 | case 'complexType': |
|
1330 | 58 | $func = $this->loadComplexType($schema, $childNode, $callback); |
|
1331 | 58 | break; |
|
1332 | 58 | case 'simpleType': |
|
1333 | 58 | $func = $this->loadSimpleType($schema, $childNode, $callback); |
|
1334 | 58 | break; |
|
1335 | } |
||
1336 | |||
1337 | 58 | if ($func instanceof Closure) { |
|
1338 | 58 | call_user_func($func); |
|
1339 | } |
||
1340 | 58 | } |
|
1341 | |||
1342 | 58 | private function loadElement( |
|
1343 | Schema $schema, |
||
1344 | DOMElement $node |
||
1345 | ): Element { |
||
1346 | 58 | $element = new Element($schema, $node->getAttribute('name')); |
|
1347 | 58 | $element->setDoc($this->getDocumentation($node)); |
|
1348 | |||
1349 | 58 | $this->fillItem($element, $node); |
|
1350 | |||
1351 | 58 | self::maybeSetMax($element, $node); |
|
1352 | 58 | self::maybeSetMin($element, $node); |
|
1353 | |||
1354 | 58 | $xp = new \DOMXPath($node->ownerDocument); |
|
1355 | 58 | $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema'); |
|
1356 | |||
1357 | 58 | if ($xp->query('ancestor::xs:choice', $node)->length) { |
|
1358 | 58 | $element->setMin(0); |
|
1359 | } |
||
1360 | |||
1361 | 58 | if ($node->hasAttribute('nillable')) { |
|
1362 | 3 | $element->setNil($node->getAttribute('nillable') == 'true'); |
|
1363 | } |
||
1364 | 58 | if ($node->hasAttribute('form')) { |
|
1365 | 3 | $element->setQualified($node->getAttribute('form') == 'qualified'); |
|
1366 | } |
||
1367 | |||
1368 | 58 | return $element; |
|
1369 | } |
||
1370 | |||
1371 | 58 | private function addAttributeFromAttributeOrRef( |
|
1372 | BaseComplexType $type, |
||
1373 | DOMElement $childNode, |
||
1374 | Schema $schema, |
||
1375 | DOMElement $node |
||
1376 | ): void { |
||
1377 | 58 | $attribute = $this->getAttributeFromAttributeOrRef( |
|
1378 | 58 | $childNode, |
|
1379 | 58 | $schema, |
|
1380 | 58 | $node |
|
1381 | ); |
||
1382 | |||
1383 | 58 | $type->addAttribute($attribute); |
|
1384 | 58 | } |
|
1385 | |||
1386 | 58 | private function findSomethingLikeAttributeGroup( |
|
1387 | Schema $schema, |
||
1388 | DOMElement $node, |
||
1389 | DOMElement $childNode, |
||
1390 | AttributeContainer $addToThis |
||
1391 | ): void { |
||
1392 | 58 | $attribute = $this->findAttributeGroup($schema, $node, $childNode->getAttribute('ref')); |
|
1393 | 58 | $addToThis->addAttribute($attribute); |
|
1394 | 58 | } |
|
1395 | |||
1396 | 58 | private function setLoadedFile(string $key, Schema $schema): void |
|
1400 | |||
1401 | 58 | private function setLoadedSchemaFromElement(DOMElement $node, Schema $schema): void |
|
1402 | { |
||
1403 | 58 | if ($node->hasAttribute('targetNamespace')) { |
|
1404 | 58 | $this->setLoadedSchema($node->getAttribute('targetNamespace'), $schema); |
|
1405 | } |
||
1406 | 58 | } |
|
1407 | |||
1408 | 58 | private function setLoadedSchema(string $namespace, Schema $schema): void |
|
1409 | { |
||
1410 | 58 | if (!isset($this->loadedSchemas[$namespace])) { |
|
1411 | 58 | $this->loadedSchemas[$namespace] = array(); |
|
1412 | } |
||
1413 | 58 | if (!in_array($schema, $this->loadedSchemas[$namespace], true)) { |
|
1414 | 58 | $this->loadedSchemas[$namespace][] = $schema; |
|
1415 | } |
||
1416 | 58 | } |
|
1417 | |||
1418 | 58 | private function setSchemaThingsFromNode( |
|
1419 | Schema $schema, |
||
1420 | DOMElement $node, |
||
1421 | Schema $parent = null |
||
1422 | ): void { |
||
1423 | 58 | $schema->setDoc($this->getDocumentation($node)); |
|
1424 | |||
1425 | 58 | if ($node->hasAttribute('targetNamespace')) { |
|
1426 | 58 | $schema->setTargetNamespace($node->getAttribute('targetNamespace')); |
|
1427 | } elseif ($parent instanceof Schema) { |
||
1428 | $schema->setTargetNamespace($parent->getTargetNamespace()); |
||
1429 | } |
||
1430 | 58 | $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified'); |
|
1431 | 58 | $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified'); |
|
1432 | 58 | $schema->setDoc($this->getDocumentation($node)); |
|
1433 | 58 | } |
|
1434 | } |
||
1435 |
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: