Passed
Push — php-7.1 ( 280be0...7ba4a5 )
by SignpostMarv
02:07
created

AbstractSchemaReader   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 549
Duplicated Lines 0 %

Test Coverage

Coverage 93.13%

Importance

Changes 0
Metric Value
dl 0
loc 549
ccs 122
cts 131
cp 0.9313
rs 8.3157
c 0
b 0
f 0
wmc 43

19 Methods

Rating   Name   Duplication   Size   Complexity  
B fillItem() 0 25 3
A getGlobalSchemaInfo() 0 3 1
A addKnownSchemaLocation() 0 5 1
A getGlobalSchema() 0 23 3
A maybeCallMethod() 0 17 4
A getDocumentation() 0 12 4
A readFile() 0 4 1
A setupGlobalSchemas() 0 16 3
A getDOM() 0 7 2
A maybeSetMax() 0 11 3
A readNode() 0 15 3
A getNamespaceSpecificFileIndex() 0 5 1
A findAndSetSomeBase() 0 10 1
A readString() 0 11 2
A schemaNode() 0 54 4
A findSomething() 0 14 3
A maybeSetMin() 0 9 2
A hasKnownSchemaLocation() 0 3 1
A getKnownSchemaLocation() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractSchemaReader 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 AbstractSchemaReader, 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 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
    public function addKnownSchemaLocation(
78
        string $remote,
79
        string $local
80
    ) : void {
81
        $this->knownLocationSchemas[$remote] = $local;
82
    }
83
84 1
    public function hasKnownSchemaLocation(string $remote) : bool
85
    {
86 1
        return isset($this->knownLocationSchemas[$remote]);
87
    }
88
89
    public function getKnownSchemaLocation(string $remote) : string
90
    {
91
        return $this->knownLocationSchemas[$remote];
92
    }
93
94
    abstract protected function loadAttributeGroup(
95
        Schema $schema,
96
        DOMElement $node
97
    ) : Closure;
98
99
    abstract protected function loadAttributeOrElementDef(
100
        Schema $schema,
101
        DOMElement $node,
102
        bool $attributeDef
103
    ) : Closure;
104
105
    abstract protected function loadAttributeDef(
106
        Schema $schema,
107
        DOMElement $node
108
    ) : Closure;
109
110 45
    public static function getDocumentation(DOMElement $node) : string
111
    {
112 45
        $doc = '';
113 45
        foreach ($node->childNodes as $childNode) {
114 45
            if ($childNode->localName == "annotation") {
115 45
                $doc .= static::getDocumentation($childNode);
116 45
            } elseif ($childNode->localName == 'documentation') {
117 45
                $doc .= ($childNode->nodeValue);
118
            }
119
        }
120 45
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
121 45
        return trim($doc);
122
    }
123
124
    /**
125
    * @param mixed ...$args
126
    */
127 45
    public function maybeCallMethod(
128
        array $methods,
129
        string $key,
130
        DOMNode $childNode,
131
        ...$args
132
    ) : ? Closure {
133 45
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
134 45
            $method = $methods[$key];
135
136 45
            $append = $this->$method(...$args);
137
138 45
            if ($append instanceof Closure) {
139 45
                return $append;
140
            }
141
        }
142
143 45
        return null;
144
    }
145
146
    /**
147
     * @return Closure[]
148
     */
149 45
    public function schemaNode(
150
        Schema $schema,
151
        DOMElement $node,
152
        Schema $parent = null
153
    ) : array {
154 45
        $schema->setSchemaThingsFromNode($node, $parent);
155 45
        $functions = array();
156
157
        $schemaReaderMethods = [
158 45
            'include' => (Schema::class . '::loadImport'),
159
            'import' => (Schema::class . '::loadImport'),
160
        ];
161
162
        $thisMethods = [
163 45
            'element' => [$this, 'loadElementDef'],
164 45
            'attribute' => [$this, 'loadAttributeDef'],
165 45
            'attributeGroup' => [$this, 'loadAttributeGroup'],
166 45
            'group' => [$this, 'loadGroup'],
167 45
            'complexType' => [$this, 'loadComplexType'],
168 45
            'simpleType' => [$this, 'loadSimpleType'],
169
        ];
170
171 45
        foreach ($node->childNodes as $childNode) {
172 45
            if ($childNode instanceof DOMElement) {
173 45
                $callback = $this->maybeCallCallableWithArgs(
174 45
                    $childNode,
175 45
                        [],
176 45
                        [],
177
                        [
178
                            [
179 45
                                $schemaReaderMethods,
180
                                [
181 45
                                    $this,
182 45
                                    $schema,
183 45
                                    $childNode,
184
                                ]
185
                            ],
186
                            [
187 45
                                $thisMethods,
188
                                [
189 45
                                    $schema,
190 45
                                    $childNode
191
                                ],
192
                            ],
193
                        ]
194
                );
195
196 45
                if ($callback instanceof Closure) {
197 45
                    $functions[] = $callback;
198
                }
199
            }
200
        }
201
202 45
        return $functions;
203
    }
204
205 45
    public static function maybeSetMax(
206
        InterfaceSetMinMax $ref,
207
        DOMElement $node
208
    ) : InterfaceSetMinMax {
209
        if (
210 45
            $node->hasAttribute("maxOccurs")
211
        ) {
212 45
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
213
        }
214
215 45
        return $ref;
216
    }
217
218 45
    public static function maybeSetMin(
219
        InterfaceSetMinMax $ref,
220
        DOMElement $node
221
    ) : InterfaceSetMinMax {
222 45
        if ($node->hasAttribute("minOccurs")) {
223 45
            $ref->setMin((int) $node->getAttribute("minOccurs"));
224
        }
225
226 45
        return $ref;
227
    }
228
229
    abstract protected static function loadSequenceNormaliseMax(
230
        DOMElement $node,
231
        ? int $max
232
    ) : ? int;
233
234
    abstract protected function loadSequence(
235
        ElementContainer $elementContainer,
236
        DOMElement $node,
237
        int $max = null
238
    ) : void;
239
240
    abstract protected function loadSequenceChildNode(
241
        ElementContainer $elementContainer,
242
        DOMElement $node,
243
        DOMElement $childNode,
244
        ? int $max
245
    ) : void;
246
247
    /**
248
    * @param mixed[][] $methods
249
    *
250
    * @return mixed
251
    */
252
    abstract protected function maybeCallCallableWithArgs(
253
        DOMElement $childNode,
254
        array $commonMethods = [],
255
        array $methods = [],
256
        array $commonArguments = []
257
    );
258
259
    abstract protected function loadSequenceChildNodeLoadSequence(
260
        ElementContainer $elementContainer,
261
        DOMElement $childNode,
262
        ? int $max
263
    ) : void;
264
265
    abstract protected function loadSequenceChildNodeLoadElement(
266
        ElementContainer $elementContainer,
267
        DOMElement $node,
268
        DOMElement $childNode,
269
        ? int $max
270
    ) : void;
271
272
    abstract protected function loadSequenceChildNodeLoadGroup(
273
        ElementContainer $elementContainer,
274
        DOMElement $node,
275
        DOMElement $childNode
276
    ) : void;
277
278
    abstract protected function addGroupAsElement(
279
        Schema $schema,
280
        DOMElement $node,
281
        DOMElement $childNode,
282
        ElementContainer $elementContainer
283
    ) : void;
284
285
    abstract protected function maybeLoadSequenceFromElementContainer(
286
        BaseComplexType $type,
287
        DOMElement $childNode
288
    ) : void;
289
290
    abstract protected function loadGroup(
291
        Schema $schema,
292
        DOMElement $node
293
    ) : Closure;
294
295
    abstract protected function loadComplexTypeBeforeCallbackCallback(
296
        Schema $schema,
297
        DOMElement $node
298
    ) : BaseComplexType;
299
300
    abstract protected function loadComplexType(
301
        Schema $schema,
302
        DOMElement $node,
303
        Closure $callback = null
304
    ) : Closure;
305
306
    abstract protected function makeCallbackCallback(
307
        Type $type,
308
        DOMElement $node,
309
        Closure $callbackCallback,
310
        Closure $callback = null
311
    ) : Closure;
312
313
    abstract protected function runCallbackAgainstDOMNodeList(
314
        Type $type,
315
        DOMElement $node,
316
        Closure $againstNodeList,
317
        Closure $callback = null
318
    ) : void;
319
320
    abstract protected function loadComplexTypeFromChildNode(
321
        BaseComplexType $type,
322
        DOMElement $node,
323
        DOMElement $childNode,
324
        Schema $schema
325
    ) : void;
326
327
    abstract protected function loadSimpleType(
328
        Schema $schema,
329
        DOMElement $node,
330
        Closure $callback = null
331
    ) : Closure;
332
333
    abstract protected function loadList(
334
        SimpleType $type,
335
        DOMElement $node
336
    ) : void;
337
338
    abstract protected function findSomeType(
339
        SchemaItem $fromThis,
340
        DOMElement $node,
341
        string $attributeName
342
    ) : SchemaItem;
343
344
    abstract protected function findSomeTypeFromAttribute(
345
        SchemaItem $fromThis,
346
        DOMElement $node,
347
        string $attributeName
348
    ) : SchemaItem;
349
350
    abstract protected function loadUnion(
351
        SimpleType $type,
352
        DOMElement $node
353
    ) : void;
354
355
    abstract protected function fillTypeNode(
356
        Type $type,
357
        DOMElement $node,
358
        bool $checkAbstract = false
359
    ) : void;
360
361
    abstract protected function loadExtensionChildNode(
362
        BaseComplexType $type,
363
        DOMElement $node,
364
        DOMElement $childNode
365
    ) : void;
366
367
    abstract protected function loadExtension(
368
        BaseComplexType $type,
369
        DOMElement $node
370
    ) : void;
371
372
    abstract protected function loadExtensionChildNodes(
373
        BaseComplexType $type,
374
        DOMNodeList $childNodes,
375
        DOMElement $node
376
    ) : void;
377
378 45
    public function findAndSetSomeBase(
379
        Type $type,
380
        Base $setBaseOnThis,
381
        DOMElement $node
382
    ) : void {
383
        /**
384
        * @var Type $parent
385
        */
386 45
        $parent = $this->findSomeType($type, $node, 'base');
387 45
        $setBaseOnThis->setBase($parent);
388 45
    }
389
390
    abstract protected function maybeLoadExtensionFromBaseComplexType(
391
        Type $type,
392
        DOMElement $childNode
393
    ) : void;
394
395
    abstract protected function loadRestriction(
396
        Type $type,
397
        DOMElement $node
398
    ) : void;
399
400
    /**
401
    * @return mixed[]
402
    */
403
    abstract protected static function splitParts(
404
        DOMElement $node,
405
        string $typeName
406
    ) : array;
407
408
    /**
409
     * @throws TypeException
410
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
411
     */
412 45
    public function findSomething(
413
        string $finder,
414
        Schema $schema,
415
        DOMElement $node,
416
        string $typeName
417
    ) {
418 45
        list ($name, $namespace) = static::splitParts($node, $typeName);
419
420 45
        $namespace = $namespace ?: $schema->getTargetNamespace();
421
422
        try {
423 45
            return $schema->$finder($name, $namespace);
424
        } catch (TypeNotFoundException $e) {
425
            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);
426
        }
427
    }
428
429
    abstract protected function loadElementDef(
430
        Schema $schema,
431
        DOMElement $node
432
    ) : Closure;
433
434 45
    public function fillItem(Item $element, DOMElement $node) : void
435
    {
436 45
        foreach ($node->childNodes as $childNode) {
437
            if (
438 45
                in_array(
439 45
                    $childNode->localName,
440
                    [
441 45
                        'complexType',
442
                        'simpleType',
443
                    ]
444
                )
445
            ) {
446 45
                Type::loadTypeWithCallback(
447 45
                    $this,
448 45
                    $element->getSchema(),
449 45
                    $childNode,
450 45
                    function (Type $type) use ($element) : void {
451 45
                        $element->setType($type);
452 45
                    }
453
                );
454 45
                return;
455
            }
456
        }
457
458 45
        $this->fillItemNonLocalType($element, $node);
459 45
    }
460
461
    abstract protected function fillItemNonLocalType(
462
        Item $element,
463
        DOMElement $node
464
    ) : void;
465
466
    /**
467
    * @var Schema|null
468
    */
469
    protected $globalSchema;
470
471
    /**
472
    * @return Schema[]
473
    */
474 45
    protected function setupGlobalSchemas(array & $callbacks) : array
475
    {
476 45
        $globalSchemas = array();
477 45
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
478 45
            Schema::setLoadedFile(
479 45
                $uri,
480 45
                $globalSchemas[$namespace] = $schema = new Schema()
481
            );
482 45
            if ($namespace === self::XSD_NS) {
483 45
                $this->globalSchema = $schema;
484
            }
485 45
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
486 45
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
487
        }
488
489 45
        return $globalSchemas;
490
    }
491
492
    /**
493
    * @return string[]
494
    */
495 45
    public function getGlobalSchemaInfo() : array
496
    {
497 45
        return self::$globalSchemaInfo;
498
    }
499
500 45
    public function getGlobalSchema() : Schema
501
    {
502 45
        if (!$this->globalSchema) {
503 45
            $callbacks = array();
504 45
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
505
506 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anySimpleType"));
507 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anyType"));
508
509 45
            $globalSchemas[static::XML_NS]->addSchema($globalSchemas[static::XSD_NS], static::XSD_NS);
510 45
            $globalSchemas[static::XSD_NS]->addSchema($globalSchemas[static::XML_NS], static::XML_NS);
511
512 45
            foreach ($callbacks as $callback) {
513 45
                $callback();
514
            }
515
        }
516
517
        /**
518
        * @var Schema $out
519
        */
520 45
        $out = $this->globalSchema;
521
522 45
        return $out;
523
    }
524
525 45
    public function readNode(
526
        DOMElement $node,
527
        string $file = 'schema.xsd'
528
    ) : Schema {
529 45
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
530 45
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
531
532 45
        $rootSchema->addSchema($this->getGlobalSchema());
533 45
        $callbacks = $this->schemaNode($rootSchema, $node);
534
535 45
        foreach ($callbacks as $callback) {
536 39
            call_user_func($callback);
537
        }
538
539 45
        return $rootSchema;
540
    }
541
542
    /**
543
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
544
     *
545
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
546
     * file to distinguish between multiple schemas in a single file.
547
     */
548 45
    public function getNamespaceSpecificFileIndex(
549
        string $file,
550
        string $targetNamespace
551
    ) : string {
552 45
        return $file . '#' . $targetNamespace;
553
    }
554
555
    /**
556
     * @throws IOException
557
     */
558 44
    public function readString(
559
        string $content,
560
        string $file = 'schema.xsd'
561
    ) : Schema {
562 44
        $xml = new DOMDocument('1.0', 'UTF-8');
563 44
        if (!$xml->loadXML($content)) {
564
            throw new IOException("Can't load the schema");
565
        }
566 44
        $xml->documentURI = $file;
567
568 44
        return $this->readNode($xml->documentElement, $file);
569
    }
570
571 1
    public function readFile(string $file) : Schema
572
    {
573 1
        $xml = $this->getDOM($file);
574 1
        return $this->readNode($xml->documentElement, $file);
575
    }
576
577
    /**
578
     * @throws IOException
579
     */
580 45
    public function getDOM(string $file) : DOMDocument
581
    {
582 45
        $xml = new DOMDocument('1.0', 'UTF-8');
583 45
        if (!$xml->load($file)) {
584
            throw new IOException("Can't load the file $file");
585
        }
586 45
        return $xml;
587
    }
588
}
589