Passed
Pull Request — master (#18)
by SignpostMarv
05:20
created

SchemaReader::loadComplexTypeFromChildNode()   D

Complexity

Conditions 9
Paths 11

Size

Total Lines 45
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 9.0945

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 17
cts 19
cp 0.8947
rs 4.909
c 0
b 0
f 0
cc 9
eloc 32
nc 11
nop 4
crap 9.0945
1
<?php
2
3
namespace GoetasWebservices\XML\XSDReader;
4
5
use Closure;
6
use DOMDocument;
7
use DOMElement;
8
use DOMNode;
9
use GoetasWebservices\XML\XSDReader\Documentation\DocumentationReader;
10
use GoetasWebservices\XML\XSDReader\Documentation\StandardDocumentationReader;
11
use GoetasWebservices\XML\XSDReader\Exception\IOException;
12
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
14
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
15
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
16
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
17
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
26
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
27
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
28
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
29
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
30
use GoetasWebservices\XML\XSDReader\Schema\Item;
31
use GoetasWebservices\XML\XSDReader\Schema\Schema;
32
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
34
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
35
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
36
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
37
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
38
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
39
40
class SchemaReader
41
{
42
    const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
43
44
    const XML_NS = 'http://www.w3.org/XML/1998/namespace';
45
46
    /**
47
     * @var DocumentationReader
48
     */
49
    private $documentationReader;
50
51
    /**
52
     * @var Schema[]
53
     */
54
    private $loadedFiles = array();
55 56
56
    /**
57 56
     * @var string[]
58 56
     */
59 56
    protected $knownLocationSchemas = [
60 56
        'http://www.w3.org/2001/xml.xsd' => (
61
            __DIR__.'/Resources/xml.xsd'
62 56
        ),
63 56
        'http://www.w3.org/2001/XMLSchema.xsd' => (
64 56
            __DIR__.'/Resources/XMLSchema.xsd'
65 56
        ),
66 56
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
67 56
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
68 56
        ),
69
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
70 56
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
71
        ),
72 56
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
73 56
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
74
        ),
75 47
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
76
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
77 47
        ),
78 47
    ];
79 47
80
    /**
81
     * @var string[]
82 47
     */
83 47
    protected static $globalSchemaInfo = array(
84 47
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
85 47
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
86 47
    );
87 47
88 47
    public function __construct(DocumentationReader $documentationReader = null)
89
    {
90 47
        if (null === $documentationReader) {
91 47
            $documentationReader = new StandardDocumentationReader();
92 47
        }
93
        $this->documentationReader = $documentationReader;
94 1
    }
95 1
96 1
    /**
97 47
     * @param string $remote
98 47
     * @param string $local
99 47
     */
100
    public function addKnownSchemaLocation($remote, $local)
101
    {
102 47
        $this->knownLocationSchemas[$remote] = $local;
103
    }
104 47
105 47
    /**
106 47
     * @return \Closure
107
     */
108 47
    private function loadAttributeGroup(
109 1
        Schema $schema,
110 1
        DOMElement $node
111 47
    ) {
112 1
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
113 1
        $attGroup->setDoc($this->getDocumentation($node));
114 47
        $schema->addAttributeGroup($attGroup);
115 47
116 47
        return function () use ($schema, $node, $attGroup) {
117
            SchemaReader::againstDOMNodeList(
118 47
                $node,
119
                function (
120
                    DOMElement $node,
121 47
                    DOMElement $childNode
122
                ) use (
123 47
                    $schema,
124
                    $attGroup
125 47
                ) {
126
                    switch ($childNode->localName) {
127
                        case 'attribute':
128 47
                            $attribute = $this->getAttributeFromAttributeOrRef(
129 47
                                $childNode,
130
                                $schema,
131
                                $node
132
                            );
133
                            $attGroup->addAttribute($attribute);
134
                            break;
135
                        case 'attributeGroup':
136
                            $this->findSomethingLikeAttributeGroup(
137 47
                                $schema,
138
                                $node,
139 47
                                $childNode,
140
                                $attGroup
141
                            );
142
                            break;
143
                    }
144
                }
145
            );
146
        };
147
    }
148
149 47
    /**
150
     * @return AttributeItem
151 47
     */
152
    private function getAttributeFromAttributeOrRef(
153 47
        DOMElement $childNode,
154 47
        Schema $schema,
155 47
        DOMElement $node
156
    ) {
157
        if ($childNode->hasAttribute('ref')) {
158 47
            /**
159 47
             * @var AttributeItem
160 47
             */
161 47
            $attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute('ref'));
162
        } else {
163 47
            /**
164 47
             * @var Attribute
165 47
             */
166 47
            $attribute = $this->loadAttribute($schema, $childNode);
167 47
        }
168 47
169 47
        return $attribute;
170 47
    }
171 47
172 47
    /**
173 47
     * @return Attribute
174 47
     */
175 47
    private function loadAttribute(
176 47
        Schema $schema,
177 47
        DOMElement $node
178 47
    ) {
179 47
        $attribute = new Attribute($schema, $node->getAttribute('name'));
180 47
        $attribute->setDoc($this->getDocumentation($node));
181 47
        $this->fillItem($attribute, $node);
182 47
183 47
        if ($node->hasAttribute('nillable')) {
184 47
            $attribute->setNil($node->getAttribute('nillable') == 'true');
185 47
        }
186 47
        if ($node->hasAttribute('form')) {
187 47
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
188 47
        }
189
        if ($node->hasAttribute('use')) {
190 47
            $attribute->setUse($node->getAttribute('use'));
191
        }
192
193 47
        return $attribute;
194
    }
195 47
196 47
    /**
197
     * @param bool $attributeDef
198 47
     *
199
     * @return Closure
200 47
     */
201 47
    private function loadAttributeOrElementDef(
202 47
        Schema $schema,
203 47
        DOMElement $node,
204 47
        $attributeDef
205 47
    ) {
206
        $name = $node->getAttribute('name');
207 47
        if ($attributeDef) {
208 47
            $attribute = new AttributeDef($schema, $name);
209
            $schema->addAttribute($attribute);
210 47
        } else {
211 47
            $attribute = new ElementDef($schema, $name);
212 47
            $schema->addElement($attribute);
213
        }
214 47
215 3
        return function () use ($attribute, $node) {
216 3
            $this->fillItem($attribute, $node);
217 47
        };
218 3
    }
219 3
220
    /**
221 47
     * @return Closure
222
     */
223
    private function loadAttributeDef(Schema $schema, DOMElement $node)
224 47
    {
225
        return $this->loadAttributeOrElementDef($schema, $node, true);
226 47
    }
227 47
228
    /**
229 47
     * @param DOMElement $node
230 47
     *
231 47
     * @return string
232 47
     */
233 47
    private function getDocumentation(DOMElement $node)
234 47
    {
235
        return $this->documentationReader->get($node);
236 47
    }
237
238
    /**
239 47
     * @param Schema     $schema
240
     * @param DOMElement $node
241 47
     * @param Schema     $parent
242 47
     *
243
     * @return Closure[]
244 47
     */
245 47
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
246 47
    {
247 47
        $this->setSchemaThingsFromNode($schema, $node, $parent);
248 47
        $functions = array();
249 47
250 47
        static::againstDOMNodeList(
251
            $node,
252
            function (
253 47
                DOMElement $node,
254
                DOMElement $childNode
255
            ) use (
256
                $schema,
257 47
                &$functions
258
            ) {
259
                $callback = null;
260 47
261
                switch ($childNode->localName) {
262 47
                    case 'attributeGroup':
263 47
                        $callback = $this->loadAttributeGroup($schema, $childNode);
264
                        break;
265 47
                    case 'include':
266
                    case 'import':
267
                        $callback = $this->loadImport($schema, $childNode);
268 47
                        break;
269
                    case 'element':
270
                        $callback = $this->loadElementDef($schema, $childNode);
271 47
                        break;
272
                    case 'attribute':
273
                        $callback = $this->loadAttributeDef($schema, $childNode);
274
                        break;
275 47
                    case 'group':
276
                        $callback = $this->loadGroup($schema, $childNode);
277
                        break;
278 47
                    case 'complexType':
279
                        $callback = $this->loadComplexType($schema, $childNode);
280 47
                        break;
281
                    case 'simpleType':
282 47
                        $callback = $this->loadSimpleType($schema, $childNode);
283 47
                        break;
284 47
                }
285 47
286 47
                if ($callback instanceof Closure) {
287 47
                    $functions[] = $callback;
288 47
                }
289 47
            }
290 47
        );
291 47
292 47
        return $functions;
293 47
    }
294 47
295
    /**
296 47
     * @return GroupRef
297 47
     */
298 47
    private function loadGroupRef(Group $referenced, DOMElement $node)
299 47
    {
300 47
        $ref = new GroupRef($referenced);
301 47
        $ref->setDoc($this->getDocumentation($node));
302 47
303
        self::maybeSetMax($ref, $node);
304 47
        self::maybeSetMin($ref, $node);
305 47
306 47
        return $ref;
307 47
    }
308 47
309 47
    /**
310
     * @return InterfaceSetMinMax
311 47
     */
312
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
313 47
    {
314 47
        if (
315
            $node->hasAttribute('maxOccurs')
316 47
        ) {
317
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
318
        }
319 47
320
        return $ref;
321
    }
322
323 47
    /**
324
     * @return InterfaceSetMinMax
325
     */
326 47
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
327 47
    {
328 47
        if ($node->hasAttribute('minOccurs')) {
329 47
            $ref->setMin((int) $node->getAttribute('minOccurs'));
330 47
        }
331 47
332 47
        return $ref;
333 47
    }
334 47
335 47
    /**
336
     * @param int|null $max
337
     */
338 47
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
339
    {
340 47
        $max =
341
        (
342 47
            (is_int($max) && (bool) $max) ||
343 47
            $node->getAttribute('maxOccurs') == 'unbounded' ||
344 2
            $node->getAttribute('maxOccurs') > 1
345 2
        )
346
            ? 2
347 47
            : null;
348
349 47
        static::againstDOMNodeList(
350
            $node,
351 47
            function (
352 47
                DOMElement $node,
353 47
                DOMElement $childNode
354 47
            ) use (
355
                $elementContainer,
356
                $max
357 47
            ) {
358
                $this->loadSequenceChildNode(
359 47
                    $elementContainer,
360 47
                    $node,
361 47
                    $childNode,
362 47
                    $max
363 47
                );
364 47
            }
365 47
        );
366 47
    }
367 47
368 47
    /**
369 47
     * @param int|null $max
370 47
     */
371 47 View Code Duplication
    private function loadSequenceChildNode(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
        ElementContainer $elementContainer,
373
        DOMElement $node,
374 47
        DOMElement $childNode,
375 47
        $max
376 47
    ) {
377 1
        switch ($childNode->localName) {
378 1
            case 'sequence':
379 1
            case 'choice':
380 1
            case 'all':
381 47
                $this->loadSequence(
382 2
                    $elementContainer,
383 2
                    $childNode,
384 2
                    $max
385 47
                );
386 47
                break;
387
            case 'element':
388 47
                $this->loadSequenceChildNodeLoadElement(
389 47
                    $elementContainer,
390 47
                    $node,
391 47
                    $childNode,
392
                    $max
393
                );
394 47
                break;
395
            case 'group':
396 47
                $this->addGroupAsElement(
397 47
                    $elementContainer->getSchema(),
398 47
                    $node,
399 47
                    $childNode,
400 47
                    $elementContainer
401
                );
402
                break;
403 47
        }
404
    }
405 47
406 47
    /**
407 47
     * @param int|null $max
408 47
     */
409 47
    private function loadSequenceChildNodeLoadElement(
410 47
        ElementContainer $elementContainer,
411 47
        DOMElement $node,
412 47
        DOMElement $childNode,
413 47
        $max
414 47
    ) {
415
        if ($childNode->hasAttribute('ref')) {
416 47
            /**
417 47
             * @var ElementDef $referencedElement
418 47
             */
419 47
            $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
420
            $element = new ElementRef($referencedElement);
421
            $element->setDoc($this->getDocumentation($childNode));
422 47
423
            self::maybeSetMax($element, $childNode);
424 47
            self::maybeSetMin($element, $childNode);
425 47
            if ($childNode->hasAttribute('nillable')) {
426 47
                $element->setNil($childNode->getAttribute('nillable') == 'true');
427
            }
428 47
            if ($childNode->hasAttribute('form')) {
429 47
                $element->setQualified($childNode->getAttribute('form') == 'qualified');
430
            }
431 47
        } else {
432 47
            $element = $this->loadElement(
433 47
                $elementContainer->getSchema(),
434 47
                $childNode
435 47
            );
436 47
        }
437 47
        if ($max > 1) {
438
            /*
439 47
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
440
            * phpstan@a4f89fa still thinks it's possibly null.
441 47
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
442
            */
443 47
            $element->setMax((int) $max);
444 47
        }
445 47
        $elementContainer->addElement($element);
446 47
    }
447 47
448 47
    private function addGroupAsElement(
449
        Schema $schema,
450 47
        DOMElement $node,
451 47
        DOMElement $childNode,
452
        ElementContainer $elementContainer
453 47
    ) {
454 47
        /**
455 47
         * @var Group
456 47
         */
457 47
        $referencedGroup = $this->findSomething(
458 47
            'findGroup',
459 47
            $schema,
460 47
            $node,
461
            $childNode->getAttribute('ref')
462 47
        );
463
464 47
        $group = $this->loadGroupRef($referencedGroup, $childNode);
465 47
        $elementContainer->addElement($group);
466 47
    }
467
468 47
    /**
469 47
     * @return Closure
470 47
     */
471 47
    private function loadGroup(Schema $schema, DOMElement $node)
472 47
    {
473 47
        $group = new Group($schema, $node->getAttribute('name'));
474 47
        $group->setDoc($this->getDocumentation($node));
475 47
476 47
        if ($node->hasAttribute('maxOccurs')) {
477 47
            /**
478 47
             * @var GroupRef
479 47
             */
480 47
            $group = self::maybeSetMax(new GroupRef($group), $node);
481 47
        }
482 47
        if ($node->hasAttribute('minOccurs')) {
483
            /**
484 47
             * @var GroupRef
485
             */
486 47
            $group = self::maybeSetMin(
487 47
                $group instanceof GroupRef ? $group : new GroupRef($group),
488
                $node
489 47
            );
490 47
        }
491 47
492 47
        $schema->addGroup($group);
493
494 47
        return function () use ($group, $node) {
495 47
            static::againstDOMNodeList(
496 47
                $node,
497 47
                function (DOMelement $node, DOMElement $childNode) use ($group) {
498 47
                    switch ($childNode->localName) {
499 47
                        case 'sequence':
500 47
                        case 'choice':
501 47
                        case 'all':
502 47
                            $this->loadSequence($group, $childNode);
503 47
                            break;
504 47
                    }
505 47
                }
506
            );
507 47
        };
508 47
    }
509 47
510 47
    /**
511 47
     * @param Closure|null $callback
512 47
     *
513 47
     * @return Closure
514 47
     */
515 47
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
516
    {
517 47
        /**
518
         * @var bool
519 47
         */
520 47
        $isSimple = false;
521 47
522 47
        static::againstDOMNodeList(
523 47
            $node,
524 47
            function (
525
                DOMElement $node,
526 47
                DOMElement $childNode
527 47
            ) use (
528
                &$isSimple
529 47
            ) {
530 47
                if ($isSimple) {
531 47
                    return;
532 47
                }
533 47
                if ($childNode->localName === 'simpleContent') {
534 47
                    $isSimple = true;
535 47
                }
536
            }
537 47
        );
538 47
539
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
540 47
541 47
        $type->setDoc($this->getDocumentation($node));
542 47
        if ($node->getAttribute('name')) {
543 47
            $schema->addType($type);
544 47
        }
545 47
546 47
        return function () use ($type, $node, $schema, $callback) {
547 47
            $this->fillTypeNode($type, $node, true);
548 47
549 47
            static::againstDOMNodeList(
550 47
                $node,
551 47
                function (
552 47
                    DOMElement $node,
553 47
                    DOMElement $childNode
554
                ) use (
555 47
                    $schema,
556 47
                    $type
557 47
                ) {
558 47
                    $this->loadComplexTypeFromChildNode(
559 47
                        $type,
560 47
                        $node,
561
                        $childNode,
562 47
                        $schema
563
                    );
564 47
                }
565 47
            );
566 47
567 47
            if ($callback) {
568 47
                call_user_func($callback, $type);
569 47
            }
570
        };
571 47
    }
572
573
    private function loadComplexTypeFromChildNode(
574 47
        BaseComplexType $type,
575 47
        DOMElement $node,
576 47
        DOMElement $childNode,
577 47
        Schema $schema
578
    ) {
579
        switch ($childNode->localName) {
580
            case 'sequence':
581
            case 'choice':
582
            case 'all':
583
                if ($type instanceof ElementContainer) {
584
                    $this->loadSequence(
585
                        $type,
586
                        $childNode
587
                    );
588
                }
589
                break;
590 47
            case 'attribute':
591
                $this->addAttributeFromAttributeOrRef(
592 47
                    $type,
593
                    $childNode,
594 47
                    $schema,
595
                    $node
596
                );
597 47
                break;
598
            case 'attributeGroup':
599
                $this->findSomethingLikeAttributeGroup(
600
                    $schema,
601
                    $node,
602
                    $childNode,
603 47
                    $type
604
                );
605 47
                break;
606 47
            case 'group':
607
                if (
608
                    $type instanceof ComplexType
609 47
                ) {
610 47
                    $this->addGroupAsElement(
611
                        $schema,
612
                        $node,
613 47
                        $childNode,
614
                        $type
615 47
                    );
616 47
                }
617 47
                break;
618 47
        }
619 47
    }
620 47
621 47
    /**
622 47
     * @param Closure|null $callback
623 47
     *
624
     * @return Closure
625 47
     */
626
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
627 47
    {
628 47
        $type = new SimpleType($schema, $node->getAttribute('name'));
629 47
        $type->setDoc($this->getDocumentation($node));
630 47
        if ($node->getAttribute('name')) {
631 47
            $schema->addType($type);
632 47
        }
633 47
634 47
        return function () use ($type, $node, $callback) {
635 47
            $this->fillTypeNode($type, $node, true);
636 47
637 47
            static::againstDOMNodeList(
638 47
                $node,
639 47
                function (DOMElement $node, DOMElement $childNode) use ($type) {
640 47
                    switch ($childNode->localName) {
641 47
                        case 'union':
642
                            $this->loadUnion($type, $childNode);
643
                            break;
644 47
                        case 'list':
645
                            $this->loadList($type, $childNode);
646 47
                            break;
647
                    }
648 47
                }
649
            );
650 47
651 47
            if ($callback) {
652 47
                call_user_func($callback, $type);
653 47
            }
654 47
        };
655 47
    }
656 47
657
    private function loadList(SimpleType $type, DOMElement $node)
658
    {
659 47
        if ($node->hasAttribute('itemType')) {
660 3
            /**
661 3
             * @var SimpleType
662 2
             */
663
            $listType = $this->findSomeType($type, $node, 'itemType');
664
            $type->setList($listType);
665 2
        } else {
666 1
            self::againstDOMNodeList(
667
                $node,
668
                function (
669
                    DOMElement $node,
670
                    DOMElement $childNode
671
                ) use (
672
                    $type
673 1
                ) {
674 1
                    $this->loadTypeWithCallback(
675 1
                        $type->getSchema(),
676
                        $childNode,
677
                        function (SimpleType $list) use ($type) {
678
                            $type->setList($list);
679
                        }
680 1
                    );
681
                }
682 1
            );
683
        }
684 1
    }
685
686
    /**
687
     * @param string $attributeName
688 1
     *
689 1
     * @return SchemaItem
690 1
     */
691 1
    private function findSomeType(
692 1
        SchemaItem $fromThis,
693
        DOMElement $node,
694
        $attributeName
695
    ) {
696
        return $this->findSomeTypeFromAttribute(
697
            $fromThis,
698
            $node,
699
            $node->getAttribute($attributeName)
700 47
        );
701
    }
702 47
703 47
    /**
704 47
     * @param string $attributeName
705 47
     *
706 47
     * @return SchemaItem
707 47
     */
708 47
    private function findSomeTypeFromAttribute(
709 47
        SchemaItem $fromThis,
710 47
        DOMElement $node,
711 47
        $attributeName
712 47
    ) {
713
        /**
714 47
         * @var SchemaItem
715 47
         */
716
        $out = $this->findSomething(
717 47
            'findType',
718 47
            $fromThis->getSchema(),
719
            $node,
720 47
            $attributeName
721 47
        );
722 47
723 47
        return $out;
724
    }
725 47
726
    private function loadUnion(SimpleType $type, DOMElement $node)
727
    {
728
        if ($node->hasAttribute('memberTypes')) {
729
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
730
            foreach ($types as $typeName) {
731
                /**
732
                 * @var SimpleType
733
                 */
734 47
                $unionType = $this->findSomeTypeFromAttribute(
735
                    $type,
736 47
                    $node,
737 47
                    $typeName
738
                );
739 47
                $type->addUnion($unionType);
740 47
            }
741
        }
742 47
        self::againstDOMNodeList(
743 41
            $node,
744 47
            function (
745
                DOMElement $node,
746 47
                DOMElement $childNode
747
            ) use (
748
                $type
749
            ) {
750
                $this->loadTypeWithCallback(
751
                    $type->getSchema(),
752
                    $childNode,
753
                    function (SimpleType $unType) use ($type) {
754
                        $type->addUnion($unType);
755
                    }
756
                );
757
            }
758
        );
759
    }
760 47
761
    /**
762 47
     * @param bool $checkAbstract
763
     */
764
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false)
765
    {
766
        if ($checkAbstract) {
767
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
768
        }
769
770
        static::againstDOMNodeList(
771
            $node,
772
            function (DOMElement $node, DOMElement $childNode) use ($type) {
773 46
                switch ($childNode->localName) {
774
                    case 'restriction':
775 46
                        $this->loadRestriction($type, $childNode);
776 46
                        break;
777
                    case 'extension':
778
                        if ($type instanceof BaseComplexType) {
779 46
                            $this->loadExtension($type, $childNode);
780
                        }
781 46
                        break;
782
                    case 'simpleContent':
783
                    case 'complexContent':
784
                        $this->fillTypeNode($type, $childNode);
785
                        break;
786
                }
787
            }
788
        );
789 1
    }
790
791 1
    private function loadExtension(BaseComplexType $type, DOMElement $node)
792
    {
793 1
        $extension = new Extension();
794
        $type->setExtension($extension);
795
796
        if ($node->hasAttribute('base')) {
797
            $this->findAndSetSomeBase(
798
                $type,
799
                $extension,
800
                $node
801
            );
802
        }
803 47
        $this->loadExtensionChildNodes($type, $node);
804
    }
805 47
806 47
    private function findAndSetSomeBase(
807
        Type $type,
808
        Base $setBaseOnThis,
809
        DOMElement $node
810 47
    ) {
811
        /**
812
         * @var Type
813
         */
814
        $parent = $this->findSomeType($type, $node, 'base');
815
        $setBaseOnThis->setBase($parent);
816
    }
817
818
    private function loadExtensionChildNodes(
819
        BaseComplexType $type,
820
        DOMElement $node
821
    ) {
822
        static::againstDOMNodeList(
823
            $node,
824 View Code Duplication
            function (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
825
                DOMElement $node,
826
                DOMElement $childNode
827
            ) use (
828
                $type
829
            ) {
830
                switch ($childNode->localName) {
831
                    case 'sequence':
832
                    case 'choice':
833
                    case 'all':
834
                        if ($type instanceof ElementContainer) {
835
                            $this->loadSequence(
836
                                $type,
837
                                $childNode
838
                            );
839
                        }
840
                        break;
841
                    case 'attribute':
842
                        $this->addAttributeFromAttributeOrRef(
843
                            $type,
844
                            $childNode,
845
                            $type->getSchema(),
846
                            $node
847
                        );
848
                        break;
849
                    case 'attributeGroup':
850
                        $this->findSomethingLikeAttributeGroup(
851
                            $type->getSchema(),
852
                            $node,
853
                            $childNode,
854
                            $type
855
                        );
856
                        break;
857
                }
858
            }
859
        );
860
    }
861
862
    private function loadRestriction(Type $type, DOMElement $node)
863
    {
864
        $restriction = new Restriction();
865
        $type->setRestriction($restriction);
866
        if ($node->hasAttribute('base')) {
867
            $this->findAndSetSomeBase($type, $restriction, $node);
868
        } else {
869
            self::againstDOMNodeList(
870
                $node,
871
                function (
872
                    DOMElement $node,
873
                    DOMElement $childNode
874
                ) use (
875
                    $type,
876
                    $restriction
877
                ) {
878
                    $this->loadTypeWithCallback(
879
                        $type->getSchema(),
880
                        $childNode,
881
                        function (Type $restType) use ($restriction) {
882
                            $restriction->setBase($restType);
883
                        }
884
                    );
885
                }
886
            );
887
        }
888
        self::againstDOMNodeList(
889
            $node,
890
            function (
891
                DOMElement $node,
892
                DOMElement $childNode
893
            ) use (
894
                $restriction
895
            ) {
896
                if (
897
                    in_array(
898
                        $childNode->localName,
899
                        [
900
                            'enumeration',
901
                            'pattern',
902
                            'length',
903
                            'minLength',
904
                            'maxLength',
905
                            'minInclusive',
906
                            'maxInclusive',
907
                            'minExclusive',
908
                            'maxExclusive',
909
                            'fractionDigits',
910
                            'totalDigits',
911
                            'whiteSpace',
912
                        ],
913
                        true
914
                    )
915
                ) {
916
                    $restriction->addCheck(
917
                        $childNode->localName,
918
                        [
919
                            'value' => $childNode->getAttribute('value'),
920
                            'doc' => $this->getDocumentation($childNode),
921
                        ]
922
                    );
923
                }
924
            }
925
        );
926
    }
927
928
    /**
929
     * @param string $typeName
930
     *
931
     * @return mixed[]
932
     */
933
    private static function splitParts(DOMElement $node, $typeName)
934
    {
935
        $prefix = null;
936
        $name = $typeName;
937
        if (strpos($typeName, ':') !== false) {
938
            list($prefix, $name) = explode(':', $typeName);
939
        }
940
941
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
942
943
        return array(
944
            $name,
945
            $namespace,
946
            $prefix,
947
        );
948
    }
949
950
    /**
951
     * @param string     $finder
952
     * @param Schema     $schema
953
     * @param DOMElement $node
954
     * @param string     $typeName
955
     *
956
     * @throws TypeException
957
     *
958
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
959
     */
960
    private function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
961
    {
962
        list($name, $namespace) = static::splitParts($node, $typeName);
963
964
        /**
965
         * @var string|null
966
         */
967
        $namespace = $namespace ?: $schema->getTargetNamespace();
968
969
        try {
970
            /**
971
             * @var ElementItem|Group|AttributeItem|AttributeGroup|Type
972
             */
973
            $out = $schema->$finder($name, $namespace);
974
975
            return $out;
976
        } catch (TypeNotFoundException $e) {
977
            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);
978
        }
979
    }
980
981
    /**
982
     * @return Closure
983
     */
984
    private function loadElementDef(Schema $schema, DOMElement $node)
985
    {
986
        return $this->loadAttributeOrElementDef($schema, $node, false);
987
    }
988
989
    private function fillItem(Item $element, DOMElement $node)
990
    {
991
        /**
992
         * @var bool
993
         */
994
        $skip = false;
995
        static::againstDOMNodeList(
996
            $node,
997
            function (
998
                DOMElement $node,
999
                DOMElement $childNode
1000
            ) use (
1001
                $element,
1002
                &$skip
1003
            ) {
1004
                if (
1005
                    !$skip &&
1006
                    in_array(
1007
                        $childNode->localName,
1008
                        [
1009
                            'complexType',
1010
                            'simpleType',
1011
                        ]
1012
                    )
1013
                ) {
1014
                    $this->loadTypeWithCallback(
1015
                        $element->getSchema(),
1016
                        $childNode,
1017
                        function (Type $type) use ($element) {
1018
                            $element->setType($type);
1019
                        }
1020
                    );
1021
                    $skip = true;
1022
                }
1023
            }
1024
        );
1025
        if ($skip) {
1026
            return;
1027
        }
1028
        $this->fillItemNonLocalType($element, $node);
1029
    }
1030
1031
    private function fillItemNonLocalType(Item $element, DOMElement $node)
1032
    {
1033
        if ($node->getAttribute('type')) {
1034
            /**
1035
             * @var Type
1036
             */
1037
            $type = $this->findSomeType($element, $node, 'type');
1038
        } else {
1039
            /**
1040
             * @var Type
1041
             */
1042
            $type = $this->findSomeTypeFromAttribute(
1043
                $element,
1044
                $node,
1045
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1046
            );
1047
        }
1048
1049
        $element->setType($type);
1050
    }
1051
1052
    /**
1053
     * @return Closure
1054
     */
1055
    private function loadImport(
1056
        Schema $schema,
1057
        DOMElement $node
1058
    ) {
1059
        $base = urldecode($node->ownerDocument->documentURI);
1060
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation'));
1061
1062
        $namespace = $node->getAttribute('namespace');
1063
1064
        $keys = $this->loadImportFreshKeys($namespace, $file);
1065
1066
        foreach ($keys as $key) {
1067
            if (isset($this->loadedFiles[$key])) {
1068
                $schema->addSchema($this->loadedFiles[$key]);
1069
1070
                return function () {
1071
                };
1072
            }
1073
        }
1074
1075
        return $this->loadImportFresh($namespace, $schema, $file);
1076
    }
1077
1078
    /**
1079
     * @param string $namespace
1080
     * @param string $file
1081
     *
1082
     * @return string[]
1083
     */
1084
    private function loadImportFreshKeys(
1085
        $namespace,
1086
        $file
1087
    ) {
1088
        $globalSchemaInfo = $this->getGlobalSchemaInfo();
1089
1090
        $keys = [];
1091
1092
        if (isset($globalSchemaInfo[$namespace])) {
1093
            $keys[] = $globalSchemaInfo[$namespace];
1094
        }
1095
1096
        $keys[] = $this->getNamespaceSpecificFileIndex(
1097
            $file,
1098
            $namespace
1099
        );
1100
1101
        $keys[] = $file;
1102
1103
        return $keys;
1104
    }
1105
1106
    /**
1107
     * @param string $namespace
1108
     * @param string $file
1109
     *
1110
     * @return Schema
1111
     */
1112
    private function loadImportFreshCallbacksNewSchema(
1113
        $namespace,
1114
        Schema $schema,
1115
        $file
1116
    ) {
1117
        /**
1118
         * @var Schema $newSchema
1119
         */
1120
        $newSchema = $this->setLoadedFile(
1121
            $file,
1122
            ($namespace ? new Schema() : $schema)
1123
        );
1124
1125
        if ($namespace) {
1126
            $newSchema->addSchema($this->getGlobalSchema());
1127
            $schema->addSchema($newSchema);
1128
        }
1129
1130
        return $newSchema;
1131
    }
1132
1133
    /**
1134
     * @param string $namespace
1135
     * @param string $file
1136
     *
1137
     * @return Closure[]
1138
     */
1139
    private function loadImportFreshCallbacks(
1140
        $namespace,
1141
        Schema $schema,
1142
        $file
1143
    ) {
1144
        /**
1145
         * @var string
1146
         */
1147
        $file = $file;
1148
1149
        return $this->schemaNode(
1150
            $this->loadImportFreshCallbacksNewSchema(
1151
                $namespace,
1152
                $schema,
1153
                $file
1154
            ),
1155
            $this->getDOM(
1156
                isset($this->knownLocationSchemas[$file])
1157
                    ? $this->knownLocationSchemas[$file]
1158
                    : $file
1159
            )->documentElement,
1160
            $schema
1161
        );
1162
    }
1163
1164
    /**
1165
     * @param string $namespace
1166
     * @param string $file
1167
     *
1168
     * @return Closure
1169
     */
1170
    private function loadImportFresh(
1171
        $namespace,
1172
        Schema $schema,
1173
        $file
1174
    ) {
1175
        return function () use ($namespace, $schema, $file) {
1176
            foreach (
1177
                $this->loadImportFreshCallbacks(
1178
                    $namespace,
1179
                    $schema,
1180
                    $file
1181
                ) as $callback
1182
            ) {
1183
                $callback();
1184
            }
1185
        };
1186
    }
1187
1188
    /**
1189
     * @var Schema|null
1190
     */
1191
    protected $globalSchema;
1192
1193
    /**
1194
     * @return string[]
1195
     */
1196
    public function getGlobalSchemaInfo()
1197
    {
1198
        return self::$globalSchemaInfo;
1199
    }
1200
1201
    /**
1202
     * @return Schema
1203
     */
1204
    public function getGlobalSchema()
1205
    {
1206
        if (!$this->globalSchema) {
1207
            $callbacks = array();
1208
            $globalSchemas = array();
1209
            /**
1210
            * @var string $namespace
1211
            */
1212
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1213
                $this->setLoadedFile(
1214
                    $uri,
1215
                    $globalSchemas[$namespace] = $schema = new Schema()
1216
                );
1217
                if ($namespace === self::XSD_NS) {
1218
                    $this->globalSchema = $schema;
1219
                }
1220
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1221
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1222
            }
1223
1224
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anySimpleType'));
1225
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anyType'));
1226
1227
            $globalSchemas[(string) static::XML_NS]->addSchema(
1228
                $globalSchemas[(string) static::XSD_NS],
1229
                (string) static::XSD_NS
1230
            );
1231
            $globalSchemas[(string) static::XSD_NS]->addSchema(
1232
                $globalSchemas[(string) static::XML_NS],
1233
                (string) static::XML_NS
1234
            );
1235
1236
            /**
1237
             * @var Closure
1238
             */
1239
            foreach ($callbacks as $callback) {
1240
                $callback();
1241
            }
1242
        }
1243
1244
        /**
1245
         * @var Schema
1246
         */
1247
        $out = $this->globalSchema;
1248
1249
        return $out;
1250
    }
1251
1252
    /**
1253
     * @param DOMElement $node
1254
     * @param string     $file
1255
     *
1256
     * @return Schema
1257
     */
1258
    private function readNode(DOMElement $node, $file = 'schema.xsd')
1259
    {
1260
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1261
        $this->setLoadedFile($fileKey, $rootSchema = new Schema());
1262
1263
        $rootSchema->addSchema($this->getGlobalSchema());
1264
        $callbacks = $this->schemaNode($rootSchema, $node);
1265
1266
        foreach ($callbacks as $callback) {
1267
            call_user_func($callback);
1268
        }
1269
1270
        return $rootSchema;
1271
    }
1272
1273
    /**
1274
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1275
     *
1276
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1277
     * file to distinguish between multiple schemas in a single file.
1278
     *
1279
     * @param string $file
1280
     * @param string $targetNamespace
1281
     *
1282
     * @return string
1283
     */
1284
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1285
    {
1286
        return $file.'#'.$targetNamespace;
1287
    }
1288
1289
    /**
1290
     * @param string $content
1291
     * @param string $file
1292
     *
1293
     * @return Schema
1294
     *
1295
     * @throws IOException
1296
     */
1297
    public function readString($content, $file = 'schema.xsd')
1298
    {
1299
        $xml = new DOMDocument('1.0', 'UTF-8');
1300
        if (!$xml->loadXML($content)) {
1301
            throw new IOException("Can't load the schema");
1302
        }
1303
        $xml->documentURI = $file;
1304
1305
        return $this->readNode($xml->documentElement, $file);
1306
    }
1307
1308
    /**
1309
     * @param string $file
1310
     *
1311
     * @return Schema
1312
     */
1313
    public function readFile($file)
1314
    {
1315
        $xml = $this->getDOM($file);
1316
1317
        return $this->readNode($xml->documentElement, $file);
1318
    }
1319
1320
    /**
1321
     * @param string $file
1322
     *
1323
     * @return DOMDocument
1324
     *
1325
     * @throws IOException
1326
     */
1327
    private function getDOM($file)
1328
    {
1329
        $xml = new DOMDocument('1.0', 'UTF-8');
1330
        if (!$xml->load($file)) {
1331
            throw new IOException("Can't load the file $file");
1332
        }
1333
1334
        return $xml;
1335
    }
1336
1337
    private static function againstDOMNodeList(
1338
        DOMElement $node,
1339
        Closure $againstNodeList
1340
    ) {
1341
        $limit = $node->childNodes->length;
1342
        for ($i = 0; $i < $limit; $i += 1) {
1343
            /**
1344
             * @var DOMNode
1345
             */
1346
            $childNode = $node->childNodes->item($i);
1347
1348
            if ($childNode instanceof DOMElement) {
1349
                $againstNodeList(
1350
                    $node,
1351
                    $childNode
1352
                );
1353
            }
1354
        }
1355
    }
1356
1357
    private function loadTypeWithCallback(
1358
        Schema $schema,
1359
        DOMElement $childNode,
1360
        Closure $callback
1361
    ) {
1362
        /**
1363
         * @var Closure|null $func
1364
         */
1365
        $func = null;
1366
1367
        switch ($childNode->localName) {
1368
            case 'complexType':
1369
                $func = $this->loadComplexType($schema, $childNode, $callback);
1370
                break;
1371
            case 'simpleType':
1372
                $func = $this->loadSimpleType($schema, $childNode, $callback);
1373
                break;
1374
        }
1375
1376
        if ($func instanceof Closure) {
1377
            call_user_func($func);
1378
        }
1379
    }
1380
1381
    /**
1382
     * @return Element
1383
     */
1384
    private function loadElement(
1385
        Schema $schema,
1386
        DOMElement $node
1387
    ) {
1388
        $element = new Element($schema, $node->getAttribute('name'));
1389
        $element->setDoc($this->getDocumentation($node));
1390
1391
        $this->fillItem($element, $node);
1392
1393
        self::maybeSetMax($element, $node);
1394
        self::maybeSetMin($element, $node);
1395
1396
        $xp = new \DOMXPath($node->ownerDocument);
1397
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
1398
1399
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1400
            $element->setMin(0);
1401
        }
1402
1403
        if ($node->hasAttribute('nillable')) {
1404
            $element->setNil($node->getAttribute('nillable') == 'true');
1405
        }
1406
        if ($node->hasAttribute('form')) {
1407
            $element->setQualified($node->getAttribute('form') == 'qualified');
1408
        }
1409
1410
        return $element;
1411
    }
1412
1413
    private function addAttributeFromAttributeOrRef(
1414
        BaseComplexType $type,
1415
        DOMElement $childNode,
1416
        Schema $schema,
1417
        DOMElement $node
1418
    ) {
1419
        $attribute = $this->getAttributeFromAttributeOrRef(
1420
            $childNode,
1421
            $schema,
1422
            $node
1423
        );
1424
1425
        $type->addAttribute($attribute);
1426
    }
1427
1428
    private function findSomethingLikeAttributeGroup(
1429
        Schema $schema,
1430
        DOMElement $node,
1431
        DOMElement $childNode,
1432
        AttributeContainer $addToThis
1433
    ) {
1434
        /**
1435
         * @var AttributeItem
1436
         */
1437
        $attribute = $this->findSomething('findAttributeGroup', $schema, $node, $childNode->getAttribute('ref'));
1438
        $addToThis->addAttribute($attribute);
1439
    }
1440
1441
    /**
1442
     * @param string $key
1443
     *
1444
     * @return Schema
1445
     */
1446
    private function setLoadedFile($key, Schema $schema)
1447
    {
1448
        $this->loadedFiles[$key] = $schema;
1449
1450
        return $schema;
1451
    }
1452
1453
    private function setSchemaThingsFromNode(
1454
        Schema $schema,
1455
        DOMElement $node,
1456
        Schema $parent = null
1457
    ) {
1458
        $schema->setDoc($this->getDocumentation($node));
1459
1460
        if ($node->hasAttribute('targetNamespace')) {
1461
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1462
        } elseif ($parent) {
1463
            $schema->setTargetNamespace($parent->getTargetNamespace());
1464
        }
1465
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1466
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1467
        $schema->setDoc($this->getDocumentation($node));
1468
    }
1469
}
1470