Passed
Push — static-analysis ( a54da0...03455e )
by SignpostMarv
01:32
created

SchemaReader   F

Complexity

Total Complexity 160

Size/Duplication

Total Lines 1161
Duplicated Lines 0.86 %

Importance

Changes 0
Metric Value
dl 10
loc 1161
rs 0.6314
c 0
b 0
f 0
wmc 160

48 Methods

Rating   Name   Duplication   Size   Complexity  
A addKnownSchemaLocation() 0 3 1
A __construct() 0 8 1
A loadAttribute() 0 16 4
A getAttributeFromAttributeOrRef() 0 18 2
B loadAttributeGroup() 0 26 4
A loadAttributeDef() 0 3 1
B loadElement() 0 24 4
B getDocumentation() 0 14 5
A maybeSetMin() 0 7 2
A loadAttributeRef() 0 15 4
A loadElementRef() 0 15 3
A loadGroupRef() 0 9 1
A maybeSetMax() 0 9 3
A setSchemaThingsFromNode() 0 15 3
A setDoc() 0 3 1
A loadAttributeOrElementDef() 0 17 2
A schemaNode() 0 16 3
A addGroupAsElement() 0 18 1
B loadGroup() 0 36 6
B loadUnion() 0 24 3
B loadTypeWithCallback() 0 24 4
C loadComplexType() 0 35 8
A findSomeType() 0 9 1
B loadSimpleType() 0 24 6
B loadComplexTypeFromChildNode() 0 45 6
C loadSequenceChildNode() 0 37 7
A loadTypeWithCallbackOnChildNodes() 0 7 2
B loadSequence() 0 17 7
A loadList() 0 17 2
A findSomeTypeFromAttribute() 0 16 1
A maybeLoadSequenceFromElementContainer() 0 16 2
A readFile() 0 4 1
B getGlobalSchema() 0 31 5
C loadImport() 0 43 11
A getClosuresFromChildNode() 4 20 2
B fillTypeNode() 4 19 5
B loadRestriction() 0 37 4
A readNode() 0 13 3
A getNamespaceSpecificFileIndex() 0 3 1
A findAndSetSomeBase() 0 10 1
B fillItem() 0 40 6
A splitParts() 0 13 3
A loadElementDef() 0 3 1
A findSomething() 0 10 3
A readString() 0 9 2
C loadExtension() 0 40 8
A getDOM() 0 7 2
A maybeLoadExtensionFromBaseComplexType() 0 18 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SchemaReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaReader, and based on these observations, apply Extract Interface, too.

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