src/Utils/XmlDeserializer.php
<?php declare(strict_types=1); namespace Core\Utils; use DOMDocument;use Exception; class XmlDeserializer{ /** * @var DOMDocument */ private $dom; /** * @int */ private $loadOptions; /** * @param int|null $loadOptions A bit field of LIBXML_* constants */ public function __construct(?int $loadOptions = null) { $this->dom = new DOMDocument(); $this->loadOptions = $loadOptions ?? (LIBXML_NONET | LIBXML_NOBLANKS); } public function deserialize(string $xml, string $rootName, string $clazz) { $this->dom->loadXML($xml, $this->loadOptions); return $this->fromElement($this->dom, $rootName, $clazz); } public function deserializeToArray( string $xml, string $rootName, string $itemName, string $clazz ) { $this->dom->loadXML($xml, $this->loadOptions); return $this->fromElementToArray($this->dom, $itemName, $clazz, $rootName); } public function deserializeToMap( string $xml, string $rootName, string $clazz ) { $this->dom->loadXML($xml, $this->loadOptions); return $this->fromElementToMap($this->dom, $rootName, $clazz); } public function fromAttribute(\DOMElement $parent, string $name, string $clazz) { if (!$parent->hasAttribute($name)) { static::assertNullable($parent, '@' . $name, $clazz); return null; } $attributeValue = $parent->getAttribute($name); return $this->convertSimple($parent->getAttributeNode($name), $attributeValue, $clazz); } public function fromElement(\DOMNode $parent, string $name, string $clazz) { $element = static::getChildNodeByTagName($parent, $name); if ($element === null) { static::assertNullable($parent, $name . '[1]', $clazz); return null; } return $this->convert($element, $clazz); } public function fromElementToArray( \DOMNode $parent, string $itemName, string $clazz, ?string $wrappingElementName = null ) { if ($wrappingElementName === null) { $elements = static::getChildNodesByTagName($parent, $itemName); } else { $wrappingElement = static::getChildNodeByTagName($parent, $wrappingElementName); if ($wrappingElement === null) { static::assertNullable($parent, $wrappingElementName . '[1]', $clazz); return null; } $elements = static::getChildNodesByTagName($wrappingElement, $itemName); } return \array_map( function ($element) use ($clazz) { return $this->convert($element, $clazz); }, $elements ); } public function fromElementToMap( \DOMNode $parent, string $name, string $clazz ) { $wrapper = static::getChildNodeByTagName($parent, $name); if ($wrapper === null) { static::assertNullable($parent, $name . '[1]', $clazz); return null; } $map = []; foreach ($wrapper->childNodes as $element) { if ($element->nodeType === XML_ELEMENT_NODE && $element->hasAttribute('key') === true) { $map[$element->getAttribute('key')] = $this->convert($element, $clazz); } } return $map; } private function convert(\DOMElement $node, string $clazz) { $type = static::withoutNullQualifier($clazz); if (\class_exists($type) && \method_exists($type, 'fromXmlElement')) { return \call_user_func([$type, 'fromXmlElement'], $this, $node); } return $this->convertSimple($node, $node->textContent, $clazz); } private function convertSimple(\DOMNode $node, string $value, string $clazz) { $type = static::withoutNullQualifier($clazz); if ($type === 'float') { return \is_numeric($value) ? \floatval($value) : static::throwTypeException($node, $value, $clazz); } elseif ($type === 'int') { return \is_numeric($value) ? \intval($value) : static::throwTypeException($node, $value, $clazz); } elseif ($type === 'bool') { return \strcasecmp($value, 'true') === 0 ?: (\strcasecmp($value, 'false') === 0 ? false : static::throwTypeException($node, $value, $clazz)); } return $value; } private static function assertNullable(\DOMNode $parentNode, string $nodeSubPath, string $clazz): void { if ($clazz[0] === '?') { return; } $sourceNodePath = static::makeNodePath($parentNode, $nodeSubPath); throw new Exception( 'Required value not found at XML path "' . $sourceNodePath . '" during deserialization.' ); } private static function throwTypeException(\DOMNode $sourceNode, $value, string $clazz): void { throw new Exception( 'Expected value of type "' . $clazz . '" but got value "' . $value . '" at XML path "' . $sourceNode->getNodePath() . '" during deserialization.' ); } private static function withoutNullQualifier(string $type): string { if (\strlen($type) > 1 && $type[0] === '?') { return \substr($type, 1); } return $type; } private static function makeNodePath(\DOMNode $node, $subpath) { $parentPath = $node->getNodePath(); if ($parentPath === '/') { $parentPath = ''; } return $parentPath . '/' . $subpath; } Function `getChildNodeByTagName` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring. private static function getChildNodeByTagName(\DOMNode $node, string $name): ?\DOMElement { if ($node->hasChildNodes()) { foreach ($node->childNodes as $childNode) { if ($childNode->nodeType === XML_ELEMENT_NODE && $childNode->tagName === $name) { return $childNode; } } } return null; } Function `getChildNodesByTagName` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring. private static function getChildNodesByTagName(\DOMNode $node, string $name): array { $arr = []; if ($node->hasChildNodes()) { foreach ($node->childNodes as $childNode) { if ($childNode->nodeType === XML_ELEMENT_NODE && $childNode->tagName === $name) { $arr[] = $childNode; } } } return $arr; }}