XmlSerializationVisitor::visitArray()   F
last analyzed

Complexity

Conditions 15
Paths 6272

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 15.0549

Importance

Changes 0
Metric Value
cc 15
eloc 19
nc 6272
nop 2
dl 0
loc 31
ccs 15
cts 16
cp 0.9375
crap 15.0549
rs 1.7499
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace JMS\Serializer;
6
7
use JMS\Serializer\Exception\NotAcceptableException;
8
use JMS\Serializer\Exception\RuntimeException;
9
use JMS\Serializer\Metadata\ClassMetadata;
10
use JMS\Serializer\Metadata\PropertyMetadata;
11
use JMS\Serializer\Type\Type;
12
use JMS\Serializer\Visitor\SerializationVisitorInterface;
13
14
/**
15
 * @author Johannes M. Schmitt <[email protected]>
16
 *
17
 * @phpstan-import-type TypeArray from Type
18
 */
19
final class XmlSerializationVisitor extends AbstractVisitor implements SerializationVisitorInterface
20
{
21
    /**
22
     * @var \DOMDocument
23
     */
24
    private $document;
25
26
    /**
27
     * @var string
28
     */
29
    private $defaultRootName;
30
31
    /**
32
     * @var string|null
33
     */
34
    private $defaultRootNamespace;
35
36 121
    /**
37
     * @var string|null
38
     */
39
    private $defaultRootPrefix;
40
41
    /**
42
     * @var \SplStack
43
     */
44 121
    private $stack;
45 121
46 121
    /**
47
     * @var \SplStack
48 121
     */
49 121
    private $metadataStack;
50
51 121
    /**
52
     * @var \DOMNode|\DOMElement|null
53 121
     */
54 121
    private $currentNode;
55 121
56 121
    /**
57
     * @var ClassMetadata|PropertyMetadata|null
58 121
     */
59
    private $currentMetadata;
60 121
61 121
    /**
62
     * @var bool
63 121
     */
64
    private $hasValue;
65
66 111
    /**
67
     * @var bool
68 111
     */
69 20
    private $nullWasVisited;
70 20
71 20
    /**
72
     * @var \SplStack
73 91
     */
74 91
    private $objectMetadataStack;
75 91
76
    public function __construct(
77
        bool $formatOutput = true,
78 111
        string $defaultEncoding = 'UTF-8',
79 111
        string $defaultVersion = '1.0',
80 8
        string $defaultRootName = 'result',
81
        ?string $defaultRootNamespace = null,
82 103
        ?string $defaultRootPrefix = null
83
    ) {
84 111
        $this->objectMetadataStack = new \SplStack();
85 111
        $this->stack = new \SplStack();
86
        $this->metadataStack = new \SplStack();
87 111
88
        $this->currentNode = null;
89
        $this->nullWasVisited = false;
90 9
91
        $this->document = $this->createDocument($formatOutput, $defaultVersion, $defaultEncoding);
92 9
93 9
        $this->defaultRootName = $defaultRootName;
94 9
        $this->defaultRootNamespace = $defaultRootNamespace;
95
        $this->defaultRootPrefix = $defaultRootPrefix;
96 9
    }
97
98
    private function createDocument(bool $formatOutput, string $defaultVersion, string $defaultEncoding): \DOMDocument
99 80
    {
100
        $document = new \DOMDocument($defaultVersion, $defaultEncoding);
101 80
        $document->formatOutput = $formatOutput;
102
103 80
        return $document;
104
    }
105
106 2
    public function createRoot(?ClassMetadata $metadata = null, ?string $rootName = null, ?string $rootNamespace = null, ?string $rootPrefix = null): \DOMElement
107
    {
108 2
        if (null !== $metadata && !empty($metadata->xmlRootName)) {
109
            $rootPrefix = $metadata->xmlRootPrefix;
110
            $rootName = $metadata->xmlRootName;
111 5
            $rootNamespace = $metadata->xmlRootNamespace ?: $this->getClassDefaultNamespace($metadata);
112
        } else {
113 5
            $rootName = $rootName ?: $this->defaultRootName;
114
            $rootNamespace = $rootNamespace ?: $this->defaultRootNamespace;
115
            $rootPrefix = $rootPrefix ?: $this->defaultRootPrefix;
116 21
        }
117
118 21
        $document = $this->getDocument();
119
        if ($rootNamespace) {
120
            $rootNode = $document->createElementNS($rootNamespace, (null !== $rootPrefix ? $rootPrefix . ':' : '') . $rootName);
121 9
        } else {
122
            $rootNode = $document->createElement($rootName);
123 9
        }
124 4
125
        $document->appendChild($rootNode);
126 6
        $this->setCurrentNode($rootNode);
127
128
        return $rootNode;
129
    }
130 32
131
    /**
132 32
     * {@inheritdoc}
133 11
     */
134
    public function visitNull($data, array $type)
135
    {
136 32
        $node = $this->document->createAttribute('xsi:nil');
137 32
        $node->value = 'true';
138 32
        $this->nullWasVisited = true;
139
140 32
        return $node;
141 32
    }
142
143 31
    /**
144
     * {@inheritdoc}
145 31
     */
146 31
    public function visitString(string $data, array $type)
147 31
    {
148
        $doCData = null !== $this->currentMetadata ? $this->currentMetadata->xmlElementCData : true;
149 31
150 7
        return $doCData ? $this->document->createCDATASection($data) : $this->document->createTextNode((string) $data);
151
    }
152
153
    /**
154 31
     * @param mixed $data
155 28
     * @param TypeArray $type
0 ignored issues
show
Bug introduced by
The type JMS\Serializer\TypeArray was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
156
     */
157 5
    public function visitSimpleString($data, array $type): \DOMText
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

157
    public function visitSimpleString($data, /** @scrutinizer ignore-unused */ array $type): \DOMText

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
158 5
    {
159
        return $this->document->createTextNode((string) $data);
160
    }
161 31
162
    /**
163 32
     * {@inheritdoc}
164
     */
165 80
    public function visitBoolean(bool $data, array $type)
166
    {
167 80
        return $this->document->createTextNode($data ? 'true' : 'false');
168
    }
169 80
170 78
    /**
171
     * {@inheritdoc}
172
     */
173 80
    public function visitInteger(int $data, array $type)
174
    {
175 80
        return $this->document->createTextNode((string) $data);
176 80
    }
177
178 79
    /**
179
     * {@inheritdoc}
180 79
     */
181 14
    public function visitDouble(float $data, array $type)
182 14
    {
183 14
        $dataResult = $data;
184
        $precision = $type['params'][0] ?? null;
185 14
        if (is_int($precision)) {
186
            $roundMode = $type['params'][1] ?? null;
187
            $roundMode = $this->mapRoundMode($roundMode);
188
            $dataResult = round($dataResult, $precision, $roundMode);
189 14
        }
190
191 14
        $decimalsNumbers = $type['params'][2] ?? null;
192
        if (null === $decimalsNumbers) {
193
            $parts = explode('.', (string) $dataResult);
194 76
            if (count($parts) < 2 || !$parts[1]) {
195 76
                $decimalsNumbers = 1;
196
            }
197 1
        }
198
199
        if (null !== $decimalsNumbers) {
200 76
            $dataResult = number_format($dataResult, $decimalsNumbers, '.', '');
201 9
        }
202
203 9
        return $this->document->createTextNode((string) $dataResult);
204 9
    }
205 9
206
    /**
207 9
     * {@inheritdoc}
208
     */
209
    public function visitArray(array $data, array $type): void
210
    {
211 9
        if (null === $this->currentNode) {
212
            $this->createRoot();
213 9
        }
214
215
        $entryName = null !== $this->currentMetadata && null !== $this->currentMetadata->xmlEntryName ? $this->currentMetadata->xmlEntryName : 'entry';
0 ignored issues
show
Bug introduced by
The property xmlEntryName does not seem to exist on JMS\Serializer\Metadata\ClassMetadata.
Loading history...
216 72
        $keyAttributeName = null !== $this->currentMetadata && null !== $this->currentMetadata->xmlKeyAttribute ? $this->currentMetadata->xmlKeyAttribute : null;
0 ignored issues
show
Bug introduced by
The property xmlKeyAttribute does not exist on JMS\Serializer\Metadata\ClassMetadata. Did you mean xmlAttribute?
Loading history...
217 2
        $namespace = null !== $this->currentMetadata && null !== $this->currentMetadata->xmlEntryNamespace ? $this->currentMetadata->xmlEntryNamespace : null;
0 ignored issues
show
Bug introduced by
The property xmlEntryNamespace does not exist on JMS\Serializer\Metadata\ClassMetadata. Did you mean xmlNamespace?
Loading history...
218 1
219
        $elType = $this->getElementType($type);
220
        foreach ($data as $k => $v) {
221 1
            $tagName = null !== $this->currentMetadata && $this->currentMetadata->xmlKeyValuePairs && $this->isElementNameValid((string) $k) ? $k : $entryName;
0 ignored issues
show
Bug introduced by
The property xmlKeyValuePairs does not seem to exist on JMS\Serializer\Metadata\ClassMetadata.
Loading history...
222 1
223 1
            $entryNode = $this->createElement($tagName, $namespace);
224 1
            $this->currentNode->appendChild($entryNode);
225
            $this->setCurrentNode($entryNode);
226 1
227
            if (null !== $keyAttributeName) {
228
                $entryNode->setAttribute($keyAttributeName, (string) $k);
229
            }
230 1
231
            try {
232
                if (null !== $node = $this->navigator->accept($v, $elType)) {
0 ignored issues
show
Bug introduced by
The method accept() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

232
                if (null !== $node = $this->navigator->/** @scrutinizer ignore-call */ accept($v, $elType)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
233 1
                    $this->currentNode->appendChild($node);
234
                }
235
            } catch (NotAcceptableException $e) {
236 70
                $this->currentNode->parentNode->removeChild($this->currentNode);
0 ignored issues
show
Bug introduced by
The method removeChild() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

236
                $this->currentNode->parentNode->/** @scrutinizer ignore-call */ 
237
                                                removeChild($this->currentNode);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
237
            }
238 69
239 9
            $this->revertCurrentNode();
240 69
        }
241
    }
242 69
243 69
    /**
244 69
     * {@inheritdoc}
245
     */
246
    public function startVisitingObject(ClassMetadata $metadata, object $data, array $type): void
247 70
    {
248
        $this->objectMetadataStack->push($metadata);
249
250 70
        if (null === $this->currentNode) {
251 70
            $this->createRoot($metadata);
252
        }
253 2
254 2
        $this->addNamespaceAttributes($metadata, $this->currentNode);
255 2
256 2
        $this->hasValue = false;
257 2
    }
258 2
259
    /**
260
     * {@inheritdoc}
261 70
     */
262
    public function visitProperty(PropertyMetadata $metadata, $v): void
263 70
    {
264 69
        if ($metadata->xmlAttribute) {
265
            $this->setCurrentMetadata($metadata);
266 69
            $node = $this->navigator->accept($v, $metadata->type);
0 ignored issues
show
Bug introduced by
It seems like $metadata->type can also be of type JMS\Serializer\Metadata\TypeArray; however, parameter $type of JMS\Serializer\GraphNavigatorInterface::accept() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

266
            $node = $this->navigator->accept($v, /** @scrutinizer ignore-type */ $metadata->type);
Loading history...
267 3
            $this->revertCurrentMetadata();
268
269
            if (!$node instanceof \DOMCharacterData) {
270
                throw new RuntimeException(sprintf('Unsupported value for XML attribute for %s. Expected character data, but got %s.', $metadata->name, json_encode($v)));
271 70
            }
272 70
273
            $this->setAttributeOnNode($this->currentNode, $metadata->serializedName, $node->nodeValue, $metadata->xmlNamespace);
0 ignored issues
show
Bug introduced by
It seems like $metadata->serializedName can also be of type null; however, parameter $name of JMS\Serializer\XmlSerial...r::setAttributeOnNode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

273
            $this->setAttributeOnNode($this->currentNode, /** @scrutinizer ignore-type */ $metadata->serializedName, $node->nodeValue, $metadata->xmlNamespace);
Loading history...
Bug introduced by
It seems like $this->currentNode can also be of type null; however, parameter $node of JMS\Serializer\XmlSerial...r::setAttributeOnNode() does only seem to accept DOMElement, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

273
            $this->setAttributeOnNode(/** @scrutinizer ignore-type */ $this->currentNode, $metadata->serializedName, $node->nodeValue, $metadata->xmlNamespace);
Loading history...
274 70
275
            return;
276 70
        }
277
278
        if (
279 6
            ($metadata->xmlValue && $this->currentNode->childNodes->length > 0)
280
            || (!$metadata->xmlValue && $this->hasValue)
281 6
        ) {
282
            throw new RuntimeException(sprintf('If you make use of @XmlValue, all other properties in the class must have the @XmlAttribute annotation. Invalid usage detected in class %s.', $metadata->class));
283
        }
284 7
285
        if ($metadata->xmlValue) {
286 7
            $this->hasValue = true;
287
288
            $this->setCurrentMetadata($metadata);
289 69
            $node = $this->navigator->accept($v, $metadata->type);
290
            $this->revertCurrentMetadata();
291 69
292
            if (!$node instanceof \DOMCharacterData) {
293
                throw new RuntimeException(sprintf('Unsupported value for property %s::$%s. Expected character data, but got %s.', $metadata->class, $metadata->name, \is_object($node) ? \get_class($node) : \gettype($node)));
294 77
            }
295
296 77
            $this->currentNode->appendChild($node);
0 ignored issues
show
Bug introduced by
The method appendChild() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

296
            $this->currentNode->/** @scrutinizer ignore-call */ 
297
                                appendChild($node);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
297 77
298
            return;
299 110
        }
300
301 110
        if ($metadata->xmlAttributeMap) {
302 22
            if (!\is_array($v)) {
303 1
                throw new RuntimeException(sprintf('Unsupported value type for XML attribute map. Expected array but got %s.', \gettype($v)));
304
            }
305 21
306 21
            foreach ($v as $key => $value) {
307 20
                $this->setCurrentMetadata($metadata);
308
                $node = $this->navigator->accept($value, null);
309
                $this->revertCurrentMetadata();
310
311
                if (!$node instanceof \DOMCharacterData) {
312 110
                    throw new RuntimeException(sprintf('Unsupported value for a XML attribute map value. Expected character data, but got %s.', json_encode($v)));
313 9
                }
314 9
315 9
                $this->setAttributeOnNode($this->currentNode, $key, $node->nodeValue, $metadata->xmlNamespace);
316 9
            }
317
318
            return;
319 110
        }
320
321
        if ($addEnclosingElement = !$this->isInLineCollection($metadata) && !$metadata->inline) {
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $addEnclosingElement = (...&& ! $metadata->inline), Probably Intended Meaning: ($addEnclosingElement = ... && ! $metadata->inline
Loading history...
322 2
            $namespace = $metadata->xmlNamespace ?? $this->getClassDefaultNamespace($this->objectMetadataStack->top());
323
324 2
            $element = $this->createElement($metadata->serializedName, $namespace);
0 ignored issues
show
Bug introduced by
It seems like $metadata->serializedName can also be of type null; however, parameter $tagName of JMS\Serializer\XmlSerial...isitor::createElement() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

324
            $element = $this->createElement(/** @scrutinizer ignore-type */ $metadata->serializedName, $namespace);
Loading history...
325
            $this->currentNode->appendChild($element);
326
            $this->setCurrentNode($element);
327
        }
328
329
        $this->setCurrentMetadata($metadata);
330
331
        try {
332 113
            if (null !== $node = $this->navigator->accept($v, $metadata->type)) {
333
                $this->currentNode->appendChild($node);
334 113
            }
335
        } catch (NotAcceptableException $e) {
336
            $this->currentNode->parentNode->removeChild($this->currentNode);
0 ignored issues
show
Bug introduced by
It seems like $this->currentNode can also be of type null; however, parameter $child of DOMNode::removeChild() does only seem to accept DOMNode, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

336
            $this->currentNode->parentNode->removeChild(/** @scrutinizer ignore-type */ $this->currentNode);
Loading history...
337 113
            $this->revertCurrentMetadata();
338
            $this->revertCurrentNode();
339
            $this->hasValue = false;
340 78
341
            return;
342 78
        }
343 78
344 78
        $this->revertCurrentMetadata();
345
346 112
        if ($addEnclosingElement) {
347
            $this->revertCurrentNode();
348 112
349 112
            if ($this->isElementEmpty($element) && (null === $v || $this->isSkippableCollection($metadata) || $this->isSkippableEmptyObject($node, $metadata))) {
350 112
                $this->currentNode->removeChild($element);
351
            }
352 1
        }
353
354 1
        $this->hasValue = false;
355 1
    }
356 1
357
    private function isInLineCollection(PropertyMetadata $metadata): bool
358 79
    {
359
        return $metadata->xmlCollection && $metadata->xmlCollectionInline;
360 79
    }
361
362
    private function isSkippableEmptyObject(?\DOMElement $node, PropertyMetadata $metadata): bool
363 78
    {
364
        return null === $node && !$metadata->xmlCollection && $metadata->skipWhenEmpty;
365 78
    }
366
367
    private function isSkippableCollection(PropertyMetadata $metadata): bool
368 121
    {
369
        return $metadata->xmlCollection && $metadata->xmlCollectionSkipWhenEmpty;
370 121
    }
371
372 121
    private function isElementEmpty(\DOMElement $element): bool
373
    {
374
        return !$element->hasChildNodes() && !$element->hasAttributes();
375
    }
376
377
    public function endVisitingObject(ClassMetadata $metadata, object $data, array $type): void
378
    {
379
        $this->objectMetadataStack->pop();
380
    }
381
382 2
    /**
383
     * {@inheritdoc}
384 2
     */
385
    public function getResult($node)
386
    {
387
        $this->navigator = null;
388
        if (null === $this->document->documentElement) {
389
            if ($node instanceof \DOMElement) {
390
                $this->document->appendChild($node);
391
            } else {
392
                $this->createRoot();
393 80
                if ($node) {
394
                    $this->document->documentElement->appendChild($node);
395 80
                }
396 11
            }
397 11
        }
398 11
399 7
        if ($this->nullWasVisited) {
400 5
            $this->document->documentElement->setAttributeNS(
401
                'http://www.w3.org/2000/xmlns/',
402 11
                'xmlns:xsi',
403
                'http://www.w3.org/2001/XMLSchema-instance',
404 80
            );
405
        }
406 79
407
        return $this->document->saveXML();
408 79
    }
409 73
410
    public function getCurrentNode(): ?\DOMNode
411 10
    {
412 6
        return $this->currentNode;
413
    }
414 9
415 3
    public function getCurrentMetadata(): ?PropertyMetadata
416
    {
417 9
        return $this->currentMetadata;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->currentMetadata could return the type JMS\Serializer\Metadata\ClassMetadata which is incompatible with the type-hinted return JMS\Serializer\Metadata\PropertyMetadata|null. Consider adding an additional type-check to rule them out.
Loading history...
418
    }
419
420 15
    public function getDocument(): \DOMDocument
421
    {
422 15
        return $this->document;
423 5
    }
424 2
425
    public function setCurrentMetadata(PropertyMetadata $metadata): void
426 5
    {
427
        $this->metadataStack->push($this->currentMetadata);
428 12
        $this->currentMetadata = $metadata;
429
    }
430 15
431
    public function setCurrentNode(\DOMNode $node): void
432 68
    {
433
        $this->stack->push($this->currentNode);
434 68
        $this->currentNode = $node;
435
    }
436
437
    public function setCurrentAndRootNode(\DOMNode $node): void
438
    {
439
        $this->setCurrentNode($node);
440
        $this->document->appendChild($node);
441
    }
442
443
    public function revertCurrentNode(): ?\DOMNode
444
    {
445
        return $this->currentNode = $this->stack->pop();
446
    }
447
448
    public function revertCurrentMetadata(): ?PropertyMetadata
449
    {
450
        return $this->currentMetadata = $this->metadataStack->pop();
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456
    public function prepare($data)
457
    {
458
        $this->nullWasVisited = false;
459
460
        return $data;
461
    }
462
463
    /**
464
     * Checks that the name is a valid XML element name.
465
     */
466
    private function isElementNameValid(string $name): bool
467
    {
468
        return $name && false === strpos($name, ' ') && preg_match('#^[\pL_][\pL0-9._-]*$#ui', $name);
469
    }
470
471
    /**
472
     * Adds namespace attributes to the XML root element
473
     */
474
    private function addNamespaceAttributes(ClassMetadata $metadata, \DOMElement $element): void
475
    {
476
        foreach ($metadata->xmlNamespaces as $prefix => $uri) {
477
            $attribute = 'xmlns';
478
            if ('' !== $prefix) {
479
                $attribute .= ':' . $prefix;
480
            } elseif ($element->namespaceURI === $uri) {
481
                continue;
482
            }
483
484
            $element->setAttributeNS('http://www.w3.org/2000/xmlns/', $attribute, $uri);
485
        }
486
    }
487
488
    private function createElement(string $tagName, ?string $namespace = null): \DOMElement
489
    {
490
        // See #1087 - element must be like: <element xmlns="" /> - https://www.w3.org/TR/REC-xml-names/#iri-use
491
        // Use of an empty string in a namespace declaration turns it into an "undeclaration".
492
        if ('' === $namespace) {
493
            // If we have a default namespace, we need to create namespaced.
494
            if ($this->parentHasNonEmptyDefaultNs()) {
495
                return $this->document->createElementNS($namespace, $tagName);
496
            }
497
498
            return $this->document->createElement($tagName);
499
        }
500
501
        if (null === $namespace) {
502
            return $this->document->createElement($tagName);
503
        }
504
505
        if ($this->currentNode->isDefaultNamespace($namespace)) {
506
            return $this->document->createElementNS($namespace, $tagName);
507
        }
508
509
        if (!($prefix = $this->currentNode->lookupPrefix($namespace)) && !($prefix = $this->document->lookupPrefix($namespace))) {
510
            $prefix = 'ns-' . substr(sha1($namespace), 0, 8);
511
        }
512
513
        return $this->document->createElementNS($namespace, $prefix . ':' . $tagName);
514
    }
515
516
    private function setAttributeOnNode(\DOMElement $node, string $name, string $value, ?string $namespace = null): void
517
    {
518
        if (null !== $namespace) {
519
            if (!$prefix = $node->lookupPrefix($namespace)) {
520
                $prefix = 'ns-' . substr(sha1($namespace), 0, 8);
521
            }
522
523
            $node->setAttributeNS($namespace, $prefix . ':' . $name, $value);
524
        } else {
525
            $node->setAttribute($name, $value);
526
        }
527
    }
528
529
    private function getClassDefaultNamespace(ClassMetadata $metadata): ?string
530
    {
531
        return $metadata->xmlNamespaces[''] ?? null;
532
    }
533
534
    private function parentHasNonEmptyDefaultNs(): bool
535
    {
536
        return null !== ($uri = $this->currentNode->lookupNamespaceUri(null)) && ('' !== $uri);
537
    }
538
}
539