XmlParserService   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 28
eloc 63
c 1
b 0
f 0
dl 0
loc 190
ccs 73
cts 73
cp 1
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A mapAlias() 0 13 3
A getDocument() 0 3 1
A getAliases() 0 3 1
A getIsAliasMatch() 0 17 3
A __clone() 0 3 1
A convertAttributes() 0 15 4
A setDocument() 0 12 3
A convertChildren() 0 12 2
A convert() 0 17 5
A setAliases() 0 10 2
A __construct() 0 5 1
A mapKey() 0 5 2
1
<?php
2
3
namespace Horat1us\Services;
4
5
use Horat1us\XmlConvertibleInterface;
6
use Horat1us\XmlConvertibleObject;
7
8
/**
9
 * Class XmlParserService
10
 * @package Horat1us\Services
11
 */
12
class XmlParserService
13
{
14
    /**
15
     * @var \DOMNode
16
     */
17
    protected $element;
18
19
    /**
20
     * @var XmlConvertibleInterface[]
21
     */
22
    protected $aliases;
23
24
    /**
25
     * XmlParserService constructor.
26
     * @param $document
27
     * @param array $aliases
28
     */
29
    public function __construct($document, array $aliases = [])
30
    {
31 15
        $this
32
            ->setAliases($aliases)
33
            ->setDocument($document);
34 15
    }
35 12
36 11
    public function __clone()
37
    {
38 8
        $this->element = clone $this->element;
39
    }
40 8
41 8
    /**
42
     * @return XmlConvertibleInterface
43
     */
44
    public function convert()
45
    {
46 10
        $nodeObject = new XmlConvertibleObject($this->element->nodeName);
47
        foreach ($this->aliases as $key => $alias) {
48
            if ($this->getIsAliasMatch($key, $alias)) {
49 10
                $nodeObject = clone $alias;
50
            }
51 10
        }
52 10
53
        if ($this->element->hasChildNodes()) {
54 10
            $this->convertChildren($nodeObject);
55 8
        }
56
        if ($this->element->hasAttributes()) {
57 10
            $this->convertAttributes($nodeObject);
58 9
        }
59
60
        return $nodeObject;
61 10
    }
62
63
    /**
64
     * @param XmlConvertibleInterface $object
65
     * @return $this
66
     */
67
    public function convertChildren(XmlConvertibleInterface &$object)
68 8
    {
69
        $children = [];
70 8
        $service = clone $this;
71 8
72
        foreach ($this->element->childNodes as $childNode) {
73 8
            $service->setDocument($childNode);
74 8
            $children[] = $service->convert();
75 8
        }
76
        $object->setXmlChildren($children);
77 8
78
        return $this;
79 8
    }
80
81
    public function convertAttributes(XmlConvertibleInterface &$object)
82 9
    {
83
        $properties = $object->getXmlProperties();
84 9
        /** @var \DOMAttr $attribute */
85
        foreach ($this->element->attributes as $attribute) {
86 9
            if (
87
                !$object instanceof XmlConvertibleObject
88 9
                && !in_array($attribute->name, $properties)
89 9
            ) {
90
                throw new \UnexpectedValueException(
91 1
                    get_class($object) . ' must have defined ' . $attribute->name . ' XML property',
92 1
                    4
93 1
                );
94
            }
95
            $object->{$attribute->name} = $attribute->value;
96 9
        }
97
    }
98 9
99
    /**
100
     * @param \DOMNode|\DOMDocument $document
101
     * @return $this
102
     */
103
    public function setDocument($document)
104 12
    {
105
        if ($document instanceof \DOMDocument) {
106 12
            return $this->setDocument($document->firstChild);
107 8
        }
108
109
        if (!$document instanceof \DOMNode) {
0 ignored issues
show
introduced by
$document is always a sub-type of DOMNode.
Loading history...
110 12
            throw new \InvalidArgumentException("Document must be instance of DOMElement or DOMNode");
111 1
        }
112
        $this->element = $document;
113 11
114
        return $this;
115 11
    }
116
117
    /**
118
     * @return \DOMElement
119
     */
120
    public function getDocument()
121 1
    {
122
        return $this->element;
123 1
    }
124
125
    /**
126
     * @param XmlConvertibleInterface[]|string $aliases
127
     * @return $this
128
     */
129
    public function setAliases(array $aliases = [])
130 15
    {
131
        $this->aliases = [];
132 15
133
        foreach ($aliases as $key => $element) {
134 15
            $element = $this->mapAlias($element);
135
            $this->aliases[$this->mapKey($key, $element)] = $element;
136 13
        }
137 15
138
        return $this;
139 10
    }
140 12
141
    /**
142 12
     * @return XmlConvertibleInterface[]
143
     */
144
    public function getAliases()
145
    {
146
        return $this->aliases;
147
    }
148 1
149
    /**
150 1
     * @param string|XmlConvertibleInterface $element
151
     * @return XmlConvertibleInterface
152
     */
153
    protected function mapAlias($element)
154
    {
155
        if ($element instanceof XmlConvertibleInterface) {
156
            return $element;
157 13
        }
158
        if (!is_string($element)) {
0 ignored issues
show
introduced by
The condition is_string($element) is always true.
Loading history...
159 13
            throw new \UnexpectedValueException(
160 10
                "All aliases must be instance or class implements " . XmlConvertibleInterface::class
161
            );
162 12
        }
163 3
164 3
        // Additional type-checking
165
        return $this->mapAlias(new $element());
166
    }
167
168
    /**
169 10
     * @param string|integer $key
170
     * @param XmlConvertibleInterface $element
171
     * @return string
172
     */
173
    protected function mapKey($key, XmlConvertibleInterface $element): string
174
    {
175
        return is_numeric($key)
176
            ? $element->getXmlElementName()
177 10
            : $key;
178
    }
179 10
180 1
    /**
181 10
     * @param string $key
182
     * @param XmlConvertibleInterface $element
183
     * @return bool
184
     */
185
    protected function getIsAliasMatch(string $key, XmlConvertibleInterface $element): bool
186
    {
187
        if ($this->element->nodeName !== $key) {
188
            return false;
189 10
        }
190
191 10
        return array_reduce(
192 7
            array_filter(
193
                $element->getXmlProperties(),
194
                function ($property) use ($element) {
195 9
                    return empty($element->{$property});
196
                }
197 6
            ),
198 9
            function (bool $carry, $property) use ($element) {
199 9
                return $carry && $this->element->attributes->getNamedItem($property) != $element->{$property};
0 ignored issues
show
Bug introduced by
The method getNamedItem() 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

199
                return $carry && $this->element->attributes->/** @scrutinizer ignore-call */ getNamedItem($property) != $element->{$property};

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...
200 6
            },
201 9
            true
202
        );
203
    }
204
}
205