Passed
Push — master ( 7f7f5c...8413b2 )
by Tim
02:29
created

Schema::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 34
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 15
c 1
b 0
f 0
nc 1
nop 11
dl 0
loc 34
rs 9.7666

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSchema\XML\xs;
6
7
use DOMElement;
8
use SimpleSAML\XML\Assert\Assert;
9
use SimpleSAML\XML\Constants as C;
10
use SimpleSAML\XML\{SchemaValidatableElementInterface, SchemaValidatableElementTrait};
11
use SimpleSAML\XML\Type\LangValue;
12
use SimpleSAML\XML\Utils\XPath;
13
use SimpleSAML\XMLSchema\Exception\{InvalidDOMElementException, SchemaViolationException};
14
use SimpleSAML\XMLSchema\Type\Builtin\{AnyURIValue, IDValue, StringValue, TokenValue};
15
use SimpleSAML\XMLSchema\Type\{BlockSetValue, FormChoiceValue, FullDerivationSetValue};
16
17
use function array_merge;
18
use function strval;
19
20
/**
21
 * Class representing the schema-element
22
 *
23
 * @package simplesamlphp/xml-common
24
 */
25
final class Schema extends AbstractOpenAttrs implements SchemaValidatableElementInterface
26
{
27
    use SchemaValidatableElementTrait;
1 ignored issue
show
introduced by
The trait SimpleSAML\XML\SchemaValidatableElementTrait requires some properties which are not provided by SimpleSAML\XMLSchema\XML\xs\Schema: $message, $line
Loading history...
28
29
    /** @var string */
30
    public const LOCALNAME = 'schema';
31
32
    /** The exclusions for the xs:anyAttribute element */
33
    public const XS_ANY_ATTR_EXCLUSIONS = [
34
        [C::NS_XML, 'lang'],
35
    ];
36
37
38
    /**
39
     * Schema constructor
40
     *
41
     * @param (
42
     *     \SimpleSAML\XMLSchema\XML\xs\XsInclude|
43
     *     \SimpleSAML\XMLSchema\XML\xs\Import|
44
     *     \SimpleSAML\XMLSchema\XML\xs\Redefine|
45
     *     \SimpleSAML\XMLSchema\XML\xs\Annotation
46
     * )[] $topLevelElements
47
     * @param (
48
     *     \SimpleSAML\XMLSchema\XML\xs\TopLevelSimpleType|
49
     *     \SimpleSAML\XMLSchema\XML\xs\TopLevelComplexType|
50
     *     \SimpleSAML\XMLSchema\XML\xs\NamedGroup|
51
     *     \SimpleSAML\XMLSchema\XML\xs\NamedAttributeGroup|
52
     *     \SimpleSAML\XMLSchema\XML\xs\TopLevelAttribute|
53
     *     \SimpleSAML\XMLSchema\XML\xs\TopLevelElement|
54
     *     \SimpleSAML\XMLSchema\XML\xs\Notation|
55
     *     \SimpleSAML\XMLSchema\XML\xs\Annotation
56
     * )[] $schemaTopElements
57
     * @param \SimpleSAML\XMLSchema\Type\Builtin\AnyURIValue $targetNamespace
58
     * @param \SimpleSAML\XMLSchema\Type\Builtin\TokenValue $version
59
     * @param \SimpleSAML\XMLSchema\Type\FullDerivationSetValue $finalDefault
60
     * @param \SimpleSAML\XMLSchema\Type\BlockSetValue $blockDefault
61
     * @param \SimpleSAML\XMLSchema\Type\FormChoiceValue|null $attributeFormDefault
62
     * @param \SimpleSAML\XMLSchema\Type\FormChoiceValue|null $elementFormDefault
63
     * @param \SimpleSAML\XMLSchema\Type\Builtin\IDValue|null $id
64
     * @param \SimpleSAML\XML\Type\LangValue|null $lang
65
     * @param array<\SimpleSAML\XML\Attribute> $namespacedAttributes
66
     */
67
    public function __construct(
68
        protected array $topLevelElements = [],
69
        protected array $schemaTopElements = [],
70
        protected ?AnyURIValue $targetNamespace = null,
71
        protected ?TokenValue $version = null,
72
        protected ?FullDerivationSetValue $finalDefault = null,
73
        protected ?BlockSetValue $blockDefault = null,
74
        protected ?FormChoiceValue $attributeFormDefault = null,
75
        protected ?FormChoiceValue $elementFormDefault = null,
76
        protected ?IDValue $id = null,
77
        protected ?LangValue $lang = null,
78
        array $namespacedAttributes = [],
79
    ) {
80
        Assert::maxCount($topLevelElements, C::UNBOUNDED_LIMIT);
81
        Assert::allIsInstanceOfAny(
82
            $topLevelElements,
83
            [XsInclude::class, Import::class, Redefine::class, Annotation::class],
84
            SchemaViolationException::class,
85
        );
86
87
        Assert::maxCount($schemaTopElements, C::UNBOUNDED_LIMIT);
88
        Assert::allIsInstanceOfAny(
89
            $schemaTopElements,
90
            [
91
                RedefinableInterface::class,
92
                TopLevelAttribute::class,
93
                TopLevelElement::class,
94
                Notation::class,
95
                Annotation::class,
96
            ],
97
            SchemaViolationException::class,
98
        );
99
100
        parent::__construct($namespacedAttributes);
101
    }
102
103
104
    /**
105
     * Collect the value of the topLevelElements-property
106
     *
107
     * @return (
1 ignored issue
show
Documentation Bug introduced by
The doc comment ( at position 1 could not be parsed: the token is null at position 1.
Loading history...
108
     *     \SimpleSAML\XMLSchema\XML\xs\XsInclude|
109
     *     \SimpleSAML\XMLSchema\XML\xs\Import|
110
     *     \SimpleSAML\XMLSchema\XML\xs\Redefine|
111
     *     \SimpleSAML\XMLSchema\XML\xs\Annotation
112
     * )[]
113
     */
114
    public function getTopLevelElements(): array
115
    {
116
        return $this->topLevelElements;
117
    }
118
119
120
    /**
121
     * Collect the value of the schemaTopElements-property
122
     *
123
     * @return (
1 ignored issue
show
Documentation Bug introduced by
The doc comment ( at position 1 could not be parsed: the token is null at position 1.
Loading history...
124
     *     \SimpleSAML\XMLSchema\XML\xs\RedefinableInterface|
125
     *     \SimpleSAML\XMLSchema\XML\xs\TopLevelAttribute|
126
     *     \SimpleSAML\XMLSchema\XML\xs\TopLevelElement|
127
     *     \SimpleSAML\XMLSchema\XML\xs\Notation|
128
     *     \SimpleSAML\XMLSchema\XML\xs\Annotation
129
     * )[]
130
     */
131
    public function getSchemaTopElements(): array
132
    {
133
        return $this->schemaTopElements;
134
    }
135
136
137
    /**
138
     * Collect the value of the targetNamespace-property
139
     *
140
     * @return \SimpleSAML\XMLSchema\Type\Builtin\AnyURIValue|null
141
     */
142
    public function getTargetNamespace(): ?AnyURIValue
143
    {
144
        return $this->targetNamespace;
145
    }
146
147
148
    /**
149
     * Collect the value of the version-property
150
     *
151
     * @return \SimpleSAML\XMLSchema\Type\Builtin\TokenValue|null
152
     */
153
    public function getVersion(): ?TokenValue
154
    {
155
        return $this->version;
156
    }
157
158
159
    /**
160
     * Collect the value of the blockDefault-property
161
     *
162
     * @return \SimpleSAML\XMLSchema\Type\BlockSetValue|null
163
     */
164
    public function getBlockDefault(): ?BlockSetValue
165
    {
166
        return $this->blockDefault;
167
    }
168
169
170
    /**
171
     * Collect the value of the finalDefault-property
172
     *
173
     * @return \SimpleSAML\XMLSchema\Type\FullDerivationSetValue|null
174
     */
175
    public function getFinalDefault(): ?FullDerivationSetValue
176
    {
177
        return $this->finalDefault;
178
    }
179
180
181
    /**
182
     * Collect the value of the attributeFormDefault-property
183
     *
184
     * @return \SimpleSAML\XMLSchema\Type\FormChoiceValue|null
185
     */
186
    public function getAttributeFormDefault(): ?FormChoiceValue
187
    {
188
        return $this->attributeFormDefault;
189
    }
190
191
192
    /**
193
     * Collect the value of the elementFormDefault-property
194
     *
195
     * @return \SimpleSAML\XMLSchema\Type\FormChoiceValue|null
196
     */
197
    public function getElementFormDefault(): ?FormChoiceValue
198
    {
199
        return $this->elementFormDefault;
200
    }
201
202
203
    /**
204
     * Collect the value of the id-property
205
     *
206
     * @return \SimpleSAML\XMLSchema\Type\Builtin\IDValue|null
207
     */
208
    public function getID(): ?IDValue
209
    {
210
        return $this->id;
211
    }
212
213
214
    /**
215
     * Collect the value of the lang-property
216
     *
217
     * @return \SimpleSAML\XML\Type\LangValue|null
218
     */
219
    public function getLang(): ?LangValue
220
    {
221
        return $this->lang;
222
    }
223
224
225
    /**
226
     * Test if an object, at the state it's in, would produce an empty XML-element
227
     *
228
     * @return bool
229
     */
230
    public function isEmptyElement(): bool
231
    {
232
        return parent::isEmptyElement() &&
233
            empty($this->getTopLevelElements()) &&
234
            empty($this->getSchemaTopElements()) &&
235
            empty($this->getTargetNamespace()) &&
236
            empty($this->getVersion()) &&
237
            empty($this->getFinalDefault()) &&
238
            empty($this->getBlockDefault()) &&
239
            empty($this->getAttributeFormDefault()) &&
240
            empty($this->getElementFormDefault()) &&
241
            empty($this->getId()) &&
242
            empty($this->getLang());
243
    }
244
245
246
    /**
247
     * Create an instance of this object from its XML representation.
248
     *
249
     * @param \DOMElement $xml
250
     * @return static
251
     *
252
     * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
253
     *   if the qualified name of the supplied element is wrong
254
     */
255
    public static function fromXML(DOMElement $xml): static
256
    {
257
        Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
258
        Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
259
260
        $xpCache = XPath::getXPath($xml);
261
262
        $beforeAllowed = [
263
            'annotation' => Annotation::class,
264
            'import' => Import::class,
265
            'include' => XsInclude::class,
266
            'redefine' => Redefine::class,
267
        ];
268
        $beforeSchemaTopElements = XPath::xpQuery(
269
            $xml,
270
            '('
271
            . '/xs:schema/xs:group|'
272
            . '/xs:schema/xs:attributeGroup|'
273
            . '/xs:schema/xs:complexType|'
274
            . '/xs:schema/xs:simpleType|'
275
            . '/xs:schema/xs:element|'
276
            .  '/xs:schema/xs:attribute'
277
            . ')[1]/preceding-sibling::xs:*',
278
            $xpCache,
279
        );
280
281
        $topLevelElements = [];
282
        foreach ($beforeSchemaTopElements as $node) {
283
            /** @var \DOMElement $node */
284
            if ($node instanceof DOMElement) {
285
                if ($node->namespaceURI === C::NS_XS && array_key_exists($node->localName, $beforeAllowed)) {
286
                    $topLevelElements[] = $beforeAllowed[$node->localName]::fromXML($node);
287
                }
288
            }
289
        }
290
291
        $afterAllowed = [
292
            'annotation' => Annotation::class,
293
            'attribute' => TopLevelAttribute::class,
294
            'attributeGroup' => NamedAttributeGroup::class,
295
            'complexType' => TopLevelComplexType::class,
296
            'element' => TopLevelElement::class,
297
            'notation' => Notation::class,
298
            'simpleType' => TopLevelSimpleType::class,
299
        ];
300
        $afterSchemaTopElementFirstHit = XPath::xpQuery(
301
            $xml,
302
            '('
303
            . '/xs:schema/xs:group|'
304
            . '/xs:schema/xs:attributeGroup|'
305
            . '/xs:schema/xs:complexType'
306
            . '/xs:schema/xs:simpleType|'
307
            . '/xs:schema/xs:element|'
308
            . '/xs:schema/xs:attribute'
309
            . ')[1]',
310
            $xpCache,
311
        );
312
313
        $afterSchemaTopElementSibling = XPath::xpQuery(
314
            $xml,
315
            '('
316
            . '/xs:schema/xs:group|'
317
            . '/xs:schema/xs:attributeGroup|'
318
            . '/xs:schema/xs:complexType'
319
            . '/xs:schema/xs:simpleType|'
320
            . '/xs:schema/xs:element|'
321
            . '/xs:schema/xs:attribute'
322
            . ')[1]/following-sibling::xs:*',
323
            $xpCache,
324
        );
325
326
        $afterSchemaTopElements = array_merge($afterSchemaTopElementFirstHit, $afterSchemaTopElementSibling);
327
328
        $schemaTopElements = [];
329
        foreach ($afterSchemaTopElements as $node) {
330
            /** @var \DOMElement $node */
331
            if ($node instanceof DOMElement) {
332
                if ($node->namespaceURI === C::NS_XS && array_key_exists($node->localName, $afterAllowed)) {
333
                    $schemaTopElements[] = $afterAllowed[$node->localName]::fromXML($node);
334
                }
335
            }
336
        }
337
338
        $lang = $xml->hasAttributeNS(C::NS_XML, 'lang')
339
            ? LangValue::fromString($xml->getAttributeNS(C::NS_XML, 'lang'))
340
            : null;
341
342
        return new static(
343
            $topLevelElements,
344
            $schemaTopElements,
345
            self::getOptionalAttribute($xml, 'targetNamespace', AnyURIValue::class, null),
346
            self::getOptionalAttribute($xml, 'version', TokenValue::class, null),
347
            self::getOptionalAttribute($xml, 'finalDefault', FullDerivationSetValue::class, null),
348
            self::getOptionalAttribute($xml, 'blockDefault', BlockSetValue::class, null),
349
            self::getOptionalAttribute($xml, 'attributeFormDefault', FormChoiceValue::class, null),
350
            self::getOptionalAttribute($xml, 'elementFormDefault', FormChoiceValue::class, null),
351
            self::getOptionalAttribute($xml, 'id', IDValue::class, null),
352
            $lang,
353
            self::getAttributesNSFromXML($xml),
354
        );
355
    }
356
357
358
    /**
359
     * Add this Schema to an XML element.
360
     *
361
     * @param \DOMElement|null $parent The element we should append this Schema to.
362
     * @return \DOMElement
363
     */
364
    public function toXML(?DOMElement $parent = null): DOMElement
365
    {
366
        $e = parent::toXML($parent);
367
368
        if ($this->getTargetNamespace() !== null) {
369
            $e->setAttribute('targetNamespace', strval($this->getTargetNamespace()));
370
        }
371
372
        if ($this->getVersion() !== null) {
373
            $e->setAttribute('version', strval($this->getVersion()));
374
        }
375
376
        if ($this->getFinalDefault() !== null) {
377
            $e->setAttribute('finalDefault', strval($this->getFinalDefault()));
378
        }
379
380
        if ($this->getBlockDefault() !== null) {
381
            $e->setAttribute('blockDefault', strval($this->getBlockDefault()));
382
        }
383
384
        if ($this->getAttributeFormDefault() !== null) {
385
            $e->setAttribute('attributeFormDefault', strval($this->getAttributeFormDefault()));
386
        }
387
388
        if ($this->getElementFormDefault() !== null) {
389
            $e->setAttribute('elementFormDefault', strval($this->getElementFormDefault()));
390
        }
391
392
        if ($this->getId() !== null) {
393
            $e->setAttribute('id', strval($this->getId()));
394
        }
395
396
        $this->getLang()?->toAttribute()->toXML($e);
397
398
        foreach ($this->getTopLevelElements() as $tle) {
399
            $tle->toXML($e);
400
        }
401
402
        foreach ($this->getSchemaTopElements() as $ste) {
403
            $ste->toXML($e);
404
        }
405
406
        return $e;
407
    }
408
}
409