Passed
Push — static-analysis ( 07c60a...d8a1cf )
by SignpostMarv
05:20
created

SchemaReader::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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