ExtendableElementTrait::setElements()   B
last analyzed

Complexity

Conditions 11
Paths 32

Size

Total Lines 77
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 42
nc 32
nop 1
dl 0
loc 77
rs 7.3166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XML;
6
7
use DOMElement;
8
use RuntimeException;
9
use SimpleSAML\XML\Assert\Assert;
10
use SimpleSAML\XML\Chunk;
11
use SimpleSAML\XML\Constants as C;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XML\Constants was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use SimpleSAML\XML\Registry\ElementRegistry;
13
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
14
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
15
use SimpleSAML\XMLSchema\XML\Constants\NS;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSchema\XML\Constants\NS was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
17
use function array_diff;
18
use function array_map;
19
use function array_search;
20
use function defined;
21
use function implode;
22
use function in_array;
23
use function is_array;
24
use function rtrim;
25
use function sprintf;
26
27
/**
28
 * Trait grouping common functionality for elements implementing the xs:any element.
29
 *
30
 * @package simplesamlphp/xml-common
31
 */
32
trait ExtendableElementTrait
33
{
34
    /** @var \SimpleSAML\XML\SerializableElementInterface[] */
35
    protected array $elements = [];
36
37
38
    /**
39
     * Parse an XML document and get the child elements from the specified namespace(s).
40
     * The namespace defaults to the XS_ANY_ELT_NAMESPACE constant on the element.
41
     * NOTE: In case the namespace is ##any, this method will also return local non-namespaced elements!
42
     *
43
     * @param \DOMElement $xml
44
     * @param string|string[]|null $namespace
45
     * @return list<\SimpleSAML\XML\SerializableElementInterface> $elements
46
     */
47
    protected static function getChildElementsFromXML(
48
        DOMElement $xml,
49
        string|array|null $namespace = null,
50
    ): array {
51
        $namespace = $namespace ?? static::XS_ANY_ELT_NAMESPACE;
52
        $exclusionList = self::getElementExclusions();
53
        $registry = ElementRegistry::getInstance();
54
        $elements = [];
55
56
        // Validate namespace value
57
        if (!is_array($namespace)) {
58
            // Must be one of the predefined values
59
            Assert::oneOf($namespace, NS::$PREDEFINED);
60
61
            foreach ($xml->childNodes as $elt) {
62
                if (!($elt instanceof DOMElement)) {
63
                    continue;
64
                } elseif (
65
                    $exclusionList
0 ignored issues
show
Bug Best Practice introduced by
The expression $exclusionList of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
66
                    && (in_array([$elt->namespaceURI, $elt->localName], $exclusionList, true)
67
                        || in_array([$elt->namespaceURI, '*'], $exclusionList, true))
68
                ) {
69
                    continue;
70
                } elseif ($namespace === NS::OTHER && in_array($elt->namespaceURI, [self::NS, null], true)) {
0 ignored issues
show
Bug introduced by
The constant SimpleSAML\XML\ExtendableElementTrait::NS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
71
                    continue;
72
                } elseif ($namespace === NS::TARGETNAMESPACE && $elt->namespaceURI !== self::NS) {
73
                    continue;
74
                } elseif ($namespace === NS::LOCAL && $elt->namespaceURI !== null) {
75
                    continue;
76
                }
77
78
                $handler = $registry->getElementHandler($elt->namespaceURI, $elt->localName);
79
                $elements[] = ($handler === null) ? Chunk::fromXML($elt) : $handler::fromXML($elt);
80
            }
81
        } else {
82
            // Array must be non-empty and cannot contain ##any or ##other
83
            Assert::notEmpty($namespace);
84
            Assert::allStringNotEmpty($namespace);
85
            Assert::allNotSame($namespace, NS::ANY);
86
            Assert::allNotSame($namespace, NS::OTHER);
87
88
            // Replace the ##targetedNamespace with the actual namespace
89
            if (($key = array_search(NS::TARGETNAMESPACE, $namespace)) !== false) {
90
                $namespace[$key] = self::NS;
91
            }
92
93
            // Replace the ##local with null
94
            if (($key = array_search(NS::LOCAL, $namespace)) !== false) {
95
                $namespace[$key] = null;
96
            }
97
98
            foreach ($xml->childNodes as $elt) {
99
                if (!($elt instanceof DOMElement)) {
100
                    continue;
101
                } elseif (in_array([$elt->namespaceURI, $elt->localName], $exclusionList, true)) {
102
                    continue;
103
                } elseif (!in_array($elt->namespaceURI, $namespace, true)) {
104
                    continue;
105
                }
106
107
                $handler = $registry->getElementHandler($elt->namespaceURI, $elt->localName);
108
                $elements[] = ($handler === null) ? Chunk::fromXML($elt) : $handler::fromXML($elt);
109
            }
110
        }
111
112
        return $elements;
113
    }
114
115
116
    /**
117
     * Set an array with all elements present.
118
     *
119
     * @param \SimpleSAML\XML\SerializableElementInterface[] $elements
120
     */
121
    protected function setElements(array $elements): void
122
    {
123
        Assert::maxCount($elements, C::UNBOUNDED_LIMIT);
124
        Assert::allIsInstanceOf($elements, SerializableElementInterface::class);
125
126
        $namespace = $this->getElementNamespace();
127
        // Validate namespace value
128
        if (!is_array($namespace)) {
129
            // Must be one of the predefined values
130
            Assert::oneOf($namespace, NS::$PREDEFINED);
131
        } else {
132
            // Array must be non-empty and cannot contain ##any or ##other
133
            Assert::notEmpty($namespace);
134
            Assert::allNotSame($namespace, NS::ANY);
135
            Assert::allNotSame($namespace, NS::OTHER);
136
        }
137
138
        // Get namespaces for all elements
139
        /** @var array<\SimpleSAML\XML\AbstractElement|\SimpleSAML\XML\Chunk> $elements */
140
        $actual_namespaces = array_map(
141
            /**
142
             * @return string|null
143
             */
144
            function (AbstractElement|Chunk $elt): ?string {
145
                return ($elt instanceof Chunk) ? $elt->getNamespaceURI() : $elt::getNamespaceURI();
146
            },
147
            $elements,
148
        );
149
150
        if ($namespace === NS::LOCAL) {
151
            // If ##local then all namespaces must be null
152
            Assert::allNull($actual_namespaces, SchemaViolationException::class);
153
        } elseif (is_array($namespace)) {
154
            // Make a local copy of the property that we can edit
155
            $allowed_namespaces = $namespace;
156
157
            // Replace the ##targetedNamespace with the actual namespace
158
            if (($key = array_search(NS::TARGETNAMESPACE, $allowed_namespaces)) !== false) {
159
                $allowed_namespaces[$key] = self::NS;
160
            }
161
162
            // Replace the ##local with null
163
            if (($key = array_search(NS::LOCAL, $allowed_namespaces)) !== false) {
164
                $allowed_namespaces[$key] = null;
165
            }
166
167
            $diff = array_diff($actual_namespaces, $allowed_namespaces);
168
            Assert::isEmpty(
169
                $diff,
170
                sprintf(
171
                    'Elements from namespaces [ %s ] are not allowed inside a %s element.',
172
                    rtrim(implode(', ', $diff)),
173
                    self::NS,
174
                ),
175
                SchemaViolationException::class,
176
            );
177
        } elseif ($namespace === NS::OTHER) {
178
            // Must be any namespace other than the parent element, excluding elements with no namespace
179
            Assert::notInArray(null, $actual_namespaces, SchemaViolationException::class);
180
            Assert::allNotSame($actual_namespaces, self::NS, SchemaViolationException::class);
181
        } elseif ($namespace === NS::TARGETNAMESPACE) {
182
            // Must be the same namespace as the one of the parent element
183
            Assert::allSame($actual_namespaces, self::NS, SchemaViolationexception::class);
184
        } else {
185
            // XS_ANY_NS_ANY
186
        }
187
188
        $exclusionList = self::getElementExclusions();
189
        foreach ($elements as $i => $elt) {
190
            Assert::true(
191
                !in_array([$elt->getNamespaceURI(), $elt->getLocalName()], $exclusionList, true)
192
                && !in_array([$elt->getNamespaceURI(), '*'], $exclusionList, true),
193
                InvalidDOMElementException::class,
194
            );
195
        }
196
197
        $this->elements = $elements;
198
    }
199
200
201
    /**
202
     * Get an array with all elements present.
203
     *
204
     * @return \SimpleSAML\XML\SerializableElementInterface[]
205
     */
206
    public function getElements(): array
207
    {
208
        return $this->elements;
209
    }
210
211
212
    /**
213
     * @return string|string[]
214
     */
215
    public function getElementNamespace(): array|string
216
    {
217
        Assert::true(
218
            defined('static::XS_ANY_ELT_NAMESPACE'),
219
            self::getClassName(self::class)
220
            . '::XS_ANY_ELT_NAMESPACE constant must be defined and set to the namespace for the xs:any element.',
221
            RuntimeException::class,
222
        );
223
224
        return static::XS_ANY_ELT_NAMESPACE;
225
    }
226
227
228
    /**
229
     * Get the exclusions list for getChildElementsFromXML.
230
     *
231
     * @return array{array{string|null, string}}|array{}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{array{string|null, string}}|array{} at position 2 could not be parsed: Expected ':' at position 2, but found 'array'.
Loading history...
232
     */
233
    public static function getElementExclusions(): array
234
    {
235
        if (defined('static::XS_ANY_ELT_EXCLUSIONS')) {
236
            return static::XS_ANY_ELT_EXCLUSIONS;
237
        }
238
239
        return [];
240
    }
241
}
242