Issues (21)

src/AbstractElement.php (3 issues)

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
                $ret[] = static::fromXML($node);
157
            }
158
        }
159
160
        return $ret;
161
    }
162
163
164
    /**
165
     * Get the namespace for the element.
166
     *
167
     * @return string|null
168
     */
169
    public static function getNamespaceURI(): ?string
170
    {
171
        Assert::true(
172
            defined('static::NS'),
173
            self::getClassName(static::class)
174
            . '::NS constant must be defined and set to the namespace for the XML-class it represents.',
175
            RuntimeException::class,
176
        );
177
        // @phpstan-ignore classConstant.notFound
178
        Assert::nullOrValidURI(static::NS, SchemaViolationException::class); // @phpstan-ignore-line
179
180
        // @phpstan-ignore classConstant.notFound
181
        return static::NS; // @phpstan-ignore-line
182
    }
183
184
185
    /**
186
     * Get the namespace-prefix for the element.
187
     *
188
     * @return string
189
     */
190
    public static function getNamespacePrefix(): string
191
    {
192
        Assert::true(
193
            defined('static::NS_PREFIX'),
194
            self::getClassName(static::class)
195
            . '::NS_PREFIX constant must be defined and set to the namespace prefix for the XML-class it represents.',
196
            RuntimeException::class,
197
        );
198
199
        // @phpstan-ignore classConstant.notFound
200
        return strval(static::NS_PREFIX); // @phpstan-ignore-line
201
    }
202
203
204
    /**
205
     * Get the local name for the element.
206
     *
207
     * @return string
208
     */
209
    public static function getLocalName(): string
210
    {
211
        if (defined('static::LOCALNAME')) {
212
            $ncName = static::LOCALNAME;
213
        } else {
214
            $ncName = self::getClassName(static::class);
215
        }
216
217
        Assert::validNCName($ncName, SchemaViolationException::class);
218
        return $ncName;
219
    }
220
221
222
    /**
223
     * Test if an object, at the state it's in, would produce an empty XML-element
224
     *
225
     * @codeCoverageIgnore
226
     * @return bool
227
     */
228
    public function isEmptyElement(): bool
229
    {
230
        return false;
231
    }
232
}
233