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

AbstractElement::getBooleanAttribute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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