Passed
Push — static-analysis ( 85afd6...0e0544 )
by SignpostMarv
03:23
created

AbstractSchemaReader::schemaNode()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 63
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 40
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 34
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 63
ccs 40
cts 40
cp 1
crap 2
rs 9.4347

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace GoetasWebservices\XML\XSDReader;
3
4
use Closure;
5
use DOMDocument;
6
use DOMElement;
7
use DOMNode;
8
use DOMNodeList;
9
use GoetasWebservices\XML\XSDReader\Exception\IOException;
10
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
11
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
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
abstract class AbstractSchemaReader
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 string[]
47
    */
48
    protected $knownLocationSchemas = [
49
        'http://www.w3.org/2001/xml.xsd' => (
50
            __DIR__ . '/Resources/xml.xsd'
51
        ),
52
        'http://www.w3.org/2001/XMLSchema.xsd' => (
53
            __DIR__ . '/Resources/XMLSchema.xsd'
54
        ),
55
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
56
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
57
        ),
58
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
59
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
60
        ),
61
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
62
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
63
        ),
64
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
65
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
66
        ),
67
    ];
68
69
    /**
70
    * @var string[]
71
    */
72
    protected static $globalSchemaInfo = array(
73
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
74
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
75
    );
76
77
    /**
78
    * @param string $remote
79
    * @param string $local
80
    */
81
    public function addKnownSchemaLocation($remote, $local)
82
    {
83
        $this->knownLocationSchemas[$remote] = $local;
84
    }
85
86
    /**
87
    * @param string $remote
88
    *
89
    * @return bool
90
    */
91 3
    public function hasKnownSchemaLocation($remote)
92
    {
93 3
        return isset($this->knownLocationSchemas[$remote]);
94
    }
95
96
    /**
97
    * @param string $remote
98
    *
99
    * @return string
100
    */
101 1
    public function getKnownSchemaLocation($remote)
102
    {
103 1
        return $this->knownLocationSchemas[$remote];
104
    }
105
106
    /**
107
    * @return Closure
108
    */
109
    abstract protected function loadAttributeGroup(Schema $schema, DOMElement $node);
110
111
    /**
112
    * @param bool $attributeDef
113
    *
114
    * @return Closure
115
    */
116
    abstract protected function loadAttributeOrElementDef(
117
        Schema $schema,
118
        DOMElement $node,
119
        $attributeDef
120
    );
121
122
    /**
123
    * @return Closure
124
    */
125
    abstract protected function loadAttributeDef(Schema $schema, DOMElement $node);
126
127
    /**
128
     * @param DOMElement $node
129
     * @return string
130
     */
131 135
    public static function getDocumentation(DOMElement $node)
132
    {
133 135
        $doc = '';
134 135
        static::againstDOMNodeList(
135 135
            $node,
136
            function (
137
                DOMElement $node,
138
                DOMElement $childNode
139
            ) use (
140 45
                & $doc
141
            ) {
142 135
                if ($childNode->localName == "annotation") {
143 135
                    $doc .= static::getDocumentation($childNode);
144 135
                } elseif ($childNode->localName == 'documentation') {
145 135
                    $doc .= $childNode->nodeValue;
146 45
                }
147 135
            }
148 45
        );
149 135
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
150 135
        return trim($doc);
151
    }
152
153
    /**
154
    * @param string[] $methods
155
    * @param string $key
156
    *
157
    * @return Closure|null
158
    */
159 135
    public function maybeCallMethod(
160
        array $methods,
161
        $key,
162
        DOMNode $childNode,
163
        ...$args
164
    ) {
165 135
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
166 135
            $method = $methods[$key];
167
168
            /**
169
            * @var Closure|null $append
170
            */
171 135
            $append = $this->$method(...$args);
172
173 135
            if ($append instanceof Closure) {
174 135
                return $append;
175
            }
176 45
        }
177 135
    }
178
179
    /**
180
     *
181
     * @param Schema $schema
182
     * @param DOMElement $node
183
     * @param Schema $parent
184
     * @return Closure[]
185
     */
186 135
    public function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
187
    {
188 135
        $schema->setSchemaThingsFromNode($node, $parent);
189 135
        $functions = array();
190
191
        $schemaReaderMethods = [
192 135
            'include' => (Schema::class . '::loadImport'),
193 45
            'import' => (Schema::class . '::loadImport'),
194 45
        ];
195
196
        $thisMethods = [
197 135
            'element' => [$this, 'loadElementDef'],
198 135
            'attribute' => [$this, 'loadAttributeDef'],
199 135
            'attributeGroup' => [$this, 'loadAttributeGroup'],
200 135
            'group' => [$this, 'loadGroup'],
201 135
            'complexType' => [$this, 'loadComplexType'],
202 135
            'simpleType' => [$this, 'loadSimpleType'],
203 45
        ];
204
205 135
        static::againstDOMNodeList(
206 135
            $node,
207
            function (
208
                DOMElement $node,
209
                DOMElement $childNode
210
            ) use (
211 135
                $schemaReaderMethods,
212 135
                $schema,
213 135
                $thisMethods,
214 45
                & $functions
215
            ) {
216
                /**
217
                * @var Closure|null $callback
218
                */
219 135
                $callback = $this->maybeCallCallableWithArgs(
220 135
                    $childNode,
221 135
                        [],
222 135
                        [],
223
                        [
224
                            [
225 135
                                $schemaReaderMethods,
226
                                [
227 135
                                    $this,
228 135
                                    $schema,
229 135
                                    $childNode,
230
                                ]
231 45
                            ],
232
                            [
233 135
                                $thisMethods,
234
                                [
235 135
                                    $schema,
236 90
                                    $childNode
237 45
                                ],
238 45
                            ],
239
                        ]
240 45
                );
241
242 135
                if ($callback instanceof Closure) {
243 135
                    $functions[] = $callback;
244 45
                }
245 135
            }
246 45
        );
247
248 135
        return $functions;
249
    }
250
251
    /**
252
    * @return InterfaceSetMinMax
253
    */
254 135
    public static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
255
    {
256
        if (
257 135
            $node->hasAttribute("maxOccurs")
258 45
        ) {
259 135
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
260 45
        }
261
262 135
        return $ref;
263
    }
264
265
    /**
266
    * @return InterfaceSetMinMax
267
    */
268 135
    public static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
269
    {
270 135
        if ($node->hasAttribute("minOccurs")) {
271 135
            $ref->setMin((int) $node->getAttribute("minOccurs"));
272 45
        }
273
274 135
        return $ref;
275
    }
276
277
    /**
278
    * @param int|null $max
279
    *
280
    * @return int|null
281
    */
282
    abstract protected static function loadSequenceNormaliseMax(DOMElement $node, $max);
283
284
    /**
285
    * @param int|null $max
286
    */
287
    abstract protected function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null);
288
289
    /**
290
    * @param int|null $max
291
    */
292
    abstract protected function loadSequenceChildNode(
293
        ElementContainer $elementContainer,
294
        DOMElement $node,
295
        DOMElement $childNode,
296
        $max
297
    );
298
299
    /**
300
    * @param mixed[][] $methods
301
    *
302
    * @return mixed
303
    */
304
    abstract protected function maybeCallCallableWithArgs(
305
        DOMElement $childNode,
306
        array $commonMethods = [],
307
        array $methods = [],
308
        array $commonArguments = []
309
    );
310
311
    /**
312
    * @param int|null $max
313
    */
314
    abstract protected function loadSequenceChildNodeLoadSequence(
315
        ElementContainer $elementContainer,
316
        DOMElement $childNode,
317
        $max
318
    );
319
320
    /**
321
    * @param int|null $max
322
    */
323
    abstract protected function loadSequenceChildNodeLoadElement(
324
        ElementContainer $elementContainer,
325
        DOMElement $node,
326
        DOMElement $childNode,
327
        $max
328
    );
329
330
    abstract protected function loadSequenceChildNodeLoadGroup(
331
        ElementContainer $elementContainer,
332
        DOMElement $node,
333
        DOMElement $childNode
334
    );
335
336
    abstract protected function addGroupAsElement(
337
        Schema $schema,
338
        DOMElement $node,
339
        DOMElement $childNode,
340
        ElementContainer $elementContainer
341
    );
342
343
    abstract protected function maybeLoadSequenceFromElementContainer(
344
        BaseComplexType $type,
345
        DOMElement $childNode
346
    );
347
348
    /**
349
    * @return Closure
350
    */
351
    abstract protected function loadGroup(Schema $schema, DOMElement $node);
352
353
    /**
354
    * @return BaseComplexType
355
    */
356
    abstract protected function loadComplexTypeBeforeCallbackCallback(
357
        Schema $schema,
358
        DOMElement $node
359
    );
360
361
    /**
362
    * @param Closure|null $callback
363
    *
364
    * @return Closure
365
    */
366
    abstract protected function loadComplexType(Schema $schema, DOMElement $node, $callback = null);
367
368
    /**
369
    * @param Closure|null $callback
370
    *
371
    * @return Closure
372
    */
373
    abstract protected function makeCallbackCallback(
374
        Type $type,
375
        DOMElement $node,
376
        Closure $callbackCallback,
377
        $callback = null
378
    );
379
380
    /**
381
    * @param Closure|null $callback
382
    */
383
    abstract protected function runCallbackAgainstDOMNodeList(
384
        Type $type,
385
        DOMElement $node,
386
        Closure $againstNodeList,
387
        $callback = null
388
    );
389
390
    abstract protected function loadComplexTypeFromChildNode(
391
        BaseComplexType $type,
392
        DOMElement $node,
393
        DOMElement $childNode,
394
        Schema $schema
395
    );
396
397
    /**
398
    * @param Closure|null $callback
399
    *
400
    * @return Closure
401
    */
402
    abstract protected function loadSimpleType(Schema $schema, DOMElement $node, $callback = null);
403
404
    abstract protected function loadList(SimpleType $type, DOMElement $node);
405
406
    /**
407
    * @param string $attributeName
408
    *
409
    * @return SchemaItem
410
    */
411
    abstract protected function findSomeType(
412
        SchemaItem $fromThis,
413
        DOMElement $node,
414
        $attributeName
415
    );
416
417
    /**
418
    * @param string $attributeName
419
    *
420
    * @return SchemaItem
421
    */
422
    abstract protected function findSomeTypeFromAttribute(
423
        SchemaItem $fromThis,
424
        DOMElement $node,
425
        $attributeName
426
    );
427
428
    abstract protected function loadUnion(SimpleType $type, DOMElement $node);
429
430
    /**
431
    * @param bool $checkAbstract
432
    */
433
    abstract protected function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false);
434
435
    abstract protected function loadExtensionChildNode(
436
        BaseComplexType $type,
437
        DOMElement $node,
438
        DOMElement $childNode
439
    );
440
441
    abstract protected function loadExtension(BaseComplexType $type, DOMElement $node);
442
443 135
    public function findAndSetSomeBase(
444
        Type $type,
445
        Base $setBaseOnThis,
446
        DOMElement $node
447
    ) {
448
        /**
449
        * @var Type $parent
450
        */
451 135
        $parent = $this->findSomeType($type, $node, 'base');
452 135
        $setBaseOnThis->setBase($parent);
453 135
    }
454
455
    abstract protected function maybeLoadExtensionFromBaseComplexType(
456
        Type $type,
457
        DOMElement $childNode
458
    );
459
460
    abstract protected function loadRestriction(Type $type, DOMElement $node);
461
462
    /**
463
    * @param string $typeName
464
    *
465
    * @return mixed[]
466
    */
467
    abstract protected static function splitParts(DOMElement $node, $typeName);
468
469
    /**
470
     *
471
     * @param string $finder
472
     * @param Schema $schema
473
     * @param DOMElement $node
474
     * @param string $typeName
475
     * @throws TypeException
476
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
477
     */
478 135
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
479
    {
480 135
        list ($name, $namespace) = static::splitParts($node, $typeName);
481
482
        /**
483
        * @var string|null $namespace
484
        */
485 135
        $namespace = $namespace ?: $schema->getTargetNamespace();
486
487
        try {
488
            /**
489
            * @var ElementItem|Group|AttributeItem|AttributeGroup|Type $out
490
            */
491 135
            $out = $schema->$finder($name, $namespace);
492
493 135
            return $out;
494
        } catch (TypeNotFoundException $e) {
495
            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);
496
        }
497
    }
498
499
    /**
500
    * @return Closure
501
    */
502
    abstract protected function loadElementDef(Schema $schema, DOMElement $node);
503
504 135
    public function fillItem(Item $element, DOMElement $node)
505
    {
506
        /**
507
        * @var bool $skip
508
        */
509 135
        $skip = false;
510 135
        static::againstDOMNodeList(
511 135
            $node,
512
            function (
513
                DOMElement $node,
514
                DOMElement $childNode
515
            ) use (
516 135
                $element,
517 45
                & $skip
518
            ) {
519
                if (
520 135
                    ! $skip &&
521 135
                    in_array(
522 135
                        $childNode->localName,
523
                        [
524 135
                            'complexType',
525 45
                            'simpleType',
526
                        ]
527 45
                    )
528 45
                ) {
529 135
                    Type::loadTypeWithCallback(
530 135
                        $this,
531 135
                        $element->getSchema(),
532 135
                        $childNode,
533 135
                        function (Type $type) use ($element) {
534 135
                            $element->setType($type);
535 135
                        }
536 45
                    );
537 135
                    $skip = true;
538 45
                }
539 135
            }
540 45
        );
541 135
        if ($skip) {
542 135
            return;
543
        }
544 135
        $this->fillItemNonLocalType($element, $node);
545 135
    }
546
547
    abstract protected function fillItemNonLocalType(Item $element, DOMElement $node);
548
549
    /**
550
    * @var Schema|null
551
    */
552
    protected $globalSchema;
553
554
    /**
555
    * @return Schema[]
556
    */
557 135
    protected function setupGlobalSchemas(array & $callbacks)
558
    {
559 135
        $globalSchemas = array();
560 135
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
561 135
            Schema::setLoadedFile(
562 135
                $uri,
563 135
                $globalSchemas[$namespace] = $schema = new Schema()
564 45
            );
565 135
            if ($namespace === self::XSD_NS) {
566 135
                $this->globalSchema = $schema;
567 45
            }
568 135
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
569 135
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
570 45
        }
571
572 135
        return $globalSchemas;
573
    }
574
575
    /**
576
    * @return string[]
577
    */
578 135
    public function getGlobalSchemaInfo()
579
    {
580 135
        return self::$globalSchemaInfo;
581
    }
582
583
    /**
584
     *
585
     * @return Schema
586
     */
587 135
    public function getGlobalSchema()
588
    {
589 135
        if (!$this->globalSchema) {
590 135
            $callbacks = array();
591 135
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
592
593 135
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anySimpleType"));
594 135
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anyType"));
595
596 135
            $globalSchemas[static::XML_NS]->addSchema(
597 135
                $globalSchemas[static::XSD_NS],
598 90
                (string) static::XSD_NS
599 45
            );
600 135
            $globalSchemas[static::XSD_NS]->addSchema(
601 135
                $globalSchemas[static::XML_NS],
602 90
                (string) static::XML_NS
603 45
            );
604
605
            /**
606
            * @var Closure $callback
607
            */
608 135
            foreach ($callbacks as $callback) {
609 135
                $callback();
610 45
            }
611 45
        }
612
613
        /**
614
        * @var Schema $out
615
        */
616 135
        $out = $this->globalSchema;
617
618 135
        return $out;
619
    }
620
621
    /**
622
     * @param DOMElement $node
623
     * @param string  $file
624
     *
625
     * @return Schema
626
     */
627 135
    public function readNode(DOMElement $node, $file = 'schema.xsd')
628
    {
629 135
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
630 135
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
631
632 135
        $rootSchema->addSchema($this->getGlobalSchema());
633 135
        $callbacks = $this->schemaNode($rootSchema, $node);
634
635 135
        foreach ($callbacks as $callback) {
636 117
            call_user_func($callback);
637 45
        }
638
639 135
        return $rootSchema;
640
    }
641
642
    /**
643
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
644
     *
645
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
646
     * file to distinguish between multiple schemas in a single file.
647
     *
648
     * @param string $file
649
     * @param string $targetNamespace
650
     *
651
     * @return string
652
     */
653 135
    public function getNamespaceSpecificFileIndex($file, $targetNamespace)
654
    {
655 135
        return $file . '#' . $targetNamespace;
656
    }
657
658
    /**
659
     * @param string $content
660
     * @param string $file
661
     *
662
     * @return Schema
663
     *
664
     * @throws IOException
665
     */
666 132
    public function readString($content, $file = 'schema.xsd')
667
    {
668 132
        $xml = new DOMDocument('1.0', 'UTF-8');
669 132
        if (!$xml->loadXML($content)) {
670
            throw new IOException("Can't load the schema");
671
        }
672 132
        $xml->documentURI = $file;
673
674 132
        return $this->readNode($xml->documentElement, $file);
675
    }
676
677
    /**
678
     * @param string $file
679
     *
680
     * @return Schema
681
     */
682 3
    public function readFile($file)
683
    {
684 3
        $xml = $this->getDOM($file);
685 3
        return $this->readNode($xml->documentElement, $file);
686
    }
687
688
    /**
689
     * @param string $file
690
     *
691
     * @return DOMDocument
692
     *
693
     * @throws IOException
694
     */
695 135
    public function getDOM($file)
696
    {
697 135
        $xml = new DOMDocument('1.0', 'UTF-8');
698 135
        if (!$xml->load($file)) {
699
            throw new IOException("Can't load the file $file");
700
        }
701 135
        return $xml;
702
    }
703
704 135
    public static function againstDOMNodeList(
705
        DOMElement $node,
706
        Closure $againstNodeList
707
    ) {
708 135
        $limit = $node->childNodes->length;
709 135
        for ($i = 0; $i < $limit; $i += 1) {
710
            /**
711
            * @var DOMNode $childNode
712
            */
713 135
            $childNode = $node->childNodes->item($i);
714
715 135
            if ($childNode instanceof DOMElement) {
716 135
                $againstNodeList(
717 135
                    $node,
718 90
                    $childNode
719 45
                );
720 45
            }
721 45
        }
722 135
    }
723
}
724