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

AbstractElement::getClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
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\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
        if (!$xml->hasAttribute($name)) {
105
            return $default;
106
        }
107
108
        return self::getAttribute($xml, $name, $type);
109
    }
110
111
112
    /**
113
     * Static method that processes a fully namespaced class name and returns the name of the class from it.
114
     *
115
     * @param string $class
116
     * @return string
117
     */
118
    public static function getClassName(string $class): string
119
    {
120
        $ncName = join('', array_slice(explode('\\', $class), -1));
121
        Assert::validNCName($ncName, SchemaViolationException::class);
122
        return $ncName;
123
    }
124
125
126
    /**
127
     * Get the XML qualified name (prefix:name) of the element represented by this class.
128
     *
129
     * @return string
130
     */
131
    public function getQualifiedName(): string
132
    {
133
        $prefix = static::getNamespacePrefix();
134
        $qName = $prefix ? ($prefix . ':' . static::getLocalName()) : static::getLocalName();
135
        Assert::validQName($qName, SchemaViolationException::class);
136
        return $qName;
137
    }
138
139
140
    /**
141
     * Extract localized names from the children of a given element.
142
     *
143
     * @param \DOMElement $parent The element we want to search.
144
     * @return array<static> An array of objects of this class.
145
     */
146
    public static function getChildrenOfClass(DOMElement $parent): array
147
    {
148
        $ret = [];
149
        foreach ($parent->childNodes as $node) {
150
            if (
151
                $node instanceof DOMElement
152
                && $node->namespaceURI === static::getNamespaceURI()
153
                && $node->localName === static::getLocalName()
154
            ) {
155
                // Normalize the DOMElement by importing it into a clean empty document
156
                $newDoc = DOMDocumentFactory::create();
157
                /** @var \DOMElement $node */
158
                $node = $newDoc->appendChild($newDoc->importNode($node, true));
159
160
                $ret[] = static::fromXML($node);
161
            }
162
        }
163
164
        return $ret;
165
    }
166
167
168
    /**
169
     * Get the namespace for the element.
170
     *
171
     * @return string|null
172
     */
173
    public static function getNamespaceURI(): ?string
174
    {
175
        Assert::true(
176
            defined('static::NS'),
177
            self::getClassName(static::class)
178
            . '::NS constant must be defined and set to the namespace for the XML-class it represents.',
179
            RuntimeException::class,
180
        );
181
        // @phpstan-ignore classConstant.notFound
182
        Assert::nullOrValidURI(static::NS, SchemaViolationException::class); // @phpstan-ignore-line
183
184
        // @phpstan-ignore classConstant.notFound
185
        return static::NS; // @phpstan-ignore-line
186
    }
187
188
189
    /**
190
     * Get the namespace-prefix for the element.
191
     *
192
     * @return string
193
     */
194
    public static function getNamespacePrefix(): string
195
    {
196
        Assert::true(
197
            defined('static::NS_PREFIX'),
198
            self::getClassName(static::class)
199
            . '::NS_PREFIX constant must be defined and set to the namespace prefix for the XML-class it represents.',
200
            RuntimeException::class,
201
        );
202
203
        // @phpstan-ignore classConstant.notFound
204
        return strval(static::NS_PREFIX); // @phpstan-ignore-line
205
    }
206
207
208
    /**
209
     * Get the local name for the element.
210
     *
211
     * @return string
212
     */
213
    public static function getLocalName(): string
214
    {
215
        if (defined('static::LOCALNAME')) {
216
            $ncName = static::LOCALNAME;
217
        } else {
218
            $ncName = self::getClassName(static::class);
219
        }
220
221
        Assert::validNCName($ncName, SchemaViolationException::class);
222
        return $ncName;
223
    }
224
225
226
    /**
227
     * Test if an object, at the state it's in, would produce an empty XML-element
228
     *
229
     * @codeCoverageIgnore
230
     * @return bool
231
     */
232
    public function isEmptyElement(): bool
233
    {
234
        return false;
235
    }
236
}
237