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

AbstractElement::getChildrenOfClass()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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