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

Schema   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 136
c 1
b 0
f 0
dl 0
loc 382
rs 9.0399
wmc 42

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getVersion() 0 3 1
A getFinalDefault() 0 3 1
A getTopLevelElements() 0 3 1
A getElementFormDefault() 0 3 1
A getBlockDefault() 0 3 1
A getAttributeFormDefault() 0 3 1
A getID() 0 3 1
A getSchemaTopElements() 0 3 1
A getLang() 0 3 1
A getTargetNamespace() 0 3 1
B isEmptyElement() 0 13 11
A __construct() 0 34 1
D toXML() 0 43 10
C fromXML() 0 99 10

How to fix   Complexity   

Complex Class

Complex classes like Schema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Schema, and based on these observations, apply Extract Interface, too.

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