Passed
Push — static-analysis ( 87b144...2730da )
by SignpostMarv
05:52
created

SchemaReader   F

Complexity

Total Complexity 112

Size/Duplication

Total Lines 1065
Duplicated Lines 0 %

Test Coverage

Coverage 95.21%

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 1065
ccs 557
cts 585
cp 0.9521
rs 0.8526
wmc 112

44 Methods

Rating   Name   Duplication   Size   Complexity  
A readFile() 0 4 1
A runCallbackAgainstDOMNodeList() 0 19 4
A addGroupAsElement() 0 18 1
A maybeCallMethod() 0 13 4
B getGlobalSchema() 0 34 5
A loadGroup() 0 3 1
A loadAttributeDef() 0 3 1
A addKnownSchemaLocation() 0 3 1
A __construct() 0 8 1
B loadUnion() 0 25 3
A getDocumentation() 0 12 4
A makeCallbackCallback() 0 18 1
B loadImport() 0 29 5
B loadComplexType() 0 36 5
B loadSequenceNormaliseMax() 0 10 5
A fillTypeNode() 0 21 4
B loadImportFresh() 0 25 5
A findSomeType() 0 9 1
B loadSimpleType() 0 32 2
A maybeSetMin() 0 7 2
A loadRestriction() 0 3 1
A loadComplexTypeFromChildNode() 0 67 3
B loadSequenceChildNode() 0 57 5
A readNode() 0 13 3
A getNamespaceSpecificFileIndex() 0 3 1
A findAndSetSomeBase() 0 10 1
B fillItem() 0 25 3
A maybeSetMax() 0 9 3
A loadSequence() 0 11 3
A loadList() 0 18 2
A splitParts() 0 13 3
A loadElementDef() 0 3 1
A findSomeTypeFromAttribute() 0 16 1
A readString() 0 9 2
A findSomething() 0 10 3
A fillItemNonLocalType() 0 19 2
A setSchemaThingsFromNode() 0 15 3
A loadAttributeGroup() 0 3 1
A loadExtension() 0 58 4
A loadAttributeOrElementDef() 0 17 2
A maybeLoadSequenceFromElementContainer() 0 16 2
A getDOM() 0 7 2
B schemaNode() 0 31 3
A maybeLoadExtensionFromBaseComplexType() 0 18 2

How to fix   Complexity   

Complex Class

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.

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
2
namespace GoetasWebservices\XML\XSDReader;
3
4
use Closure;
5
use DOMDocument;
6
use DOMElement;
7
use DOMNode;
8
use GoetasWebservices\XML\XSDReader\Exception\IOException;
9
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
10
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
11
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
14
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
15
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
16
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
17
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
22
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
23
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
24
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
26
use GoetasWebservices\XML\XSDReader\Schema\Item;
27
use GoetasWebservices\XML\XSDReader\Schema\Schema;
28
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
29
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
30
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
34
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
35
use RuntimeException;
36
37
class SchemaReader
38
{
39
40
    const XSD_NS = "http://www.w3.org/2001/XMLSchema";
41
42
    const XML_NS = "http://www.w3.org/XML/1998/namespace";
43
44
    /**
45
    * @var string[]
46
    */
47
    private $knownLocationSchemas = array();
48
49
    /**
50
    * @var string[]
51
    */
52
    private static $globalSchemaInfo = array(
53
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
54
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
55
    );
56
57 162
    public function __construct()
58
    {
59 162
        $this->addKnownSchemaLocation('http://www.w3.org/2001/xml.xsd', __DIR__ . '/Resources/xml.xsd');
60 162
        $this->addKnownSchemaLocation('http://www.w3.org/2001/XMLSchema.xsd', __DIR__ . '/Resources/XMLSchema.xsd');
61 162
        $this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd');
62 162
        $this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd');
63 162
        $this->addKnownSchemaLocation('https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
64 162
        $this->addKnownSchemaLocation('http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
65 162
    }
66
67
    /**
68
    * @param string $remote
69
    * @param string $local
70
    */
71 162
    public function addKnownSchemaLocation($remote, $local)
72
    {
73 162
        $this->knownLocationSchemas[$remote] = $local;
74 162
    }
75
76
    /**
77
    * @return Closure
78
    */
79 135
    private function loadAttributeGroup(Schema $schema, DOMElement $node)
80
    {
81 135
        return AttributeGroup::loadAttributeGroup($this, $schema, $node);
82
    }
83
84
    /**
85
    * @param bool $attributeDef
86
    *
87
    * @return Closure
88
    */
89 135
    private function loadAttributeOrElementDef(
90
        Schema $schema,
91
        DOMElement $node,
92
        $attributeDef
93 1
    ) {
94 135
        $name = $node->getAttribute('name');
95 135
        if ($attributeDef) {
96 135
            $attribute = new AttributeDef($schema, $name);
97 135
            $schema->addAttribute($attribute);
98 45
        } else {
99 135
            $attribute = new ElementDef($schema, $name);
100 135
            $schema->addElement($attribute);
101
        }
102
103
104 90
        return function () use ($attribute, $node) {
105 135
            $this->fillItem($attribute, $node);
106 135
        };
107
    }
108
109
    /**
110
    * @return Closure
111
    */
112 135
    private function loadAttributeDef(Schema $schema, DOMElement $node)
113
    {
114 135
        return $this->loadAttributeOrElementDef($schema, $node, true);
115
    }
116
117
    /**
118
     * @param DOMElement $node
119
     * @return string
120
     */
121 135
    public static function getDocumentation(DOMElement $node)
122
    {
123 135
        $doc = '';
124 135
        foreach ($node->childNodes as $childNode) {
125 135
            if ($childNode->localName == "annotation") {
126 135
                $doc .= static::getDocumentation($childNode);
127 135
            } elseif ($childNode->localName == 'documentation') {
128 135
                        $doc .= ($childNode->nodeValue);
129 45
            }
130 45
        }
131 135
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
132 135
        return trim($doc);
133
    }
134
135 135
    private function setSchemaThingsFromNode(
136
        Schema $schema,
137
        DOMElement $node,
138
        Schema $parent = null
139
    ) {
140 135
        $schema->setDoc(static::getDocumentation($node));
141
142 135
        if ($node->hasAttribute("targetNamespace")) {
143 135
            $schema->setTargetNamespace($node->getAttribute("targetNamespace"));
144 45
        } elseif ($parent) {
145
            $schema->setTargetNamespace($parent->getTargetNamespace());
146
        }
147 135
        $schema->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified");
148 135
        $schema->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified");
149 135
        $schema->setDoc(static::getDocumentation($node));
150 135
    }
151
152
    /**
153
    * @param string $key
154
    *
155
    * @return Closure|null
156
    */
157 135
    public function maybeCallMethod(
158
        array $methods,
159
        $key,
160
        DOMNode $childNode,
161
        ...$args
162
    ) {
163 135
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
164 135
            $method = $methods[$key];
165
166 135
            $append = $this->$method(...$args);
167
168 135
            if ($append instanceof Closure) {
169 135
                return $append;
170
            }
171 45
        }
172 135
    }
173
174
    /**
175
     *
176
     * @param Schema $schema
177
     * @param DOMElement $node
178
     * @param Schema $parent
179
     * @return Closure[]
180
     */
181 135
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
182
    {
183 135
        $this->setSchemaThingsFromNode($schema, $node, $parent);
184 135
        $functions = array();
185
186 90
        static $methods = [
187
            'include' => 'loadImport',
188
            'import' => 'loadImport',
189
            'element' => 'loadElementDef',
190
            'attribute' => 'loadAttributeDef',
191
            'attributeGroup' => 'loadAttributeGroup',
192
            'group' => 'loadGroup',
193
            'complexType' => 'loadComplexType',
194
            'simpleType' => 'loadSimpleType',
195 45
        ];
196
197 135
        foreach ($node->childNodes as $childNode) {
198 135
            $callback = $this->maybeCallMethod(
199 135
                $methods,
200 135
                (string) $childNode->localName,
201 135
                $childNode,
202 135
                $schema,
203 90
                $childNode
204 45
            );
205
206 135
            if ($callback instanceof Closure) {
207 135
                $functions[] = $callback;
208 45
            }
209 45
        }
210
211 135
        return $functions;
212
    }
213
214
    /**
215
    * @return InterfaceSetMinMax
216
    */
217 135
    public static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
218
    {
219
        if (
220 135
            $node->hasAttribute("maxOccurs")
221 45
        ) {
222 135
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
223 45
        }
224
225 135
        return $ref;
226
    }
227
228
    /**
229
    * @return InterfaceSetMinMax
230
    */
231 135
    public static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
232
    {
233 135
        if ($node->hasAttribute("minOccurs")) {
234 135
            $ref->setMin((int) $node->getAttribute("minOccurs"));
235 45
        }
236
237 135
        return $ref;
238
    }
239
240
    /**
241
    * @param int|null $max
242
    *
243
    * @return int|null
244
    */
245 135
    private static function loadSequenceNormaliseMax(DOMElement $node, $max)
246
    {
247
        return
248
        (
249 135
            (is_int($max) && (bool) $max) ||
250 135
            $node->getAttribute("maxOccurs") == "unbounded" ||
251 135
            $node->getAttribute("maxOccurs") > 1
252 45
        )
253 90
            ? 2
254 135
            : null;
255
    }
256
257
    /**
258
    * @param int|null $max
259
    */
260 135
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
261
    {
262 135
        $max = static::loadSequenceNormaliseMax($node, $max);
263
264 135
        foreach ($node->childNodes as $childNode) {
265 135
            if ($childNode instanceof DOMElement) {
266 135
                $this->loadSequenceChildNode(
267 135
                    $elementContainer,
268 135
                    $node,
269 135
                    $childNode,
270 90
                    $max
271 45
                );
272 45
            }
273 45
        }
274 135
    }
275
276
    /**
277
    * @param int|null $max
278
    */
279 45
    private function loadSequenceChildNode(
280
        ElementContainer $elementContainer,
281
        DOMElement $node,
282
        DOMElement $childNode,
283
        $max
284
    ) {
285 90
        $loadSeq = function () use ($elementContainer, $childNode, $max) {
286 135
            $this->loadSequence($elementContainer, $childNode, $max);
287 135
        };
288
        $methods = [
289 135
            'choice' => $loadSeq,
290 135
            'sequence' => $loadSeq,
291 135
            'all' => $loadSeq,
292 90
            'element' => function () use (
293 135
                $elementContainer,
294 135
                $node,
295 135
                $childNode,
296 135
                $max
297
            ) {
298 135
                if ($childNode->hasAttribute("ref")) {
299
                    /**
300
                    * @var ElementDef $referencedElement
301
                    */
302 135
                    $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
303 135
                    $element = ElementRef::loadElementRef(
304 135
                        $referencedElement,
305 90
                        $childNode
306 45
                    );
307 45
                } else {
308 135
                    $element = Element::loadElement(
309 135
                        $this,
310 135
                        $elementContainer->getSchema(),
311 90
                        $childNode
312 45
                    );
313
                }
314 135
                if (is_int($max) && (bool) $max) {
315 135
                    $element->setMax($max);
316 45
                }
317 135
                $elementContainer->addElement($element);
318 135
            },
319 90
            'group' => function () use (
320 135
                $elementContainer,
321 135
                $node,
322 135
                $childNode
323
            ) {
324 135
                $this->addGroupAsElement(
325 135
                    $elementContainer->getSchema(),
326 135
                    $node,
327 135
                    $childNode,
328 90
                    $elementContainer
329 45
                );
330 135
            },
331 45
        ];
332
333 135
        if (isset($methods[$childNode->localName])) {
334 135
            $method = $methods[$childNode->localName];
335 135
            $method();
336 45
        }
337 135
    }
338
339 135
    private function addGroupAsElement(
340
        Schema $schema,
341
        DOMElement $node,
342
        DOMElement $childNode,
343
        ElementContainer $elementContainer
344
    ) {
345
        /**
346
        * @var Group $referencedGroup
347
        */
348 135
        $referencedGroup = $this->findSomething(
349 135
            'findGroup',
350 135
            $schema,
351 135
            $node,
352 135
            $childNode->getAttribute("ref")
353 45
        );
354
355 135
        $group = GroupRef::loadGroupRef($referencedGroup, $childNode);
356 135
        $elementContainer->addElement($group);
357 135
    }
358
359 135
    private function maybeLoadSequenceFromElementContainer(
360
        BaseComplexType $type,
361
        DOMElement $childNode
362
    ) {
363 135
        if (! ($type instanceof ElementContainer)) {
364
            throw new RuntimeException(
365
                '$type passed to ' .
366
                __FUNCTION__ .
367
                'expected to be an instance of ' .
368
                ElementContainer::class .
369
                ' when child node localName is "group", ' .
370
                get_class($type) .
371
                ' given.'
372
            );
373
        }
374 135
        $this->loadSequence($type, $childNode);
375 135
    }
376
377
    /**
378
    * @return Closure
379
    */
380 135
    private function loadGroup(Schema $schema, DOMElement $node)
381
    {
382 135
        return Group::loadGroup($this, $schema, $node);
383
    }
384
385
    /**
386
    * @param Closure|null $callback
387
    *
388
    * @return Closure
389
    */
390 135
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
391
    {
392 135
        $isSimple = false;
393
394 135
        foreach ($node->childNodes as $childNode) {
395 135
            if ($childNode->localName === "simpleContent") {
396 6
                $isSimple = true;
397 49
                break;
398
            }
399 45
        }
400
401 135
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
402
403 135
        $type->setDoc(static::getDocumentation($node));
404 135
        if ($node->getAttribute("name")) {
405 135
            $schema->addType($type);
406 45
        }
407
408 135
        return $this->makeCallbackCallback(
409 135
            $type,
410 135
            $node,
411 90
                function (
412
                    DOMElement $node,
413
                    DOMElement $childNode
414
                ) use(
415 135
                    $schema,
416 135
                    $type
417
                ) {
418 135
                    $this->loadComplexTypeFromChildNode(
419 135
                        $type,
420 135
                        $node,
421 135
                        $childNode,
422 90
                        $schema
423 45
                    );
424 135
                },
425 90
            $callback
426 45
        );
427
    }
428
429
    /**
430
    * @param Closure|null $callback
431
    *
432
    * @return Closure
433
    */
434 45
    private function makeCallbackCallback(
435
        Type $type,
436
        DOMElement $node,
437
        Closure $callbackCallback,
438
        $callback = null
439
    ) {
440 90
        return function (
441
        ) use (
442 135
            $type,
443 135
            $node,
444 135
            $callbackCallback,
445 135
            $callback
446
        ) {
447 135
            $this->runCallbackAgainstDOMNodeList(
448 135
                $type,
449 135
                $node,
450 135
                $callbackCallback,
451 90
                $callback
452 45
            );
453 135
        };
454
    }
455
456
    /**
457
    * @param Closure|null $callback
458
    */
459 135
    private function runCallbackAgainstDOMNodeList(
460
        Type $type,
461
        DOMElement $node,
462
        Closure $againstNodeList,
463
        $callback = null
464
    ) {
465 135
        $this->fillTypeNode($type, $node, true);
466
467 135
        foreach ($node->childNodes as $childNode) {
468 135
            if ($childNode instanceof DOMElement) {
469 135
                $againstNodeList(
470 135
                    $node,
471 90
                    $childNode
472 45
                );
473 45
            }
474 45
        }
475
476 135
        if ($callback) {
477 135
            call_user_func($callback, $type);
478 45
        }
479 135
    }
480
481 45
    private function loadComplexTypeFromChildNode(
482
        BaseComplexType $type,
483
        DOMElement $node,
484
        DOMElement $childNode,
485
        Schema $schema
486
    ) {
487 90
        $maybeLoadSeq = function () use ($type, $childNode) {
488 135
            $this->maybeLoadSequenceFromElementContainer(
489 135
                $type,
490 90
                $childNode
491 45
            );
492 135
        };
493
        $methods = [
494 135
            'sequence' => $maybeLoadSeq,
495 135
            'choice' => $maybeLoadSeq,
496 135
            'all' => $maybeLoadSeq,
497 90
            'attribute' => function () use (
498 135
                $childNode,
499 135
                $schema,
500 135
                $node,
501 135
                $type
502
            ) {
503 135
            $attribute = Attribute::getAttributeFromAttributeOrRef(
504 135
                $this,
505 135
                $childNode,
506 135
                $schema,
507 90
                $node
508 45
            );
509
510 135
            $type->addAttribute($attribute);
511 135
            },
512 90
            'attributeGroup' => function() use (
513 6
                $schema,
514 6
                $node,
515 6
                $childNode,
516 6
                $type
517
            ) {
518 6
            AttributeGroup::findSomethingLikeThis(
519 6
                $this,
520 6
                $schema,
521 6
                $node,
522 6
                $childNode,
523 4
                $type
524 2
            );
525 135
            },
526 45
        ];
527
        if (
528 90
            $type instanceof ComplexType
529 45
        ) {
530 2
            $methods['group'] = function() use (
531 3
                $schema,
532 3
                $node,
533 3
                $childNode,
534 3
                $type
535
            ) {
536 3
                $this->addGroupAsElement(
537 3
                    $schema,
538 3
                    $node,
539 3
                    $childNode,
540 2
                    $type
541 1
                );
542 3
            };
543 45
        }
544
545 135
        if (isset($methods[$childNode->localName])) {
546 135
            $method = $methods[$childNode->localName];
547 135
            $method();
548 45
        }
549 135
    }
550
551
    /**
552
    * @param Closure|null $callback
553
    *
554
    * @return Closure
555
    */
556 135
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
557
    {
558 135
        $type = new SimpleType($schema, $node->getAttribute("name"));
559 135
        $type->setDoc(static::getDocumentation($node));
560 135
        if ($node->getAttribute("name")) {
561 135
            $schema->addType($type);
562 45
        }
563
564 90
        static $methods = [
565
            'union' => 'loadUnion',
566
            'list' => 'loadList',
567 45
        ];
568
569 135
        return $this->makeCallbackCallback(
570 135
            $type,
571 135
            $node,
572 90
                function (
573
                    DOMElement $node,
574
                    DOMElement $childNode
575
                ) use (
576 135
                    $methods,
577 135
                    $type
578
                ) {
579 135
                    $this->maybeCallMethod(
580 135
                        $methods,
581 135
                        $childNode->localName,
582 135
                        $childNode,
583 135
                        $type,
584 90
                        $childNode
585 45
                    );
586 135
                },
587 90
            $callback
588 45
        );
589
    }
590
591 135
    private function loadList(SimpleType $type, DOMElement $node)
592
    {
593 135
        if ($node->hasAttribute("itemType")) {
594
            /**
595
            * @var SimpleType $listType
596
            */
597 135
            $listType = $this->findSomeType($type, $node, 'itemType');
598 135
            $type->setList($listType);
599 45
        } else {
600 90
            $addCallback = function (SimpleType $list) use ($type) {
601 135
                $type->setList($list);
602 135
            };
603
604 135
            Type::loadTypeWithCallbackOnChildNodes(
605 135
                $this,
606 135
                $type->getSchema(),
607 135
                $node,
608 90
                $addCallback
609 45
            );
610
        }
611 135
    }
612
613
    /**
614
    * @param string $attributeName
615
    *
616
    * @return SchemaItem
617
    */
618 135
    private function findSomeType(
619
        SchemaItem $fromThis,
620
        DOMElement $node,
621
        $attributeName
622
    ) {
623 135
        return $this->findSomeTypeFromAttribute(
624 135
            $fromThis,
625 135
            $node,
626 135
            $node->getAttribute($attributeName)
627 45
        );
628
    }
629
630
    /**
631
    * @param string $attributeName
632
    *
633
    * @return SchemaItem
634
    */
635 135
    private function findSomeTypeFromAttribute(
636
        SchemaItem $fromThis,
637
        DOMElement $node,
638
        $attributeName
639
    ) {
640
        /**
641
        * @var SchemaItem $out
642
        */
643 135
        $out = $this->findSomething(
644 135
            'findType',
645 135
            $fromThis->getSchema(),
646 135
            $node,
647 90
            $attributeName
648 45
        );
649
650 135
        return $out;
651
    }
652
653 135
    private function loadUnion(SimpleType $type, DOMElement $node)
654
    {
655 135
        if ($node->hasAttribute("memberTypes")) {
656 135
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
657 135
            foreach ($types as $typeName) {
658
                /**
659
                * @var SimpleType $unionType
660
                */
661 135
                $unionType = $this->findSomeTypeFromAttribute(
662 135
                    $type,
663 135
                    $node,
664 90
                    $typeName
665 45
                );
666 135
                $type->addUnion($unionType);
667 45
            }
668 45
        }
669 90
        $addCallback = function (SimpleType $unType) use ($type) {
670 135
            $type->addUnion($unType);
671 135
        };
672
673 135
        Type::loadTypeWithCallbackOnChildNodes(
674 135
            $this,
675 135
            $type->getSchema(),
676 135
            $node,
677 90
            $addCallback
678 45
        );
679 135
    }
680
681
    /**
682
    * @param bool $checkAbstract
683
    */
684 135
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false)
685
    {
686
687 135
        if ($checkAbstract) {
688 135
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
689 45
        }
690
691 90
        static $methods = [
692
            'restriction' => 'loadRestriction',
693
            'extension' => 'maybeLoadExtensionFromBaseComplexType',
694
            'simpleContent' => 'fillTypeNode',
695
            'complexContent' => 'fillTypeNode',
696 45
        ];
697
698 135
        foreach ($node->childNodes as $childNode) {
699 135
            $this->maybeCallMethod(
700 135
                $methods,
701 135
                (string) $childNode->localName,
702 135
                $childNode,
703 135
                $type,
704 90
                $childNode
705 45
            );
706 45
        }
707 135
    }
708
709 135
    private function loadExtension(BaseComplexType $type, DOMElement $node)
710
    {
711 135
        $extension = new Extension();
712 135
        $type->setExtension($extension);
713
714 135
        if ($node->hasAttribute("base")) {
715 135
            $this->findAndSetSomeBase(
716 135
                $type,
717 135
                $extension,
718 90
                $node
719 45
            );
720 45
        }
721
722 90
        $seqFromElement = function (DOMElement $childNode) use ($type) {
723 135
            $this->maybeLoadSequenceFromElementContainer(
724 135
                $type,
725 90
                $childNode
726 45
            );
727 135
        };
728
729
        $methods = [
730 135
            'sequence' => $seqFromElement,
731 135
            'choice' => $seqFromElement,
732 135
            'all' => $seqFromElement,
733 90
            'attribute' => function (
734
                DOMElement $childNode
735
            ) use (
736 135
                $node,
737 135
                $type
738
            ) {
739 135
                $attribute = Attribute::getAttributeFromAttributeOrRef(
740 135
                    $this,
741 135
                    $childNode,
742 135
                    $type->getSchema(),
743 90
                    $node
744 45
                );
745 135
                $type->addAttribute($attribute);
746 135
            },
747 90
            'attributeGroup' => function (
748
                DOMElement $childNode
749
            ) use (
750 135
                $node,
751 135
                $type
752
            ) {
753 135
                AttributeGroup::findSomethingLikeThis(
754 135
                    $this,
755 135
                    $type->getSchema(),
756 135
                    $node,
757 135
                    $childNode,
758 90
                    $type
759 45
                );
760 135
            },
761 45
        ];
762
763 135
        foreach ($node->childNodes as $childNode) {
764 135
            if (isset($methods[$childNode->localName])) {
765 135
                $method = $methods[$childNode->localName];
766 135
                $method($childNode);
767 45
            }
768 45
        }
769 135
    }
770
771 135
    public function findAndSetSomeBase(
772
        Type $type,
773
        Base $setBaseOnThis,
774
        DOMElement $node
775
    ) {
776
        /**
777
        * @var Type $parent
778
        */
779 135
        $parent = $this->findSomeType($type, $node, 'base');
780 135
        $setBaseOnThis->setBase($parent);
781 135
    }
782
783 135
    private function maybeLoadExtensionFromBaseComplexType(
784
        Type $type,
785
        DOMElement $childNode
786
    ) {
787 135
        if (! ($type instanceof BaseComplexType)) {
788
            throw new RuntimeException(
789
                'Argument 1 passed to ' .
790
                __METHOD__ .
791
                ' needs to be an instance of ' .
792
                BaseComplexType::class .
793
                ' when passed onto ' .
794
                static::class .
795
                '::loadExtension(), ' .
796
                get_class($type) .
797
                ' given.'
798
            );
799
        }
800 135
        $this->loadExtension($type, $childNode);
801 135
    }
802
803 135
    private function loadRestriction(Type $type, DOMElement $node)
804
    {
805 135
        Restriction::loadRestriction($this, $type, $node);
806 135
    }
807
808
    /**
809
    * @param string $typeName
810
    *
811
    * @return mixed[]
812
    */
813 135
    private static function splitParts(DOMElement $node, $typeName)
814
    {
815 135
        $prefix = null;
816 135
        $name = $typeName;
817 135
        if (strpos($typeName, ':') !== false) {
818 135
            list ($prefix, $name) = explode(':', $typeName);
819 45
        }
820
821 135
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
822
        return array(
823 135
            $name,
824 135
            $namespace,
825 90
            $prefix
826 45
        );
827
    }
828
829
    /**
830
     *
831
     * @param string $finder
832
     * @param Schema $schema
833
     * @param DOMElement $node
834
     * @param string $typeName
835
     * @throws TypeException
836
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
837
     */
838 135
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
839
    {
840 135
        list ($name, $namespace) = self::splitParts($node, $typeName);
841
842 135
        $namespace = $namespace ?: $schema->getTargetNamespace();
843
844
        try {
845 135
            return $schema->$finder($name, $namespace);
846
        } catch (TypeNotFoundException $e) {
847
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", strtolower(substr($finder, 4)), $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
848
        }
849
    }
850
851
    /**
852
    * @return Closure
853
    */
854 135
    private function loadElementDef(Schema $schema, DOMElement $node)
855
    {
856 135
        return $this->loadAttributeOrElementDef($schema, $node, false);
857
    }
858
859 135
    public function fillItem(Item $element, DOMElement $node)
860
    {
861 135
        foreach ($node->childNodes as $childNode) {
862
            if (
863 135
                in_array(
864 135
                    $childNode->localName,
865
                    [
866 135
                        'complexType',
867 45
                        'simpleType',
868
                    ]
869 45
                )
870 45
            ) {
871 135
                Type::loadTypeWithCallback(
872 135
                    $this,
873 135
                    $element->getSchema(),
874 135
                    $childNode,
875 90
                    function (Type $type) use ($element) {
876 135
                        $element->setType($type);
877 135
                    }
878 45
                );
879 135
                return;
880
            }
881 45
        }
882
883 135
        $this->fillItemNonLocalType($element, $node);
884 135
    }
885
886 135
    private function fillItemNonLocalType(Item $element, DOMElement $node)
887
    {
888 135
        if ($node->getAttribute("type")) {
889
            /**
890
            * @var Type $type
891
            */
892 135
            $type = $this->findSomeType($element, $node, 'type');
893 45
        } else {
894
            /**
895
            * @var Type $type
896
            */
897 135
            $type = $this->findSomeTypeFromAttribute(
898 135
                $element,
899 135
                $node,
900 135
                ($node->lookupPrefix(self::XSD_NS) . ':anyType')
901 45
            );
902
        }
903
904 135
        $element->setType($type);
905 135
    }
906
907
    /**
908
    * @return Closure
909
    */
910 135
    private function loadImport(Schema $schema, DOMElement $node)
911
    {
912 135
        $base = urldecode($node->ownerDocument->documentURI);
913 135
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
914
915 135
        $namespace = $node->getAttribute("namespace");
916
917
        if (
918
            (
919 135
                isset(self::$globalSchemaInfo[$namespace]) &&
920 135
                Schema::hasLoadedFile(
921 135
                    $loadedFilesKey = self::$globalSchemaInfo[$namespace]
922 45
                )
923 45
            ) ||
924 9
            Schema::hasLoadedFile(
925 9
                    $loadedFilesKey = $this->getNamespaceSpecificFileIndex(
926 9
                        $file,
927 6
                        $namespace
928 3
                    )
929 3
            ) ||
930 47
            Schema::hasLoadedFile($loadedFilesKey = $file)
931 45
        ) {
932 135
            $schema->addSchema(Schema::getLoadedFile($loadedFilesKey));
933
934 90
            return function() {
935 135
            };
936
        }
937
938 3
        return $this->loadImportFresh($schema, $node, $file, $namespace);
939
    }
940
941
    /**
942
    * @param string $file
943
    * @param string $namespace
944
    *
945
    * @return Closure
946
    */
947 3
    private function loadImportFresh(
948
        Schema $schema,
949
        DOMElement $node,
950
        $file,
951
        $namespace
952
    ) {
953 3
        if (! $namespace) {
954 3
            $newSchema = Schema::setLoadedFile($file, $schema);
955 1
        } else {
956
            $newSchema = Schema::setLoadedFile($file, new Schema());
957
            $newSchema->addSchema($this->getGlobalSchema());
958
        }
959
960 3
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
961
962 3
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
963
964 3
        if ($namespace) {
965
            $schema->addSchema($newSchema);
966
        }
967
968
969 3
        return function () use ($callbacks) {
970 3
            foreach ($callbacks as $callback) {
971 3
                $callback();
972 1
            }
973 3
        };
974
    }
975
976
    /**
977
    * @var Schema|null
978
    */
979
    private $globalSchema;
980
981
    /**
982
     *
983
     * @return Schema
984
     */
985 135
    public function getGlobalSchema()
986
    {
987 135
        if (!$this->globalSchema) {
988 135
            $callbacks = array();
989 135
            $globalSchemas = array();
990 135
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
991 135
                Schema::setLoadedFile(
992 135
                    $uri,
993 135
                    $globalSchemas[$namespace] = $schema = new Schema()
994 45
                );
995 135
                if ($namespace === self::XSD_NS) {
996 135
                    $this->globalSchema = $schema;
997 45
                }
998 135
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
999 135
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1000 45
            }
1001
1002 135
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
1003 135
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
1004
1005 135
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
1006 135
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
1007
1008 135
            foreach ($callbacks as $callback) {
1009 135
                $callback();
1010 45
            }
1011 45
        }
1012
1013
        /**
1014
        * @var Schema $out
1015
        */
1016 135
        $out = $this->globalSchema;
1017
1018 135
        return $out;
1019
    }
1020
1021
    /**
1022
     * @param DOMElement $node
1023
     * @param string  $file
1024
     *
1025
     * @return Schema
1026
     */
1027 135
    public function readNode(DOMElement $node, $file = 'schema.xsd')
1028
    {
1029 135
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1030 135
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
1031
1032 135
        $rootSchema->addSchema($this->getGlobalSchema());
1033 135
        $callbacks = $this->schemaNode($rootSchema, $node);
1034
1035 135
        foreach ($callbacks as $callback) {
1036 117
            call_user_func($callback);
1037 45
        }
1038
1039 135
        return $rootSchema;
1040
    }
1041
1042
    /**
1043
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1044
     *
1045
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1046
     * file to distinguish between multiple schemas in a single file.
1047
     *
1048
     * @param string $file
1049
     * @param string $targetNamespace
1050
     *
1051
     * @return string
1052
     */
1053 135
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1054
    {
1055 135
        return $file . '#' . $targetNamespace;
1056
    }
1057
1058
    /**
1059
     * @param string $content
1060
     * @param string $file
1061
     *
1062
     * @return Schema
1063
     *
1064
     * @throws IOException
1065
     */
1066 132
    public function readString($content, $file = 'schema.xsd')
1067
    {
1068 132
        $xml = new DOMDocument('1.0', 'UTF-8');
1069 132
        if (!$xml->loadXML($content)) {
1070
            throw new IOException("Can't load the schema");
1071
        }
1072 132
        $xml->documentURI = $file;
1073
1074 132
        return $this->readNode($xml->documentElement, $file);
1075
    }
1076
1077
    /**
1078
     * @param string $file
1079
     *
1080
     * @return Schema
1081
     */
1082 3
    public function readFile($file)
1083
    {
1084 3
        $xml = $this->getDOM($file);
1085 3
        return $this->readNode($xml->documentElement, $file);
1086
    }
1087
1088
    /**
1089
     * @param string $file
1090
     *
1091
     * @return DOMDocument
1092
     *
1093
     * @throws IOException
1094
     */
1095 135
    private function getDOM($file)
1096
    {
1097 135
        $xml = new DOMDocument('1.0', 'UTF-8');
1098 135
        if (!$xml->load($file)) {
1099
            throw new IOException("Can't load the file $file");
1100
        }
1101 135
        return $xml;
1102
    }
1103
}
1104