Passed
Push — master ( e83944...4f4375 )
by Tim
01:40
created

AbstractElement   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 81
dl 0
loc 279
rs 10
c 1
b 0
f 0
wmc 27

14 Methods

Rating   Name   Duplication   Size   Complexity  
A instantiateParentElement() 0 19 2
A getOptionalAttribute() 0 7 2
A getIntegerAttribute() 0 13 2
A getOptionalIntegerAttribute() 0 7 2
A getBooleanAttribute() 0 14 2
A getNamespacePrefix() 0 10 1
A getClassName() 0 5 1
A getNamespaceURI() 0 11 1
A getChildrenOfClass() 0 14 4
A getOptionalBooleanAttribute() 0 7 2
A getLocalName() 0 10 2
A getQualifiedName() 0 6 2
A getAttribute() 0 12 3
A isEmptyElement() 0 3 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XML;
6
7
use DOMDocument;
8
use DOMElement;
9
use RuntimeException;
10
use SimpleSAML\XML\Exception\MissingAttributeException;
11
use SimpleSAML\XML\Exception\SchemaViolationException;
12
use SimpleSAML\XML\SerializableElementTrait;
13
use SimpleSAML\Assert\Assert;
14
15
use function array_slice;
16
use function defined;
17
use function explode;
18
use function func_num_args;
19
use function in_array;
20
use function intval;
21
use function join;
22
23
/**
24
 * Abstract class to be implemented by all classes
25
 *
26
 * @package simplesamlphp/xml-common
27
 */
28
abstract class AbstractElement implements ElementInterface, SerializableElementInterface
29
{
30
    use SerializableElementTrait;
31
32
33
    /**
34
     * Create a document structure for this element
35
     *
36
     * @param \DOMElement|null $parent The element we should append to.
37
     * @return \DOMElement
38
     */
39
    public function instantiateParentElement(DOMElement $parent = null): DOMElement
40
    {
41
        $qualifiedName = $this->getQualifiedName();
42
        $namespace = static::getNamespaceURI();
43
44
        if ($parent === null) {
45
            $doc = new DOMDocument();
46
            $e = $doc->createElementNS($namespace, $qualifiedName);
47
            $doc->appendChild($e);
48
        } else {
49
            $doc = $parent->ownerDocument;
50
            Assert::notNull($doc);
51
52
            /** @psalm-var \DOMDocument $doc */
53
            $e = $doc->createElementNS($namespace, $qualifiedName);
54
            $parent->appendChild($e);
55
        }
56
57
        return $e;
58
    }
59
60
61
    /**
62
     * Get the value of an attribute from a given element.
63
     *
64
     * @param \DOMElement $xml The element where we should search for the attribute.
65
     * @param string      $name The name of the attribute.
66
     * @return string
67
     *
68
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the attribute is missing from the element
69
     */
70
    public static function getAttribute(DOMElement $xml, string $name): string
71
    {
72
        $prefix = static::getNamespacePrefix();
73
        $localName = static::getLocalName();
74
        $qName = $prefix ? ($prefix . ':' . $localName) : $localName;
75
        Assert::true(
76
            $xml->hasAttribute($name) && func_num_args() === 2,
77
            sprintf('Missing \'%s\' attribute on %s.', $name, $qName),
78
            MissingAttributeException::class,
79
        );
80
81
        return $xml->getAttribute($name);
82
    }
83
84
85
    /**
86
     * Get the value of an attribute from a given element.
87
     *
88
     * @param \DOMElement $xml The element where we should search for the attribute.
89
     * @param string      $name The name of the attribute.
90
     * @param string|null $default The default to return in case the attribute does not exist and it is optional.
91
     * @return string|null
92
     */
93
    public static function getOptionalAttribute(DOMElement $xml, string $name, ?string $default = null): ?string
94
    {
95
        if (!$xml->hasAttribute($name)) {
96
            return $default;
97
        }
98
99
        return self::getAttribute($xml, $name);
100
    }
101
102
103
    /**
104
     * @param \DOMElement $xml The element where we should search for the attribute.
105
     * @param string      $name The name of the attribute.
106
     * @return bool
107
     *
108
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the attribute is missing from the element
109
     * @throws \SimpleSAML\Assert\AssertionFailedException if the attribute is not a boolean
110
     */
111
    public static function getBooleanAttribute(DOMElement $xml, string $name): bool
112
    {
113
        $value = self::getAttribute($xml, $name);
114
115
        $prefix = static::getNamespacePrefix();
116
        $localName = static::getLocalName();
117
        $qName = $prefix ? ($prefix . ':' . $localName) : $localName;
118
        Assert::oneOf(
119
            $value,
120
            ['0', '1', 'false', 'true'],
121
            sprintf('The \'%s\' attribute of %s must be a boolean.', $name, $qName),
122
        );
123
124
        return in_array($value, ['1', 'true'], true);
125
    }
126
127
128
    /**
129
     * @param \DOMElement $xml The element where we should search for the attribute.
130
     * @param string      $name The name of the attribute.
131
     * @param bool|null   $default The default to return in case the attribute does not exist and it is optional.
132
     * @return bool|null
133
     *
134
     * @throws \SimpleSAML\Assert\AssertionFailedException if the attribute is not a boolean
135
     */
136
    public static function getOptionalBooleanAttribute(DOMElement $xml, string $name, ?bool $default = null): ?bool
137
    {
138
        if (!$xml->hasAttribute($name)) {
139
            return $default;
140
        }
141
142
        return self::getBooleanAttribute($xml, $name);
143
    }
144
145
146
    /**
147
     * Get the integer value of an attribute from a given element.
148
     *
149
     * @param \DOMElement $xml The element where we should search for the attribute.
150
     * @param string      $name The name of the attribute.
151
     * @return int
152
     *
153
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the attribute is missing from the element
154
     * @throws \SimpleSAML\Assert\AssertionFailedException if the attribute is not an integer
155
     */
156
    public static function getIntegerAttribute(DOMElement $xml, string $name): int
157
    {
158
        $value = self::getAttribute($xml, $name);
159
160
        $prefix = static::getNamespacePrefix();
161
        $localName = static::getLocalName();
162
        $qName = $prefix ? ($prefix . ':' . $localName) : $localName;
163
        Assert::numeric(
164
            $value,
165
            sprintf('The \'%s\' attribute of %s must be numerical.', $name, $qName),
166
        );
167
168
        return intval($value);
169
    }
170
171
172
    /**
173
     * Get the integer value of an attribute from a given element.
174
     *
175
     * @param \DOMElement $xml The element where we should search for the attribute.
176
     * @param string      $name The name of the attribute.
177
     * @param int|null    $default The default to return in case the attribute does not exist and it is optional.
178
     * @return int|null
179
     *
180
     * @throws \SimpleSAML\Assert\AssertionFailedException if the attribute is not an integer
181
     */
182
    public static function getOptionalIntegerAttribute(DOMElement $xml, string $name, ?int $default = null): ?int
183
    {
184
        if (!$xml->hasAttribute($name)) {
185
            return $default;
186
        }
187
188
        return self::getIntegerAttribute($xml, $name);
189
    }
190
191
192
    /**
193
     * Static method that processes a fully namespaced class name and returns the name of the class from it.
194
     *
195
     * @param string $class
196
     * @return string
197
     */
198
    public static function getClassName(string $class): string
199
    {
200
        $ncName = join('', array_slice(explode('\\', $class), -1));
201
        Assert::validNCName($ncName, SchemaViolationException::class);
202
        return $ncName;
203
    }
204
205
206
    /**
207
     * Get the XML qualified name (prefix:name) of the element represented by this class.
208
     *
209
     * @return string
210
     */
211
    public function getQualifiedName(): string
212
    {
213
        $prefix = static::getNamespacePrefix();
214
        $qName = $prefix ? ($prefix . ':' . static::getLocalName()) : static::getLocalName();
215
        Assert::validQName($qName, SchemaViolationException::class);
216
        return $qName;
217
    }
218
219
220
    /**
221
     * Extract localized names from the children of a given element.
222
     *
223
     * @param \DOMElement $parent The element we want to search.
224
     * @return static[] An array of objects of this class.
225
     */
226
    public static function getChildrenOfClass(DOMElement $parent): array
227
    {
228
        $ret = [];
229
        foreach ($parent->childNodes as $node) {
230
            if (
231
                $node->namespaceURI === static::getNamespaceURI()
232
                && $node->localName === static::getLocalName()
233
            ) {
234
                /** @psalm-var \DOMElement $node */
235
                $ret[] = static::fromXML($node);
236
            }
237
        }
238
239
        return $ret;
240
    }
241
242
243
    /**
244
     * Get the namespace for the element.
245
     *
246
     * @return string|null
247
     */
248
    public static function getNamespaceURI(): ?string
249
    {
250
        Assert::true(
251
            defined('static::NS'),
252
            self::getClassName(static::class)
253
            . '::NS constant must be defined and set to the namespace for the XML-class it represents.',
254
            RuntimeException::class,
255
        );
256
        Assert::nullOrValidURI(static::NS, SchemaViolationException::class);
257
258
        return static::NS;
259
    }
260
261
262
    /**
263
     * Get the namespace-prefix for the element.
264
     *
265
     * @return string|null
266
     */
267
    public static function getNamespacePrefix(): ?string
268
    {
269
        Assert::true(
270
            defined('static::NS_PREFIX'),
271
            self::getClassName(static::class)
272
            . '::NS_PREFIX constant must be defined and set to the namespace prefix for the XML-class it represents.',
273
            RuntimeException::class,
274
        );
275
276
        return static::NS_PREFIX;
277
    }
278
279
280
    /**
281
     * Get the local name for the element.
282
     *
283
     * @return string
284
     */
285
    public static function getLocalName(): string
286
    {
287
        if (defined('static::LOCALNAME')) {
288
            $ncName = static::LOCALNAME;
289
        } else {
290
            $ncName = self::getClassName(static::class);
291
        }
292
293
        Assert::validNCName($ncName, SchemaViolationException::class);
294
        return $ncName;
295
    }
296
297
298
    /**
299
     * Test if an object, at the state it's in, would produce an empty XML-element
300
     *
301
     * @codeCoverageIgnore
302
     * @return bool
303
     */
304
    public function isEmptyElement(): bool
305
    {
306
        return false;
307
    }
308
}
309