Completed
Push — master ( f9cdf8...850158 )
by Tim
19s queued 15s
created

ExtendableElementTrait::getChildElementsFromXML()   D

Complexity

Conditions 19
Paths 16

Size

Total Lines 60
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 38
c 2
b 0
f 0
dl 0
loc 60
rs 4.5166
cc 19
nc 16
nop 2

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\Chunk;
11
use SimpleSAML\XML\Constants as C;
12
use SimpleSAML\XML\Registry\ElementRegistry;
13
use SimpleSAML\XML\XsNamespace as NS;
14
15
use function array_diff;
16
use function array_map;
17
use function array_search;
18
use function defined;
19
use function implode;
20
use function is_array;
21
use function rtrim;
22
use function sprintf;
23
24
/**
25
 * Trait grouping common functionality for elements implementing the xs:any element.
26
 *
27
 * @package simplesamlphp/xml-common
28
 */
29
trait ExtendableElementTrait
30
{
31
    /** @var \SimpleSAML\XML\SerializableElementInterface[] */
32
    protected array $elements = [];
33
34
35
    /**
36
     * Parse an XML document and get the child elements from the specified namespace(s).
37
     * The namespace defaults to the XS_ANY_ELT_NAMESPACE constant on the element.
38
     * NOTE: In case the namespace is ##any, this method will also return local non-namespaced elements!
39
     *
40
     * @param \DOMElement $xml
41
     * @param \SimpleSAML\XML\XsNamespace|array|null $namespace
42
     *
43
     * @return list<\SimpleSAML\XML\SerializableElementInterface> $elements
44
     */
45
    protected static function getChildElementsFromXML(DOMElement $xml, NS|array $namespace = null): array
46
    {
47
        $namespace = $namespace ?? static::XS_ANY_ELT_NAMESPACE;
1 ignored issue
show
Bug introduced by
The constant SimpleSAML\XML\Extendabl...t::XS_ANY_ELT_NAMESPACE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
48
        $exclusionList = static::getElementExclusions();
49
        $registry = ElementRegistry::getInstance();
50
        $elements = [];
51
52
        // Validate namespace value
53
        if (!is_array($namespace)) {
54
            // Must be one of the predefined values
55
            Assert::oneOf($namespace, NS::cases());
56
57
            foreach ($xml->childNodes as $elt) {
58
                if (!($elt instanceof DOMElement)) {
59
                    continue;
60
                } elseif (in_array([$elt->namespaceURI, $elt->localName], $exclusionList, true)) {
61
                    continue;
62
                } elseif ($namespace === NS::OTHER && in_array($elt->namespaceURI, [static::NS, null], true)) {
1 ignored issue
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...
63
                    continue;
64
                } elseif ($namespace === NS::TARGET && $elt->namespaceURI !== static::NS) {
65
                    continue;
66
                } elseif ($namespace === NS::LOCAL && $elt->namespaceURI !== null) {
67
                    continue;
68
                }
69
70
                $handler = $registry->getElementHandler($elt->namespaceURI, $elt->localName);
71
                $elements[] = ($handler === null) ? Chunk::fromXML($elt) : $handler::fromXML($elt);
72
            }
73
        } else {
74
            // Array must be non-empty and cannot contain ##any or ##other
75
            Assert::notEmpty($namespace);
76
            Assert::allStringNotEmpty($namespace);
77
            Assert::allNotSame($namespace, NS::ANY);
78
            Assert::allNotSame($namespace, NS::OTHER);
79
80
            // Replace the ##targetedNamespace with the actual namespace
81
            if (($key = array_search(NS::TARGET, $namespace)) !== false) {
82
                $namespace[$key] = static::NS;
83
            }
84
85
            // Replace the ##local with null
86
            if (($key = array_search(NS::LOCAL, $namespace)) !== false) {
87
                $namespace[$key] = null;
88
            }
89
90
            foreach ($xml->childNodes as $elt) {
91
                if (!($elt instanceof DOMElement)) {
92
                    continue;
93
                } elseif (in_array([$elt->namespaceURI, $elt->localName], $exclusionList, true)) {
94
                    continue;
95
                } elseif (!in_array($elt->namespaceURI, $namespace, true)) {
96
                    continue;
97
                }
98
99
                $handler = $registry->getElementHandler($elt->namespaceURI, $elt->localName);
100
                $elements[] = ($handler === null) ? Chunk::fromXML($elt) : $handler::fromXML($elt);
101
            }
102
        }
103
104
        return $elements;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $elements returns the type array|array<mixed,SimpleSAML\XML\Chunk|mixed> which is incompatible with the documented return type SimpleSAML\XML\list.
Loading history...
105
    }
106
107
108
    /**
109
     * Set an array with all elements present.
110
     *
111
     * @param \SimpleSAML\XML\SerializableElementInterface[] $elements
112
     * @return void
113
     */
114
    protected function setElements(array $elements): void
115
    {
116
        Assert::maxCount($elements, C::UNBOUNDED_LIMIT);
117
        Assert::allIsInstanceOf($elements, SerializableElementInterface::class);
118
        $namespace = $this->getElementNamespace();
119
120
        // Validate namespace value
121
        if (!is_array($namespace)) {
122
            // Must be one of the predefined values
123
            Assert::oneOf($namespace, NS::cases());
124
        } else {
125
            // Array must be non-empty and cannot contain ##any or ##other
126
            Assert::notEmpty($namespace);
127
            Assert::allNotSame($namespace, NS::ANY);
128
            Assert::allNotSame($namespace, NS::OTHER);
129
        }
130
131
        // Get namespaces for all elements
132
        $actual_namespaces = array_map(
133
            /**
134
             * @param \SimpleSAML\XML\SerializableElementInterface $elt
135
             * @return string|null
136
             */
137
            function (SerializableElementInterface $elt) {
138
                return ($elt instanceof Chunk) ? $elt->getNamespaceURI() : $elt::getNamespaceURI();
0 ignored issues
show
Bug introduced by
The method getNamespaceURI() does not exist on SimpleSAML\XML\SerializableElementInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to SimpleSAML\XML\SerializableElementInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

138
                return ($elt instanceof Chunk) ? $elt->getNamespaceURI() : $elt::/** @scrutinizer ignore-call */ getNamespaceURI();
Loading history...
139
            },
140
            $elements,
141
        );
142
143
        if ($namespace === NS::LOCAL) {
144
            // If ##local then all namespaces must be null
145
            Assert::allNull($actual_namespaces);
146
        } elseif (is_array($namespace)) {
147
            // Make a local copy of the property that we can edit
148
            $allowed_namespaces = $namespace;
149
150
            // Replace the ##targetedNamespace with the actual namespace
151
            if (($key = array_search(NS::TARGET, $allowed_namespaces)) !== false) {
152
                $allowed_namespaces[$key] = static::NS;
153
            }
154
155
            // Replace the ##local with null
156
            if (($key = array_search(NS::LOCAL, $allowed_namespaces)) !== false) {
157
                $allowed_namespaces[$key] = null;
158
            }
159
160
            $diff = array_diff($actual_namespaces, $allowed_namespaces);
161
            Assert::isEmpty(
162
                $diff,
163
                sprintf(
164
                    'Elements from namespaces [ %s ] are not allowed inside a %s element.',
165
                    rtrim(implode(', ', $diff)),
166
                    static::NS,
167
                ),
168
            );
169
        } elseif ($namespace === NS::OTHER) {
170
            // Must be any namespace other than the parent element, excluding elements with no namespace
171
            Assert::notInArray(null, $actual_namespaces);
172
            Assert::allNotSame($actual_namespaces, static::NS);
173
        } elseif ($namespace === NS::TARGET) {
174
            // Must be the same namespace as the one of the parent element
175
            Assert::allSame($actual_namespaces, static::NS);
176
        } else {
177
            // XS_ANY_NS_ANY
178
        }
179
180
        $exclusionList = static::getElementExclusions();
181
        foreach ($elements as $i => $elt) {
182
            if (in_array([$elt->getNamespaceURI(), $elt->getLocalName()], $exclusionList, true)) {
0 ignored issues
show
Bug introduced by
The method getLocalName() does not exist on SimpleSAML\XML\SerializableElementInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to SimpleSAML\XML\SerializableElementInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

182
            if (in_array([$elt->getNamespaceURI(), $elt->/** @scrutinizer ignore-call */ getLocalName()], $exclusionList, true)) {
Loading history...
183
                unset($elements[$i]);
184
            }
185
        }
186
187
        $this->elements = $elements;
188
    }
189
190
191
    /**
192
     * Get an array with all elements present.
193
     *
194
     * @return \SimpleSAML\XML\SerializableElementInterface[]
195
     */
196
    public function getElements(): array
197
    {
198
        return $this->elements;
199
    }
200
201
202
    /**
203
     * @return array|\SimpleSAML\XML\XsNamespace
204
     */
205
    public function getElementNamespace(): array|NS
206
    {
207
        Assert::true(
208
            defined('static::XS_ANY_ELT_NAMESPACE'),
209
            self::getClassName(static::class)
210
            . '::XS_ANY_ELT_NAMESPACE constant must be defined and set to the namespace for the xs:any element.',
211
            RuntimeException::class,
212
        );
213
214
        return static::XS_ANY_ELT_NAMESPACE;
1 ignored issue
show
Bug introduced by
The constant SimpleSAML\XML\Extendabl...t::XS_ANY_ELT_NAMESPACE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
215
    }
216
217
218
    /**
219
     * Get the exclusions list for getChildElementsFromXML.
220
     *
221
     * @return array<string, string>
222
     */
223
    public static function getElementExclusions(): array
224
    {
225
        if (defined('static::XS_ANY_ELT_EXCLUSIONS')) {
226
            return static::XS_ANY_ELT_EXCLUSIONS;
1 ignored issue
show
Bug introduced by
The constant SimpleSAML\XML\Extendabl...::XS_ANY_ELT_EXCLUSIONS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
227
        }
228
229
        return [];
230
    }
231
}
232