Passed
Push — master ( 3b7f5f...a1520d )
by Tim
01:35
created

getAttributesNSFromXML()   C

Complexity

Conditions 17
Paths 9

Size

Total Lines 58
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 17
eloc 32
c 1
b 0
f 0
nc 9
nop 2
dl 0
loc 58
rs 5.2166

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\Assert\Assert;
10
use SimpleSAML\XML\Attribute;
11
use SimpleSAML\XML\Constants as C;
12
13
use function array_diff;
14
use function array_map;
15
use function array_search;
16
use function defined;
17
use function implode;
18
use function is_array;
19
use function rtrim;
20
use function sprintf;
21
22
/**
23
 * Trait for elements that can have arbitrary namespaced attributes.
24
 *
25
 * @package simplesamlphp/xml-common
26
 */
27
trait ExtendableAttributesTrait
28
{
29
    /**
30
     * Extra (namespace qualified) attributes.
31
     *
32
     * @var list<\SimpleSAML\XML\Attribute>
33
     */
34
    protected array $namespacedAttributes = [];
35
36
37
    /**
38
     * Check if a namespace-qualified attribute exists.
39
     *
40
     * @param string|null $namespaceURI The namespace URI.
41
     * @param string $localName The local name.
42
     * @return bool true if the attribute exists, false if not.
43
     */
44
    public function hasAttributeNS(?string $namespaceURI, string $localName): bool
45
    {
46
        foreach ($this->getAttributesNS() as $attr) {
47
            if ($attr->getNamespaceURI() === $namespaceURI && $attr->getAttrName() === $localName) {
48
                return true;
49
            }
50
        }
51
        return false;
52
    }
53
54
55
    /**
56
     * Get a namespace-qualified attribute.
57
     *
58
     * @param string|null $namespaceURI The namespace URI.
59
     * @param string $localName The local name.
60
     * @return \SimpleSAML\XML\Attribute|null The value of the attribute, or null if the attribute does not exist.
61
     */
62
    public function getAttributeNS(?string $namespaceURI, string $localName): ?Attribute
63
    {
64
        foreach ($this->getAttributesNS() as $attr) {
65
            if ($attr->getNamespaceURI() === $namespaceURI && $attr->getAttrName() === $localName) {
66
                return $attr;
67
            }
68
        }
69
        return null;
70
    }
71
72
73
    /**
74
     * Get the namespaced attributes in this element.
75
     *
76
     * @return list<\SimpleSAML\XML\Attribute>
77
     */
78
    public function getAttributesNS(): array
79
    {
80
        return $this->namespacedAttributes;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->namespacedAttributes returns the type array which is incompatible with the documented return type SimpleSAML\XML\list.
Loading history...
81
    }
82
83
84
    /**
85
     * Parse an XML document and get the namespaced attributes from the specified namespace(s).
86
     * The namespace defaults to the XS_ANY_ATTR_NAMESPACE constant on the element.
87
     * NOTE: In case the namespace is ##any, this method will also return local non-namespaced attributes!
88
     *
89
     * @param \DOMElement $xml
90
     * @param string|array|null $namespace
91
     *
92
     * @return list<\SimpleSAML\XML\Attribute> $attributes
93
     */
94
    protected static function getAttributesNSFromXML(DOMElement $xml, string|array $namespace = null): array
95
    {
96
        $namespace = $namespace ?? static::XS_ANY_ATTR_NAMESPACE;
1 ignored issue
show
Bug introduced by
The constant SimpleSAML\XML\Extendabl...::XS_ANY_ATTR_NAMESPACE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
97
        $attributes = [];
98
99
        // Validate namespace value
100
        if (!is_array($namespace)) {
101
            // Must be one of the predefined values
102
            Assert::oneOf($namespace, C::XS_ANY_NS);
103
104
            if ($namespace === C::XS_ANY_NS_ANY) {
105
                foreach ($xml->attributes as $a) {
106
                    $attributes[] = new Attribute($a->namespaceURI, $a->prefix, $a->localName, $a->nodeValue);
107
                }
108
            } elseif ($namespace === C::XS_ANY_NS_LOCAL) {
109
                foreach ($xml->attributes as $a) {
110
                    if ($a->namespaceURI === null) {
111
                        $attributes[] = new Attribute($a->namespaceURI, $a->prefix, $a->localName, $a->nodeValue);
112
                    }
113
                }
114
            } elseif ($namespace === C::XS_ANY_NS_OTHER) {
115
                foreach ($xml->attributes as $a) {
116
                    if ($a->namespaceURI !== static::NS) {
1 ignored issue
show
Bug introduced by
The constant SimpleSAML\XML\ExtendableAttributesTrait::NS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
117
                        $attributes[] = new Attribute($a->namespaceURI, $a->prefix, $a->localName, $a->nodeValue);
118
                    }
119
                }
120
            } elseif ($namespace === C::XS_ANY_NS_TARGET) {
121
                foreach ($xml->attributes as $a) {
122
                    if ($a->namespaceURI === static::NS) {
123
                        $attributes[] = new Attribute($a->namespaceURI, $a->prefix, $a->localName, $a->nodeValue);
124
                    }
125
                }
126
            }
127
        } else {
128
            // Array must be non-empty and cannot contain ##any or ##other
129
            Assert::notEmpty($namespace);
130
            Assert::allStringNotEmpty($namespace);
131
            Assert::allNotSame($namespace, C::XS_ANY_NS_ANY);
132
            Assert::allNotSame($namespace, C::XS_ANY_NS_OTHER);
133
134
            // Replace the ##targetedNamespace with the actual namespace
135
            if (($key = array_search(C::XS_ANY_NS_TARGET, $namespace)) !== false) {
136
                $namespace[$key] = static::NS;
137
            }
138
139
            // Replace the ##local with null
140
            if (($key = array_search(C::XS_ANY_NS_LOCAL, $namespace)) !== false) {
141
                $namespace[$key] = null;
142
            }
143
144
            foreach ($xml->attributes as $a) {
145
                if (in_array($a->namespaceURI, $namespace, true)) {
146
                    $attributes[] = new Attribute($a->namespaceURI, $a->prefix, $a->localName, $a->nodeValue);
147
                }
148
            }
149
        }
150
151
        return $attributes;
152
    }
153
154
155
    /**
156
     * @param list<\SimpleSAML\XML\Attribute> $attributes
157
     * @throws \SimpleSAML\Assert\AssertionFailedException if $attributes contains anything other than Attribute objects
158
     */
159
    protected function setAttributesNS(array $attributes): void
160
    {
161
        Assert::maxCount($attributes, C::UNBOUNDED_LIMIT);
162
        Assert::allIsInstanceOf(
163
            $attributes,
164
            Attribute::class,
165
            'Arbitrary XML attributes can only be an instance of Attribute.',
166
        );
167
        $namespace = $this->getAttributeNamespace();
168
169
        // Validate namespace value
170
        if (!is_array($namespace)) {
171
            // Must be one of the predefined values
172
            Assert::oneOf($namespace, C::XS_ANY_NS);
173
        } else {
174
            // Array must be non-empty and cannot contain ##any or ##other
175
            Assert::notEmpty($namespace);
176
            Assert::allStringNotEmpty($namespace);
177
            Assert::allNotSame($namespace, C::XS_ANY_NS_ANY);
178
            Assert::allNotSame($namespace, C::XS_ANY_NS_OTHER);
179
        }
180
181
        // Get namespaces for all attributes
182
        $actual_namespaces = array_map(
183
            /**
184
             * @param \SimpleSAML\XML\Attribute $elt
185
             * @return string|null
186
             */
187
            function (Attribute $attr) {
188
                return $attr->getNamespaceURI();
189
            },
190
            $attributes,
191
        );
192
193
        if ($namespace === C::XS_ANY_NS_LOCAL) {
194
            // If ##local then all namespaces must be null
195
            Assert::allNull($actual_namespaces);
196
        } elseif (is_array($namespace)) {
197
            // Make a local copy of the property that we can edit
198
            $allowed_namespaces = $namespace;
199
200
            // Replace the ##targetedNamespace with the actual namespace
201
            if (($key = array_search(C::XS_ANY_NS_TARGET, $allowed_namespaces)) !== false) {
202
                $allowed_namespaces[$key] = static::NS;
1 ignored issue
show
Bug introduced by
The constant SimpleSAML\XML\ExtendableAttributesTrait::NS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
203
            }
204
205
            // Replace the ##local with null
206
            if (($key = array_search(C::XS_ANY_NS_LOCAL, $allowed_namespaces)) !== false) {
207
                $allowed_namespaces[$key] = null;
208
            }
209
210
            $diff = array_diff($actual_namespaces, $allowed_namespaces);
211
            Assert::isEmpty(
212
                $diff,
213
                sprintf(
214
                    'Attributes from namespaces [ %s ] are not allowed inside a %s element.',
215
                    rtrim(implode(', ', $diff)),
216
                    static::NS,
217
                ),
218
            );
219
        } else {
220
            // All attributes must be namespaced, ergo non-null
221
            Assert::allNotNull($actual_namespaces);
222
223
            if ($namespace === C::XS_ANY_NS_OTHER) {
224
                // Must be any namespace other than the parent element
225
                Assert::allNotSame($actual_namespaces, static::NS);
226
            } elseif ($namespace === C::XS_ANY_NS_TARGET) {
227
                // Must be the same namespace as the one of the parent element
228
                Assert::allSame($actual_namespaces, static::NS);
229
            }
230
        }
231
232
        $this->namespacedAttributes = $attributes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $attributes of type array is incompatible with the declared type SimpleSAML\XML\list of property $namespacedAttributes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
233
    }
234
235
236
237
    /**
238
     * @return array|string
239
     */
240
    public function getAttributeNamespace(): array|string
241
    {
242
        Assert::true(
243
            defined('static::XS_ANY_ATTR_NAMESPACE'),
244
            self::getClassName(static::class)
245
            . '::XS_ANY_ATTR_NAMESPACE constant must be defined and set to the namespace for the xs:anyAttribute.',
246
            RuntimeException::class,
247
        );
248
249
        return static::XS_ANY_ATTR_NAMESPACE;
1 ignored issue
show
Bug introduced by
The constant SimpleSAML\XML\Extendabl...::XS_ANY_ATTR_NAMESPACE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
250
    }
251
}
252