Completed
Push — dependent-import ( 8a1663 )
by Asmir
05:22
created

SchemaReader::splitParts()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 10
nc 2
nop 2
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\Documentation\DocumentationReader;
12
use GoetasWebservices\XML\XSDReader\Documentation\StandardDocumentationReader;
13
use GoetasWebservices\XML\XSDReader\Exception\IOException;
14
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
15
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
16
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
17
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
18
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
19
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
26
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
27
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
28
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
29
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
30
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
31
use GoetasWebservices\XML\XSDReader\Schema\Item;
32
use GoetasWebservices\XML\XSDReader\Schema\Schema;
33
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
34
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
35
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
36
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
37
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
38
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
39
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
40
41
class SchemaReader
42
{
43
    public const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
44
45
    public const XML_NS = 'http://www.w3.org/XML/1998/namespace';
46
47
    /**
48
     * @var DocumentationReader
49
     */
50
    private $documentationReader;
51
52
    /**
53
     * @var Schema[]
54
     */
55
    private $loadedFiles = array();
56
57
    /**
58
     * @var Schema[][]
59
     */
60
    private $loadedSchemas = array();
61
62
    /**
63
     * @var string[]
64
     */
65
    protected $knownLocationSchemas = [
66
        'http://www.w3.org/2001/xml.xsd' => (
67
            __DIR__.'/Resources/xml.xsd'
68
        ),
69
        'http://www.w3.org/2001/XMLSchema.xsd' => (
70
            __DIR__.'/Resources/XMLSchema.xsd'
71
        ),
72
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
73
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
74
        ),
75
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
76
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
77
        ),
78
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
79
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
80
        ),
81
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
82
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
83
        ),
84
    ];
85
86
    /**
87
     * @var string[]
88
     */
89
    protected static $globalSchemaInfo = array(
90
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
91
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
92
    );
93
94
    public function __construct(DocumentationReader $documentationReader = null)
95
    {
96
        if (null === $documentationReader) {
97
            $documentationReader = new StandardDocumentationReader();
98
        }
99
        $this->documentationReader = $documentationReader;
100
    }
101
102
    public function addKnownSchemaLocation(string $remote, string $local): void
103
    {
104
        $this->knownLocationSchemas[$remote] = $local;
105
    }
106
107
    private function loadAttributeGroup(
108
        Schema $schema,
109
        DOMElement $node
110
    ): Closure {
111
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
112
        $attGroup->setDoc($this->getDocumentation($node));
113
        $schema->addAttributeGroup($attGroup);
114
115
        return function () use ($schema, $node, $attGroup) {
116
            SchemaReader::againstDOMNodeList(
117
                $node,
118
                function (
119
                    DOMElement $node,
120
                    DOMElement $childNode
121
                ) use (
122
                    $schema,
123
                    $attGroup
124
                ) {
125
                    switch ($childNode->localName) {
126
                        case 'attribute':
127
                            $attribute = $this->getAttributeFromAttributeOrRef(
128
                                $childNode,
129
                                $schema,
130
                                $node
131
                            );
132
                            $attGroup->addAttribute($attribute);
133
                            break;
134
                        case 'attributeGroup':
135
                            $this->findSomethingLikeAttributeGroup(
136
                                $schema,
137
                                $node,
138
                                $childNode,
139
                                $attGroup
140
                            );
141
                            break;
142
                    }
143
                }
144
            );
145
        };
146
    }
147
148
    private function getAttributeFromAttributeOrRef(
149
        DOMElement $childNode,
150
        Schema $schema,
151
        DOMElement $node
152
    ): AttributeItem {
153
        if ($childNode->hasAttribute('ref')) {
154
            $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref'));
155
        } else {
156
            /**
157
             * @var Attribute
158
             */
159
            $attribute = $this->loadAttribute($schema, $childNode);
160
        }
161
162
        return $attribute;
163
    }
164
165
    private function loadAttribute(
166
        Schema $schema,
167
        DOMElement $node
168
    ): Attribute {
169
        $attribute = new Attribute($schema, $node->getAttribute('name'));
170
        $attribute->setDoc($this->getDocumentation($node));
171
        $this->fillItem($attribute, $node);
172
173
        if ($node->hasAttribute('nillable')) {
174
            $attribute->setNil($node->getAttribute('nillable') == 'true');
175
        }
176
        if ($node->hasAttribute('form')) {
177
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
178
        }
179
        if ($node->hasAttribute('use')) {
180
            $attribute->setUse($node->getAttribute('use'));
181
        }
182
183
        return $attribute;
184
    }
185
186
    private function loadAttributeOrElementDef(
187
        Schema $schema,
188
        DOMElement $node,
189
        bool $attributeDef
190
    ): Closure {
191
        $name = $node->getAttribute('name');
192
        if ($attributeDef) {
193
            $attribute = new AttributeDef($schema, $name);
194
            $schema->addAttribute($attribute);
195
        } else {
196
            $attribute = new ElementDef($schema, $name);
197
            $schema->addElement($attribute);
198
        }
199
200
        return function () use ($attribute, $node) {
201
            $this->fillItem($attribute, $node);
202
        };
203
    }
204
205
    private function loadAttributeDef(Schema $schema, DOMElement $node): Closure
206
    {
207
        return $this->loadAttributeOrElementDef($schema, $node, true);
208
    }
209
210
    private function getDocumentation(DOMElement $node): string
211
    {
212
        return $this->documentationReader->get($node);
213
    }
214
215
    /**
216
     * @return Closure[]
217
     */
218
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array
219
    {
220
        $this->setSchemaThingsFromNode($schema, $node, $parent);
221
        $functions = array();
222
223
        static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
224
            $node,
225
            function (
226
                DOMElement $node,
227
                DOMElement $childNode
228
            ) use (
229
                $schema,
230
                &$functions
231
            ) {
232
                $callback = null;
233
234
                switch ($childNode->localName) {
235
                    case 'attributeGroup':
236
                        $callback = $this->loadAttributeGroup($schema, $childNode);
237
                        break;
238
                    case 'include':
239
                    case 'import':
240
                        $callback = $this->loadImport($schema, $childNode);
241
                        break;
242
                    case 'element':
243
                        $callback = $this->loadElementDef($schema, $childNode);
244
                        break;
245
                    case 'attribute':
246
                        $callback = $this->loadAttributeDef($schema, $childNode);
247
                        break;
248
                    case 'group':
249
                        $callback = $this->loadGroup($schema, $childNode);
250
                        break;
251
                    case 'complexType':
252
                        $callback = $this->loadComplexType($schema, $childNode);
253
                        break;
254
                    case 'simpleType':
255
                        $callback = $this->loadSimpleType($schema, $childNode);
256
                        break;
257
                }
258
259
                if ($callback instanceof Closure) {
260
                    $functions[] = $callback;
261
                }
262
            }
263
        );
264
265
        return $functions;
266
    }
267
268
    private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef
269
    {
270
        $ref = new GroupRef($referenced);
271
        $ref->setDoc($this->getDocumentation($node));
272
273
        self::maybeSetMax($ref, $node);
274
        self::maybeSetMin($ref, $node);
275
276
        return $ref;
277
    }
278
279
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): InterfaceSetMinMax
280
    {
281
        if (
282
            $node->hasAttribute('maxOccurs')
283
        ) {
284
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
285
        }
286
287
        return $ref;
288
    }
289
290
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): InterfaceSetMinMax
291
    {
292
        if ($node->hasAttribute('minOccurs')) {
293
            $ref->setMin((int) $node->getAttribute('minOccurs'));
294
        }
295
296
        return $ref;
297
    }
298
299
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null): void
300
    {
301
        $max =
302
        (
303
            (is_int($max) && (bool) $max) ||
304
            $node->getAttribute('maxOccurs') == 'unbounded' ||
305
            $node->getAttribute('maxOccurs') > 1
306
        )
307
            ? 2
308
            : null;
309
310
        static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
311
            $node,
312
            function (
313
                DOMElement $node,
314
                DOMElement $childNode
315
            ) use (
316
                $elementContainer,
317
                $max
318
            ) {
319
                $this->loadSequenceChildNode(
320
                    $elementContainer,
321
                    $node,
322
                    $childNode,
323
                    $max
324
                );
325
            }
326
        );
327
    }
328
329
    /**
330
     * @param int|null $max
331
     */
332
    private function loadSequenceChildNode(
333
        ElementContainer $elementContainer,
334
        DOMElement $node,
335
        DOMElement $childNode,
336
        $max
337
    ): void {
338
        switch ($childNode->localName) {
339
            case 'sequence':
340
            case 'choice':
341
            case 'all':
342
                $this->loadSequence(
343
                    $elementContainer,
344
                    $childNode,
345
                    $max
346
                );
347
                break;
348
            case 'element':
349
                $this->loadSequenceChildNodeLoadElement(
350
                    $elementContainer,
351
                    $node,
352
                    $childNode,
353
                    $max
354
                );
355
                break;
356
            case 'group':
357
                $this->addGroupAsElement(
358
                    $elementContainer->getSchema(),
359
                    $node,
360
                    $childNode,
361
                    $elementContainer
362
                );
363
                break;
364
        }
365
    }
366
367
    /**
368
     * @param int|null $max
369
     */
370
    private function loadSequenceChildNodeLoadElement(
371
        ElementContainer $elementContainer,
372
        DOMElement $node,
373
        DOMElement $childNode,
374
        $max
375
    ): void {
376
        if ($childNode->hasAttribute('ref')) {
377
            $element = new ElementRef(
378
                $this->findElement($elementContainer->getSchema(), $node, $childNode->getAttribute('ref'))
379
            );
380
            $element->setDoc($this->getDocumentation($childNode));
381
382
            self::maybeSetMax($element, $childNode);
383
            self::maybeSetMin($element, $childNode);
384
            if ($childNode->hasAttribute('nillable')) {
385
                $element->setNil($childNode->getAttribute('nillable') == 'true');
386
            }
387
            if ($childNode->hasAttribute('form')) {
388
                $element->setQualified($childNode->getAttribute('form') == 'qualified');
389
            }
390
        } else {
391
            $element = $this->loadElement(
392
                $elementContainer->getSchema(),
393
                $childNode
394
            );
395
        }
396
        if ($max > 1) {
397
            /*
398
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
399
            * phpstan@a4f89fa still thinks it's possibly null.
400
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
401
            */
402
            $element->setMax((int) $max);
403
        }
404
        $elementContainer->addElement($element);
405
    }
406
407
    private function addGroupAsElement(
408
        Schema $schema,
409
        DOMElement $node,
410
        DOMElement $childNode,
411
        ElementContainer $elementContainer
412
    ): void {
413
        $referencedGroup = $this->findGroup(
414
            $schema,
415
            $node,
416
            $childNode->getAttribute('ref')
417
        );
418
419
        $group = $this->loadGroupRef($referencedGroup, $childNode);
420
        $elementContainer->addElement($group);
421
    }
422
423
    private function loadGroup(Schema $schema, DOMElement $node): Closure
424
    {
425
        $group = new Group($schema, $node->getAttribute('name'));
426
        $group->setDoc($this->getDocumentation($node));
427
428
        if ($node->hasAttribute('maxOccurs')) {
429
            /**
430
             * @var GroupRef
431
             */
432
            $group = self::maybeSetMax(new GroupRef($group), $node);
433
        }
434
        if ($node->hasAttribute('minOccurs')) {
435
            /**
436
             * @var GroupRef
437
             */
438
            $group = self::maybeSetMin(
439
                $group instanceof GroupRef ? $group : new GroupRef($group),
0 ignored issues
show
Bug introduced by
It seems like $group defined by self::maybeSetMin($group...roupRef($group), $node) on line 438 can also be of type object<GoetasWebservices...ent\InterfaceSetMinMax>; however, GoetasWebservices\XML\XS...GroupRef::__construct() does only seem to accept object<GoetasWebservices...r\Schema\Element\Group>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
440
                $node
441
            );
442
        }
443
444
        $schema->addGroup($group);
0 ignored issues
show
Bug introduced by
It seems like $group can also be of type object<GoetasWebservices...ent\InterfaceSetMinMax>; however, GoetasWebservices\XML\XS...hema\Schema::addGroup() does only seem to accept object<GoetasWebservices...r\Schema\Element\Group>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
445
446
        return function () use ($group, $node) {
447
            static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
448
                $node,
449
                function (DOMelement $node, DOMElement $childNode) use ($group) {
450
                    switch ($childNode->localName) {
451
                        case 'sequence':
452
                        case 'choice':
453
                        case 'all':
454
                            $this->loadSequence($group, $childNode);
0 ignored issues
show
Bug introduced by
It seems like $group can also be of type object<GoetasWebservices...ent\InterfaceSetMinMax>; however, GoetasWebservices\XML\XS...aReader::loadSequence() does only seem to accept object<GoetasWebservices...ement\ElementContainer>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
455
                            break;
456
                    }
457
                }
458
            );
459
        };
460
    }
461
462
    private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
463
    {
464
        /**
465
         * @var bool
466
         */
467
        $isSimple = false;
468
469
        static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
470
            $node,
471
            function (
472
                DOMElement $node,
473
                DOMElement $childNode
474
            ) use (
475
                &$isSimple
476
            ) {
477
                if ($isSimple) {
478
                    return;
479
                }
480
                if ($childNode->localName === 'simpleContent') {
481
                    $isSimple = true;
482
                }
483
            }
484
        );
485
486
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
487
488
        $type->setDoc($this->getDocumentation($node));
489
        if ($node->getAttribute('name')) {
490
            $schema->addType($type);
491
        }
492
493
        return function () use ($type, $node, $schema, $callback) {
494
            $this->fillTypeNode($type, $node, true);
495
496
            static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
497
                $node,
498
                function (
499
                    DOMElement $node,
500
                    DOMElement $childNode
501
                ) use (
502
                    $schema,
503
                    $type
504
                ) {
505
                    $this->loadComplexTypeFromChildNode(
506
                        $type,
507
                        $node,
508
                        $childNode,
509
                        $schema
510
                    );
511
                }
512
            );
513
514
            if ($callback instanceof Closure) {
515
                call_user_func($callback, $type);
516
            }
517
        };
518
    }
519
520
    private function loadComplexTypeFromChildNode(
521
        BaseComplexType $type,
522
        DOMElement $node,
523
        DOMElement $childNode,
524
        Schema $schema
525
    ): void {
526
        switch ($childNode->localName) {
527
            case 'sequence':
528
            case 'choice':
529
            case 'all':
530
                if ($type instanceof ElementContainer) {
531
                    $this->loadSequence(
532
                        $type,
533
                        $childNode
534
                    );
535
                }
536
                break;
537
            case 'attribute':
538
                $this->addAttributeFromAttributeOrRef(
539
                    $type,
540
                    $childNode,
541
                    $schema,
542
                    $node
543
                );
544
                break;
545
            case 'attributeGroup':
546
                $this->findSomethingLikeAttributeGroup(
547
                    $schema,
548
                    $node,
549
                    $childNode,
550
                    $type
551
                );
552
                break;
553
            case 'group':
554
                if (
555
                    $type instanceof ComplexType
556
                ) {
557
                    $this->addGroupAsElement(
558
                        $schema,
559
                        $node,
560
                        $childNode,
561
                        $type
562
                    );
563
                }
564
                break;
565
        }
566
    }
567
568
    private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
569
    {
570
        $type = new SimpleType($schema, $node->getAttribute('name'));
571
        $type->setDoc($this->getDocumentation($node));
572
        if ($node->getAttribute('name')) {
573
            $schema->addType($type);
574
        }
575
576
        return function () use ($type, $node, $callback) {
577
            $this->fillTypeNode($type, $node, true);
578
579
            static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
580
                $node,
581
                function (DOMElement $node, DOMElement $childNode) use ($type) {
582
                    switch ($childNode->localName) {
583
                        case 'union':
584
                            $this->loadUnion($type, $childNode);
585
                            break;
586
                        case 'list':
587
                            $this->loadList($type, $childNode);
588
                            break;
589
                    }
590
                }
591
            );
592
593
            if ($callback instanceof Closure) {
594
                call_user_func($callback, $type);
595
            }
596
        };
597
    }
598
599
    private function loadList(SimpleType $type, DOMElement $node): void
600
    {
601
        if ($node->hasAttribute('itemType')) {
602
            /**
603
             * @var SimpleType
604
             */
605
            $listType = $this->findSomeType($type, $node, 'itemType');
606
            $type->setList($listType);
0 ignored issues
show
Documentation introduced by
$listType is of type object<GoetasWebservices...ader\Schema\SchemaItem>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
607
        } else {
608
            self::againstDOMNodeList(
609
                $node,
610
                function (
611
                    DOMElement $node,
612
                    DOMElement $childNode
613
                ) use (
614
                    $type
615
                ) {
616
                    $this->loadTypeWithCallback(
617
                        $type->getSchema(),
618
                        $childNode,
619
                        function (SimpleType $list) use ($type) {
620
                            $type->setList($list);
0 ignored issues
show
Documentation introduced by
$list is of type object<GoetasWebservices...Schema\Type\SimpleType>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
621
                        }
622
                    );
623
                }
624
            );
625
        }
626
    }
627
628
    private function findSomeType(
629
        SchemaItem $fromThis,
630
        DOMElement $node,
631
        string $attributeName
632
    ): SchemaItem {
633
        return $this->findSomeTypeFromAttribute(
634
            $fromThis,
635
            $node,
636
            $node->getAttribute($attributeName)
637
        );
638
    }
639
640
    private function findSomeTypeFromAttribute(
641
        SchemaItem $fromThis,
642
        DOMElement $node,
643
        string $attributeName
644
    ): SchemaItem {
645
        $out = $this->findType(
646
            $fromThis->getSchema(),
647
            $node,
648
            $attributeName
649
        );
650
651
        return $out;
652
    }
653
654
    private function loadUnion(SimpleType $type, DOMElement $node): void
655
    {
656
        if ($node->hasAttribute('memberTypes')) {
657
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
658
            foreach ($types as $typeName) {
659
                /**
660
                 * @var SimpleType
661
                 */
662
                $unionType = $this->findSomeTypeFromAttribute(
663
                    $type,
664
                    $node,
665
                    $typeName
666
                );
667
                $type->addUnion($unionType);
0 ignored issues
show
Documentation introduced by
$unionType is of type object<GoetasWebservices...ader\Schema\SchemaItem>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
668
            }
669
        }
670
        self::againstDOMNodeList(
671
            $node,
672
            function (
673
                DOMElement $node,
674
                DOMElement $childNode
675
            ) use (
676
                $type
677
            ) {
678
                $this->loadTypeWithCallback(
679
                    $type->getSchema(),
680
                    $childNode,
681
                    function (SimpleType $unType) use ($type) {
682
                        $type->addUnion($unType);
0 ignored issues
show
Documentation introduced by
$unType is of type object<GoetasWebservices...Schema\Type\SimpleType>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
683
                    }
684
                );
685
            }
686
        );
687
    }
688
689
    private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void
690
    {
691
        if ($checkAbstract) {
692
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
693
        }
694
695
        static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
696
            $node,
697
            function (DOMElement $node, DOMElement $childNode) use ($type) {
698
                switch ($childNode->localName) {
699
                    case 'restriction':
700
                        $this->loadRestriction($type, $childNode);
701
                        break;
702
                    case 'extension':
703
                        if ($type instanceof BaseComplexType) {
704
                            $this->loadExtension($type, $childNode);
705
                        }
706
                        break;
707
                    case 'simpleContent':
708
                    case 'complexContent':
709
                        $this->fillTypeNode($type, $childNode);
710
                        break;
711
                }
712
            }
713
        );
714
    }
715
716
    private function loadExtension(BaseComplexType $type, DOMElement $node): void
717
    {
718
        $extension = new Extension();
719
        $type->setExtension($extension);
720
721
        if ($node->hasAttribute('base')) {
722
            $this->findAndSetSomeBase(
723
                $type,
724
                $extension,
725
                $node
726
            );
727
        }
728
        $this->loadExtensionChildNodes($type, $node);
729
    }
730
731
    private function findAndSetSomeBase(
732
        Type $type,
733
        Base $setBaseOnThis,
734
        DOMElement $node
735
    ): void {
736
        /**
737
         * @var Type
738
         */
739
        $parent = $this->findSomeType($type, $node, 'base');
740
        $setBaseOnThis->setBase($parent);
0 ignored issues
show
Compatibility introduced by
$parent of type object<GoetasWebservices...ader\Schema\SchemaItem> is not a sub-type of object<GoetasWebservices...eader\Schema\Type\Type>. It seems like you assume a concrete implementation of the interface GoetasWebservices\XML\XSDReader\Schema\SchemaItem to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
741
    }
742
743
    private function loadExtensionChildNodes(
744
        BaseComplexType $type,
745
        DOMElement $node
746
    ) {
747
        static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
748
            $node,
749
            function (
750
                DOMElement $node,
751
                DOMElement $childNode
752
            ) use (
753
                $type
754
            ) {
755
                switch ($childNode->localName) {
756
                    case 'sequence':
757
                    case 'choice':
758
                    case 'all':
759
                        if ($type instanceof ElementContainer) {
760
                            $this->loadSequence(
761
                                $type,
762
                                $childNode
763
                            );
764
                        }
765
                        break;
766
                    case 'attribute':
767
                        $this->addAttributeFromAttributeOrRef(
768
                            $type,
769
                            $childNode,
770
                            $type->getSchema(),
771
                            $node
772
                        );
773
                        break;
774
                    case 'attributeGroup':
775
                        $this->findSomethingLikeAttributeGroup(
776
                            $type->getSchema(),
777
                            $node,
778
                            $childNode,
779
                            $type
780
                        );
781
                        break;
782
                }
783
            }
784
        );
785
    }
786
787
    private function loadRestriction(Type $type, DOMElement $node): void
788
    {
789
        $restriction = new Restriction();
790
        $type->setRestriction($restriction);
791
        if ($node->hasAttribute('base')) {
792
            $this->findAndSetSomeBase($type, $restriction, $node);
793
        } else {
794
            self::againstDOMNodeList(
795
                $node,
796
                function (
797
                    DOMElement $node,
798
                    DOMElement $childNode
799
                ) use (
800
                    $type,
801
                    $restriction
802
                ) {
803
                    $this->loadTypeWithCallback(
804
                        $type->getSchema(),
805
                        $childNode,
806
                        function (Type $restType) use ($restriction) {
807
                            $restriction->setBase($restType);
808
                        }
809
                    );
810
                }
811
            );
812
        }
813
        self::againstDOMNodeList(
814
            $node,
815
            function (
816
                DOMElement $node,
817
                DOMElement $childNode
818
            ) use (
819
                $restriction
820
            ) {
821
                if (
822
                    in_array(
823
                        $childNode->localName,
824
                        [
825
                            'enumeration',
826
                            'pattern',
827
                            'length',
828
                            'minLength',
829
                            'maxLength',
830
                            'minInclusive',
831
                            'maxInclusive',
832
                            'minExclusive',
833
                            'maxExclusive',
834
                            'fractionDigits',
835
                            'totalDigits',
836
                            'whiteSpace',
837
                        ],
838
                        true
839
                    )
840
                ) {
841
                    $restriction->addCheck(
842
                        $childNode->localName,
843
                        [
844
                            'value' => $childNode->getAttribute('value'),
845
                            'doc' => $this->getDocumentation($childNode),
846
                        ]
847
                    );
848
                }
849
            }
850
        );
851
    }
852
853
    /**
854
     * @return mixed[]
855
     */
856
    private static function splitParts(DOMElement $node, string $typeName): array
857
    {
858
        $prefix = null;
859
        $name = $typeName;
860
        if (strpos($typeName, ':') !== false) {
861
            list($prefix, $name) = explode(':', $typeName);
862
        }
863
864
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
865
866
        return array(
867
            $name,
868
            $namespace,
869
            $prefix,
870
        );
871
    }
872
873
    private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem
874
    {
875
        list($name, $namespace) = static::splitParts($node, $typeName);
0 ignored issues
show
Bug introduced by
Since splitParts() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of splitParts() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
876
877
        $namespace = $namespace ?: $schema->getTargetNamespace();
878
879
        try {
880
            return $schema->findAttribute($name, $namespace);
881
        } catch (TypeNotFoundException $e) {
882
            throw new TypeException(
883
                sprintf(
884
                    "Can't find %s named {%s}#%s, at line %d in %s ",
885
                    'attribute',
886
                    $namespace,
887
                    $name,
888
                    $node->getLineNo(),
889
                    $node->ownerDocument->documentURI
890
                ),
891
                0,
892
                $e
893
            );
894
        }
895
    }
896
897
    private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup
898
    {
899
        list($name, $namespace) = static::splitParts($node, $typeName);
0 ignored issues
show
Bug introduced by
Since splitParts() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of splitParts() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
900
901
        $namespace = $namespace ?: $schema->getTargetNamespace();
902
903
        try {
904
            return $schema->findAttributeGroup($name, $namespace);
905
        } catch (TypeNotFoundException $e) {
906
            throw new TypeException(
907
                sprintf(
908
                    "Can't find %s named {%s}#%s, at line %d in %s ",
909
                    'attributegroup',
910
                    $namespace,
911
                    $name,
912
                    $node->getLineNo(),
913
                    $node->ownerDocument->documentURI
914
                ),
915
                0,
916
                $e
917
            );
918
        }
919
    }
920
921
    private function findElement(Schema $schema, DOMElement $node, string $typeName): ElementDef
922
    {
923
        list($name, $namespace) = static::splitParts($node, $typeName);
0 ignored issues
show
Bug introduced by
Since splitParts() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of splitParts() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
924
925
        $namespace = $namespace ?: $schema->getTargetNamespace();
926
927
        try {
928
            return $schema->findElement($name, $namespace);
929
        } catch (TypeNotFoundException $e) {
930
            throw new TypeException(
931
                sprintf(
932
                    "Can't find %s named {%s}#%s, at line %d in %s ",
933
                    'element',
934
                    $namespace,
935
                    $name,
936
                    $node->getLineNo(),
937
                    $node->ownerDocument->documentURI
938
                ),
939
                0,
940
                $e
941
            );
942
        }
943
    }
944
945
    private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group
946
    {
947
        list($name, $namespace) = static::splitParts($node, $typeName);
0 ignored issues
show
Bug introduced by
Since splitParts() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of splitParts() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
948
949
        $namespace = $namespace ?: $schema->getTargetNamespace();
950
951
        try {
952
            return $schema->findGroup($name, $namespace);
953
        } catch (TypeNotFoundException $e) {
954
            throw new TypeException(
955
                sprintf(
956
                    "Can't find %s named {%s}#%s, at line %d in %s ",
957
                    'group',
958
                    $namespace,
959
                    $name,
960
                    $node->getLineNo(),
961
                    $node->ownerDocument->documentURI
962
                ),
963
                0,
964
                $e
965
            );
966
        }
967
    }
968
969
    private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem
970
    {
971
        list($name, $namespace) = static::splitParts($node, $typeName);
0 ignored issues
show
Bug introduced by
Since splitParts() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of splitParts() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
972
973
        $namespace = $namespace ?: $schema->getTargetNamespace();
974
975
        try {
976
            return $schema->findType($name, $namespace);
977
        } catch (TypeNotFoundException $e) {
978
            throw new TypeException(
979
                sprintf(
980
                    "Can't find %s named {%s}#%s, at line %d in %s ",
981
                    'type',
982
                    $namespace,
983
                    $name,
984
                    $node->getLineNo(),
985
                    $node->ownerDocument->documentURI
986
                ),
987
                0,
988
                $e
989
            );
990
        }
991
    }
992
993
    /**
994
     * @return Closure
995
     */
996
    private function loadElementDef(Schema $schema, DOMElement $node): Closure
997
    {
998
        return $this->loadAttributeOrElementDef($schema, $node, false);
999
    }
1000
1001
    private function fillItem(Item $element, DOMElement $node)
1002
    {
1003
        /**
1004
         * @var bool
1005
         */
1006
        $skip = false;
1007
        static::againstDOMNodeList(
0 ignored issues
show
Bug introduced by
Since againstDOMNodeList() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of againstDOMNodeList() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
1008
            $node,
1009
            function (
1010
                DOMElement $node,
1011
                DOMElement $childNode
1012
            ) use (
1013
                $element,
1014
                &$skip
1015
            ) {
1016
                if (
1017
                    !$skip &&
1018
                    in_array(
1019
                        $childNode->localName,
1020
                        [
1021
                            'complexType',
1022
                            'simpleType',
1023
                        ],
1024
                        true
1025
                    )
1026
                ) {
1027
                    $this->loadTypeWithCallback(
1028
                        $element->getSchema(),
1029
                        $childNode,
1030
                        function (Type $type) use ($element) {
1031
                            $element->setType($type);
1032
                        }
1033
                    );
1034
                    $skip = true;
1035
                }
1036
            }
1037
        );
1038
        if ($skip) {
1039
            return;
1040
        }
1041
        $this->fillItemNonLocalType($element, $node);
1042
    }
1043
1044
    private function fillItemNonLocalType(Item $element, DOMElement $node): void
1045
    {
1046
        if ($node->getAttribute('type')) {
1047
            /**
1048
             * @var Type
1049
             */
1050
            $type = $this->findSomeType($element, $node, 'type');
1051
        } else {
1052
            /**
1053
             * @var Type
1054
             */
1055
            $type = $this->findSomeTypeFromAttribute(
1056
                $element,
1057
                $node,
1058
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1059
            );
1060
        }
1061
1062
        $element->setType($type);
0 ignored issues
show
Compatibility introduced by
$type of type object<GoetasWebservices...ader\Schema\SchemaItem> is not a sub-type of object<GoetasWebservices...eader\Schema\Type\Type>. It seems like you assume a concrete implementation of the interface GoetasWebservices\XML\XSDReader\Schema\SchemaItem to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
1063
    }
1064
1065
    private function loadImport(
1066
        Schema $schema,
1067
        DOMElement $node
1068
    ): Closure {
1069
1070
        $namespace = $node->getAttribute('namespace');
1071
        $schemaLocation = $node->getAttribute('schemaLocation');
1072
1073
        // postpone schema loading
1074
        if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) {
1075
            return function () use ($node, $schema, $namespace) {
1076
                if (!empty($this->loadedSchemas[$namespace])) {
1077
                    foreach ($this->loadedSchemas[$namespace] as $s) {
1078
                        $schema->addSchema($s, $namespace);
0 ignored issues
show
Documentation introduced by
$s is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1079
                    }
1080
                }
1081
            };
1082
        } elseif ($namespace && !$schemaLocation && isset(self::$globalSchemaInfo[$namespace])) {
1083
            $schema->addSchema(self::$globalSchemaInfo[$namespace]);
0 ignored issues
show
Documentation introduced by
self::$globalSchemaInfo[$namespace] is of type string, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1084
        }
1085
1086
        if ($node->hasAttribute('schemaLocation')) {
1087
            $base = urldecode($node->ownerDocument->documentURI);
1088
            $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation'));
1089
1090
            if (isset($this->loadedFiles[$file])) {
1091
                $schema->addSchema($this->loadedFiles[$file]);
0 ignored issues
show
Documentation introduced by
$this->loadedFiles[$file] is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1092
                return function () {
1093
                };
1094
            }
1095
        }
1096
1097
        return $this->loadImportFresh($namespace, $schema, $file);
0 ignored issues
show
Bug introduced by
The variable $file does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1098
    }
1099
1100
    private function createOrUseSchemaForNs(
1101
        Schema $schema,
1102
        string $namespace
1103
    ): Schema {
1104
1105
        if (('' !== trim($namespace))) {
1106
            $newSchema = new Schema();
1107
            $newSchema->addSchema($this->getGlobalSchema());
0 ignored issues
show
Documentation introduced by
$this->getGlobalSchema() is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1108
            $schema->addSchema($newSchema);
0 ignored issues
show
Documentation introduced by
$newSchema is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1109
        } else {
1110
            $newSchema = $schema;
1111
        }
1112
1113
        return $newSchema;
1114
    }
1115
1116
    private function loadImportFresh(
1117
        string $namespace,
1118
        Schema $schema,
1119
        string $file
1120
    ): Closure {
1121
        return function () use ($namespace, $schema, $file) {
1122
1123
            $dom = $this->getDOM(
1124
            isset($this->knownLocationSchemas[$file])
1125
                ? $this->knownLocationSchemas[$file]
1126
                : $file
1127
            );
1128
1129
            $schemaNew = $this->createOrUseSchemaForNs($schema, $namespace);
1130
1131
            $this->setLoadedFile($file, $schemaNew);
1132
1133
            $callbacks = $this->schemaNode($schemaNew, $dom->documentElement, $schema);
1134
1135
            foreach ($callbacks as $callback) {
1136
                $callback();
1137
            }
1138
        };
1139
    }
1140
1141
    /**
1142
     * @var Schema|null
1143
     */
1144
    protected $globalSchema;
1145
1146
    /**
1147
     * @return Schema
1148
     */
1149
    private function getGlobalSchema(): Schema
1150
    {
1151
        if (!($this->globalSchema instanceof Schema)) {
1152
            $callbacks = array();
1153
            $globalSchemas = array();
1154
            /**
1155
             * @var string $namespace
1156
             */
1157
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1158
                $this->setLoadedFile(
1159
                    $uri,
1160
                    $globalSchemas[$namespace] = $schema = new Schema()
1161
                );
1162
                if ($namespace === self::XSD_NS) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $namespace (integer) and self::XSD_NS (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1163
                    $this->globalSchema = $schema;
1164
                }
1165
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1166
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1167
            }
1168
1169
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anySimpleType'));
1170
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anyType'));
1171
1172
            $globalSchemas[(string) static::XML_NS]->addSchema(
1173
                $globalSchemas[(string) static::XSD_NS],
1174
                (string) static::XSD_NS
1175
            );
1176
            $globalSchemas[(string) static::XSD_NS]->addSchema(
1177
                $globalSchemas[(string) static::XML_NS],
1178
                (string) static::XML_NS
1179
            );
1180
1181
            /**
1182
             * @var Closure
1183
             */
1184
            foreach ($callbacks as $callback) {
1185
                $callback();
1186
            }
1187
        }
1188
1189
        /**
1190
         * @var Schema
1191
         */
1192
        $out = $this->globalSchema;
1193
1194
        if (!($out instanceof Schema)) {
1195
            throw new TypeException('Globa schema not discoverd');
1196
        }
1197
1198
        return $out;
1199
    }
1200
1201
    public function readNodes(array $nodes, string $file = null)
1202
    {
1203
        $rootSchema = new Schema();
1204
        $rootSchema->addSchema($this->getGlobalSchema());
0 ignored issues
show
Documentation introduced by
$this->getGlobalSchema() is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1205
1206
        $all = array();
1207
        foreach ($nodes as $k => $node) {
1208
            if (($node instanceof \DOMElement) && $node->namespaceURI === self::XSD_NS && $node->localName == 'schema') {
1209
1210
                $holderSchema = new Schema();
1211
                $holderSchema->addSchema($this->getGlobalSchema());
0 ignored issues
show
Documentation introduced by
$this->getGlobalSchema() is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1212
1213
                $this->setLoadedSchema($node, $holderSchema);
1214
1215
                $rootSchema->addSchema($holderSchema);
0 ignored issues
show
Documentation introduced by
$holderSchema is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1216
1217
                $callbacks = $this->schemaNode($holderSchema, $node);
1218
                $all = array_merge($callbacks, $all);
1219
            }
1220
        }
1221
1222
        if ($file) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1223
            $this->setLoadedFile($file, $rootSchema);
1224
        }
1225
1226
        foreach ($all as $callback) {
1227
            call_user_func($callback);
1228
        }
1229
        return $rootSchema;
1230
    }
1231
1232
    public function readNode(DOMElement $node, string $file = null): Schema
1233
    {
1234
        $rootSchema = new Schema();
1235
        $rootSchema->addSchema($this->getGlobalSchema());
0 ignored issues
show
Documentation introduced by
$this->getGlobalSchema() is of type object<GoetasWebservices...SDReader\Schema\Schema>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1236
1237
        if ($file) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1238
            $this->setLoadedFile($file, $rootSchema);
1239
        }
1240
1241
        $this->setLoadedSchema($node, $rootSchema);
1242
1243
1244
        $callbacks = $this->schemaNode($rootSchema, $node);
1245
1246
        foreach ($callbacks as $callback) {
1247
            call_user_func($callback);
1248
        }
1249
1250
        return $rootSchema;
1251
    }
1252
1253
    /**
1254
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1255
     *
1256
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1257
     * file to distinguish between multiple schemas in a single file.
1258
     */
1259
    private function getNamespaceSpecificFileIndex(string $file, string $targetNamespace): string
1260
    {
1261
        return $file.'#'.$targetNamespace;
1262
    }
1263
1264
    /**
1265
     * @throws IOException
1266
     */
1267
    public function readString(string $content, string $file = 'schema.xsd'): Schema
1268
    {
1269
        $xml = new DOMDocument('1.0', 'UTF-8');
1270
        if (!$xml->loadXML($content)) {
1271
            throw new IOException("Can't load the schema");
1272
        }
1273
        $xml->documentURI = $file;
1274
1275
        return $this->readNode($xml->documentElement, $file);
1276
    }
1277
1278
    public function readFile(string $file): Schema
1279
    {
1280
        $xml = $this->getDOM($file);
1281
1282
        return $this->readNode($xml->documentElement, $file);
1283
    }
1284
1285
    /**
1286
     * @throws IOException
1287
     */
1288
    private function getDOM(string $file): DOMDocument
1289
    {
1290
        $xml = new DOMDocument('1.0', 'UTF-8');
1291
        if (!$xml->load($file)) {
1292
            throw new IOException("Can't load the file $file");
1293
        }
1294
1295
        return $xml;
1296
    }
1297
1298
    private static function againstDOMNodeList(
1299
        DOMElement $node,
1300
        Closure $againstNodeList
1301
    ) {
1302
        $limit = $node->childNodes->length;
1303
        for ($i = 0; $i < $limit; $i += 1) {
1304
            /**
1305
             * @var DOMNode
1306
             */
1307
            $childNode = $node->childNodes->item($i);
1308
1309
            if ($childNode instanceof DOMElement) {
1310
                $againstNodeList(
1311
                    $node,
1312
                    $childNode
1313
                );
1314
            }
1315
        }
1316
    }
1317
1318
    private function loadTypeWithCallback(
1319
        Schema $schema,
1320
        DOMElement $childNode,
1321
        Closure $callback
1322
    ) {
1323
        /**
1324
         * @var Closure|null $func
1325
         */
1326
        $func = null;
1327
1328
        switch ($childNode->localName) {
1329
            case 'complexType':
1330
                $func = $this->loadComplexType($schema, $childNode, $callback);
1331
                break;
1332
            case 'simpleType':
1333
                $func = $this->loadSimpleType($schema, $childNode, $callback);
1334
                break;
1335
        }
1336
1337
        if ($func instanceof Closure) {
1338
            call_user_func($func);
1339
        }
1340
    }
1341
1342
    private function loadElement(
1343
        Schema $schema,
1344
        DOMElement $node
1345
    ): Element {
1346
        $element = new Element($schema, $node->getAttribute('name'));
1347
        $element->setDoc($this->getDocumentation($node));
1348
1349
        $this->fillItem($element, $node);
1350
1351
        self::maybeSetMax($element, $node);
1352
        self::maybeSetMin($element, $node);
1353
1354
        $xp = new \DOMXPath($node->ownerDocument);
1355
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
1356
1357
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1358
            $element->setMin(0);
1359
        }
1360
1361
        if ($node->hasAttribute('nillable')) {
1362
            $element->setNil($node->getAttribute('nillable') == 'true');
1363
        }
1364
        if ($node->hasAttribute('form')) {
1365
            $element->setQualified($node->getAttribute('form') == 'qualified');
1366
        }
1367
1368
        return $element;
1369
    }
1370
1371
    private function addAttributeFromAttributeOrRef(
1372
        BaseComplexType $type,
1373
        DOMElement $childNode,
1374
        Schema $schema,
1375
        DOMElement $node
1376
    ): void {
1377
        $attribute = $this->getAttributeFromAttributeOrRef(
1378
            $childNode,
1379
            $schema,
1380
            $node
1381
        );
1382
1383
        $type->addAttribute($attribute);
1384
    }
1385
1386
    private function findSomethingLikeAttributeGroup(
1387
        Schema $schema,
1388
        DOMElement $node,
1389
        DOMElement $childNode,
1390
        AttributeContainer $addToThis
1391
    ): void {
1392
        $attribute = $this->findAttributeGroup($schema, $node, $childNode->getAttribute('ref'));
1393
        $addToThis->addAttribute($attribute);
1394
    }
1395
1396
    private function setLoadedFile(string $key, Schema $schema): void
1397
    {
1398
        $this->loadedFiles[$key] = $schema;
1399
    }
1400
1401
    private function setLoadedSchema(DOMNode $node, Schema $schema): void
1402
    {
1403
        if ($node->hasAttribute('targetNamespace')) {
0 ignored issues
show
Bug introduced by
The method hasAttribute() does not exist on DOMNode. Did you maybe mean hasAttributes()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1404
            $this->loadedSchemas[$node->getAttribute('targetNamespace')][] = $schema;
1405
        }
1406
    }
1407
1408
    private function setSchemaThingsFromNode(
1409
        Schema $schema,
1410
        DOMElement $node,
1411
        Schema $parent = null
1412
    ): void {
1413
        $schema->setDoc($this->getDocumentation($node));
1414
1415
        if ($node->hasAttribute('targetNamespace')) {
1416
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1417
        } elseif ($parent instanceof Schema) {
1418
            $schema->setTargetNamespace($parent->getTargetNamespace());
1419
        }
1420
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1421
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1422
        $schema->setDoc($this->getDocumentation($node));
1423
    }
1424
}
1425