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

AbstractElement::getLocalName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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