Test Failed
Push — php-7.1 ( d00c2a...d2218e )
by SignpostMarv
10:23
created

SchemaReader::fillItemNonLocalType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1353
        $keys = $this->loadImportFreshKeys(/** @scrutinizer ignore-type */ $namespace, $file);
Loading history...
Bug introduced by
The call to GoetasWebservices\XML\XS...::loadImportFreshKeys() has too few arguments starting with file. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1353
        /** @scrutinizer ignore-call */ 
1354
        $keys = $this->loadImportFreshKeys($namespace, $file);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1354
1355
        if (
1356
            Schema::hasLoadedFile(...$keys)
1357
        ) {
1358
            $schema->addSchema(Schema::getLoadedFile(...$keys));
1359
1360
            return function (): void {
1361
            };
1362
        }
1363
1364
        return $this->loadImportFresh($namespace, $schema, $file);
1365
    }
1366
1367
    protected function loadImportFreshKeys(
1368
        SchemaReader $this,
1369
        string $namespace,
1370
        string $file
1371
    ): array {
1372
        $globalSchemaInfo = $this->getGlobalSchemaInfo();
1373
1374
        $keys = [];
1375
1376
        if (isset($globalSchemaInfo[$namespace])) {
1377
            $keys[] = $globalSchemaInfo[$namespace];
1378
        }
1379
1380
        $keys[] = $this->getNamespaceSpecificFileIndex(
1381
            $file,
1382
            $namespace
1383
        );
1384
1385
        $keys[] = $file;
1386
1387
        return $keys;
1388
    }
1389
1390
    protected function loadImportFreshCallbacksNewSchema(
1391
        string $namespace,
1392
        Schema $schema,
1393
        string $file
1394
    ): Schema {
1395
        /**
1396
         * @var Schema $newSchema
1397
         */
1398
        $newSchema = self::setLoadedFile(
0 ignored issues
show
Bug introduced by
The method setLoadedFile() does not exist on GoetasWebservices\XML\XSDReader\SchemaReader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1398
        /** @scrutinizer ignore-call */ 
1399
        $newSchema = self::setLoadedFile(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1399
            $file,
1400
            ($namespace ? new self() : $schema)
1401
        );
1402
1403
        if ($namespace) {
1404
            $newSchema->addSchema($this->getGlobalSchema());
1405
            $schema->addSchema($newSchema);
1406
        }
1407
1408
        return $newSchema;
1409
    }
1410
1411
    /**
1412
     * @return Closure[]
1413
     */
1414
    protected function loadImportFreshCallbacks(
1415
        string $namespace,
1416
        Schema $schema,
1417
        string $file
1418
    ): array {
1419
        /**
1420
         * @var string
1421
         */
1422
        $file = $file;
1423
1424
        return $this->schemaNode(
1425
            $this->loadImportFreshCallbacksNewSchema(
1426
                $namespace,
1427
                $schema,
1428
                $file
1429
            ),
1430
            $this->getDOM(
1431
                $this->hasKnownSchemaLocation($file)
1432
                    ? $this->getKnownSchemaLocation($file)
1433
                    : $file
1434
            )->documentElement,
1435
            $schema
1436
        );
1437
    }
1438
1439
    protected function loadImportFresh(
1440
        string $namespace,
1441
        Schema $schema,
1442
        string $file
1443
    ): Closure {
1444
        return function () use ($namespace, $schema, $file): void {
1445
            foreach (
1446
                $this->loadImportFreshCallbacks(
1447
                    $namespace,
1448
                    $schema,
1449
                    $file
1450
                ) as $callback
1451
            ) {
1452
                $callback();
1453
            }
1454
        };
1455
    }
1456
}
1457