Passed
Pull Request — master (#55)
by Tim
02:00
created

AbstractElement::getNamespacePrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 11
rs 10
cc 1
nc 1
nop 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
     * @template T of \SimpleSAML\XML\Type\ValueTypeInterface
59
     * @param \DOMElement     $xml The element where we should search for the attribute.
60
     * @param string          $name The name of the attribute.
61
     * @param class-string<T> $type The type of the attribute value.
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
62
     * @return T
63
     *
64
     * @throws \SimpleSAML\XML\Exception\MissingAttributeException if the attribute is missing from the element
65
     */
66
    public static function getAttribute(
67
        DOMElement $xml,
68
        string $name,
69
        string $type = StringValue::class,
70
    ): ValueTypeInterface {
71
        Assert::isAOf($type, ValueTypeInterface::class);
72
73
        $prefix = static::getNamespacePrefix();
74
        $localName = static::getLocalName();
75
        $qName = $prefix ? ($prefix . ':' . $localName) : $localName;
76
        Assert::true(
77
            $xml->hasAttribute($name),
78
            sprintf('Missing \'%s\' attribute on %s.', $name, $qName),
79
            MissingAttributeException::class,
80
        );
81
82
        $value = $xml->getAttribute($name);
83
        return $type::fromString($value);
84
    }
85
86
87
    /**
88
     * Get the value of an attribute from a given element.
89
     *
90
     * @template T of \SimpleSAML\XML\Type\ValueTypeInterface
91
     * @param \DOMElement  $xml The element where we should search for the attribute.
92
     * @param string       $name The name of the attribute.
93
     * @param class-string<T> $type The type of the attribute value.
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
94
     * @param \SimpleSAML\XML\Type\ValueTypeInterface|null $default
95
     *   The default to return in case the attribute does not exist and it is optional.
96
     * @return ($default is \SimpleSAML\XML\Type\ValueTypeInterface ? T : T|null)
97
     */
98
    public static function getOptionalAttribute(
99
        DOMElement $xml,
100
        string $name,
101
        string $type = StringValue::class,
102
        ?ValueTypeInterface $default = null,
103
    ): ?ValueTypeInterface {
104
        Assert::nullOrIsInstanceOf($default, $type);
105
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