Passed
Pull Request — master (#55)
by Tim
01:58
created

AbstractElement::getIntegerAttribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
nc 2
nop 2
dl 0
loc 13
rs 10
c 1
b 0
f 0
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\Exception\MissingAttributeException;
11
use SimpleSAML\XML\Exception\SchemaViolationException;
12
use SimpleSAML\XML\SerializableElementTrait;
13
use SimpleSAML\XML\Type\{StringValue, ValueTypeInterface};
14
15
use function array_slice;
16
use function defined;
17
use function explode;
18
use function join;
19
use function strval;
20
21
/**
22
 * Abstract class to be implemented by all classes
23
 *
24
 * @package simplesamlphp/xml-common
25
 */
26
abstract class AbstractElement implements SerializableElementInterface
27
{
28
    use SerializableElementTrait;
29
30
31
    /**
32
     * Create a document structure for this element
33
     *
34
     * @param \DOMElement|null $parent The element we should append to.
35
     * @return \DOMElement
36
     */
37
    public function instantiateParentElement(?DOMElement $parent = null): DOMElement
38
    {
39
        $qualifiedName = $this->getQualifiedName();
40
        $namespace = static::getNamespaceURI();
41
42
        if ($parent === null) {
43
            $parent = DOMDocumentFactory::create();
44
            $e = $parent->createElementNS($namespace, $qualifiedName);
45
        } else {
46
            $doc = $parent->ownerDocument;
47
            Assert::notNull($doc);
48
            $e = $doc->createElementNS($namespace, $qualifiedName);
49
        }
50
51
        $parent->appendChild($e);
52
53
        return $e;
54
    }
55
56
57
    /**
58
     * Get the value of an attribute from a given element.
59
     *
60
     * @param \DOMElement $xml The element where we should search for the attribute.
61
     * @param string      $name The name of the attribute.
62
     * @param string      $type The type of the attribute value.
63
     * @return \SimpleSAML\XML\Type\ValueTypeInterface
64
     *
65
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the attribute is missing from the element
66
     */
67
    public static function getAttribute(
68
        DOMElement $xml,
69
        string $name,
70
        string $type = StringValue::class,
71
    ): ValueTypeInterface {
72
        Assert::isAOf($type, ValueTypeInterface::class);
73
74
        $prefix = static::getNamespacePrefix();
75
        $localName = static::getLocalName();
76
        $qName = $prefix ? ($prefix . ':' . $localName) : $localName;
77
        Assert::true(
78
            $xml->hasAttribute($name),
79
            sprintf('Missing \'%s\' attribute on %s.', $name, $qName),
80
            MissingAttributeException::class,
81
        );
82
83
        $value = $xml->getAttribute($name);
84
        return $type::fromString($value);
85
    }
86
87
88
    /**
89
     * Get the value of an attribute from a given element.
90
     *
91
     * @param \DOMElement $xml The element where we should search for the attribute.
92
     * @param string      $name The name of the attribute.
93
     * @param string      $type The type of the attribute value.
94
     * @param string|null $default The default to return in case the attribute does not exist and it is optional.
95
     * @return ($default is \SimpleSAML\XML\Type\ValueTypeInterface ? \SimpleSAML\XML\Type\ValueTypeInterface : \SimpleSAML\XML\Type\ValueTypeInterface|null)
96
     */
97
    public static function getOptionalAttribute(
98
        DOMElement $xml,
99
        string $name,
100
        string $type = StringValue::class,
101
        ?ValueTypeInterface $default = null,
102
    ): ?ValueTypeInterface {
103
        if (!$xml->hasAttribute($name)) {
104
            return $default;
105
        }
106
107
        return self::getAttribute($xml, $name, $type);
108
    }
109
110
111
    /**
112
     * Static method that processes a fully namespaced class name and returns the name of the class from it.
113
     *
114
     * @param string $class
115
     * @return string
116
     */
117
    public static function getClassName(string $class): string
118
    {
119
        $ncName = join('', array_slice(explode('\\', $class), -1));
120
        Assert::validNCName($ncName, SchemaViolationException::class);
121
        return $ncName;
122
    }
123
124
125
    /**
126
     * Get the XML qualified name (prefix:name) of the element represented by this class.
127
     *
128
     * @return string
129
     */
130
    public function getQualifiedName(): string
131
    {
132
        $prefix = static::getNamespacePrefix();
133
        $qName = $prefix ? ($prefix . ':' . static::getLocalName()) : static::getLocalName();
134
        Assert::validQName($qName, SchemaViolationException::class);
135
        return $qName;
136
    }
137
138
139
    /**
140
     * Extract localized names from the children of a given element.
141
     *
142
     * @param \DOMElement $parent The element we want to search.
143
     * @return array<static> An array of objects of this class.
144
     */
145
    public static function getChildrenOfClass(DOMElement $parent): array
146
    {
147
        $ret = [];
148
        foreach ($parent->childNodes as $node) {
149
            if (
150
                $node instanceof DOMElement
151
                && $node->namespaceURI === static::getNamespaceURI()
152
                && $node->localName === static::getLocalName()
153
            ) {
154
                // Normalize the DOMElement by importing it into a clean empty document
155
                $newDoc = DOMDocumentFactory::create();
156
                /** @var \DOMElement $node */
157
                $node = $newDoc->appendChild($newDoc->importNode($node, true));
158
159
                $ret[] = static::fromXML($node);
160
            }
161
        }
162
163
        return $ret;
164
    }
165
166
167
    /**
168
     * Get the namespace for the element.
169
     *
170
     * @return string|null
171
     */
172
    public static function getNamespaceURI(): ?string
173
    {
174
        Assert::true(
175
            defined('static::NS'),
176
            self::getClassName(static::class)
177
            . '::NS constant must be defined and set to the namespace for the XML-class it represents.',
178
            RuntimeException::class,
179
        );
180
        // @phpstan-ignore classConstant.notFound
181
        Assert::nullOrValidURI(static::NS, SchemaViolationException::class); // @phpstan-ignore-line
182
183
        // @phpstan-ignore classConstant.notFound
184
        return static::NS; // @phpstan-ignore-line
185
    }
186
187
188
    /**
189
     * Get the namespace-prefix for the element.
190
     *
191
     * @return string
192
     */
193
    public static function getNamespacePrefix(): string
194
    {
195
        Assert::true(
196
            defined('static::NS_PREFIX'),
197
            self::getClassName(static::class)
198
            . '::NS_PREFIX constant must be defined and set to the namespace prefix for the XML-class it represents.',
199
            RuntimeException::class,
200
        );
201
202
        // @phpstan-ignore classConstant.notFound
203
        return strval(static::NS_PREFIX); // @phpstan-ignore-line
204
    }
205
206
207
    /**
208
     * Get the local name for the element.
209
     *
210
     * @return string
211
     */
212
    public static function getLocalName(): string
213
    {
214
        if (defined('static::LOCALNAME')) {
215
            $ncName = static::LOCALNAME;
216
        } else {
217
            $ncName = self::getClassName(static::class);
218
        }
219
220
        Assert::validNCName($ncName, SchemaViolationException::class);
221
        return $ncName;
222
    }
223
224
225
    /**
226
     * Test if an object, at the state it's in, would produce an empty XML-element
227
     *
228
     * @codeCoverageIgnore
229
     * @return bool
230
     */
231
    public function isEmptyElement(): bool
232
    {
233
        return false;
234
    }
235
}
236