Completed
Push — master ( 04134d...b75a21 )
by Alexander
02:28
created

XmlConvertible::__clone()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 4
cts 4
cp 1
cc 2
eloc 4
nc 2
nop 0
crap 2
1
<?php
2
3
namespace Horat1us;
4
5
use Horat1us\Arrays\Collection;
6
use Horat1us\Services\XmlEqualityService;
7
use Horat1us\Services\XmlExportService;
8
use Horat1us\Services\XmlParserService;
9
10
/**
11
 * Class XmlConvertible
12
 * @package Horat1us
13
 *
14
 * @mixin XmlConvertibleInterface
15
 */
16
trait XmlConvertible
17
{
18
    /**
19
     * @var XmlConvertibleInterface[]|\DOMNode[]|\DOMElement[]|null
20
     */
21
    public $xmlChildren;
22
23
    /**
24
     * Name of xml element (class name will be used by default)
25
     *
26
     * @var string
27
     */
28
    public $xmlElementName;
29
30
    /**
31
     * @param XmlConvertibleInterface $xml
32
     * @param bool $skipEmpty
33
     * @return XmlConvertible|XmlConvertibleInterface|null
34
     */
35 4
    public function xmlIntersect(
36
        XmlConvertibleInterface $xml,
37
        bool $skipEmpty = true
38
    )
39
    {
40 4
        $current = clone $this;
41 4
        $compared = clone $xml;
42
43
        if (
44 4
            $current->getXmlElementName() !== $compared->getXmlElementName()
45 3
            || array_reduce(
46 3
                $current->getXmlProperties(),
47
                function (bool $carry, string $property) use ($compared, $current) : bool {
48 3
                    return $carry
49 3
                        || (!property_exists($compared, $property))
50 3
                        || $current->{$property} !== $compared->{$property};
51 3
                },
52 4
                false
53
            )
54
        ) {
55 1
            return null;
56
        }
57
58 3
        $newChildren = array_uintersect(
59 3
            $compared->getXmlChildren() ?? [],
60 3
            $current->getXmlChildren() ?? [],
61
            function ($comparedChild, $currentChild) use ($skipEmpty) {
62 2
                if ($comparedChild === $currentChild) {
63
                    return 0;
64
                }
65
66 2
                $diff = ($currentChild instanceof XmlConvertibleInterface
67 2
                    ? $currentChild
68
                    : XmlConvertibleObject::fromXml($currentChild)
69 2
                )->xmlIntersect(
70
                    $comparedChild instanceof XmlConvertibleInterface
71 2
                        ? $comparedChild
72 2
                        : XmlConvertibleObject::fromXml($comparedChild)
73
                );
74
75 2
                return $diff === null ? -1 : 0;
76 3
            }
77
        );
78
79 3
        return $current->setXmlChildren($newChildren);
80
    }
81
82
    /**
83
     * @param XmlConvertibleInterface $xml
84
     * @return XmlConvertible|null
85
     */
86 4
    public function xmlDiff(XmlConvertibleInterface $xml)
87
    {
88 4
        $current = $this;
89 4
        $compared = $xml;
90
91
        if (
92 4
            $current->getXmlElementName() !== $compared->getXmlElementName()
93 4
            || empty($current->getXmlChildren()) && !empty($compared->getXmlChildren())
94 3
            || array_reduce(
95 3
                $current->getXmlProperties(),
96
                function (bool $carry, string $property) use ($compared, $current) : bool {
97 3
                    return $carry
98 3
                        || (!property_exists($compared, $property))
99 3
                        || $current->{$property} !== $compared->{$property};
100 3
                },
101 4
                false
102
            )
103
        ) {
104 4
            return clone $current;
105
        }
106
107
108 2
        $newChildren = Collection::from($current->getXmlChildren() ?? [])
109
            ->map(function ($child) use ($compared) {
110 2
                return array_reduce(
111 2
                    $compared->getXmlChildren() ?? [],
112
                    function ($carry, $comparedChild) use ($child) {
113 2
                        if ($carry) {
114
                            return $carry;
115
                        }
116
117 2
                        $diff = ($child instanceof XmlConvertibleInterface
118 2
                            ? $child
119
                            : XmlConvertibleObject::fromXml($child)
120 2
                        )->xmlDiff(
121
                            $comparedChild instanceof XmlConvertibleInterface
122 2
                                ? $comparedChild
123 2
                                : XmlConvertibleObject::fromXml($comparedChild)
124
                        );
125
126 2
                        return $diff;
127 2
                    });
128 2
            })
129
            ->filter(function ($child) {
130 2
                return $child !== null;
131 2
            })
132 2
            ->array;
133
134 2
        if (empty($newChildren)) {
135 2
            return null;
136
        }
137
138 2
        $target = clone $current;
139 2
        $target->setXmlChildren($newChildren);
140
141 2
        return clone $target;
142
    }
143
144
    /**
145
     * Converts object to XML and compares it with given
146
     *
147
     * @param XmlConvertibleInterface $xml
148
     * @return bool
149
     */
150 3
    public function xmlEqual(XmlConvertibleInterface $xml): bool
151
    {
152 3
        $service = new XmlEqualityService($this, $xml);
153 3
        return $service->compare();
154
    }
155
156
    /**
157
     * @param \DOMDocument|\DOMElement $document
158
     * @param array $aliases
159
     * @return XmlConvertibleInterface
160
     */
161 12
    public static function fromXml($document, array $aliases = [])
162
    {
163
        /** @var \DOMElement $document */
164 12
        if (!in_array(get_called_class(), $aliases)) {
165 7
            $aliases[(new \ReflectionClass(get_called_class()))->getShortName()] = get_called_class();
166
        }
167 12
        return (new XmlParserService($document, $aliases))->convert();
168
    }
169
170
    /**
171
     * @param \DOMDocument|null $document
172
     * @return \DOMElement
173
     */
174 14
    public function toXml(\DOMDocument $document = null): \DOMElement
175
    {
176 14
        $service = new XmlExportService($this, $document);
177 14
        return $service->export();
178
    }
179
180
    /**
181
     * Name of xml element (class name will be used by default)
182
     *
183
     * @return string
184
     */
185 23
    public function getXmlElementName(): string
186
    {
187 23
        return $this->xmlElementName ?? (new \ReflectionClass(get_called_class()))->getShortName();
188
    }
189
190
    /**
191
     * Settings name of xml element
192
     *
193
     * @param string $name
194
     * @return static
195
     */
196 1
    public function setXmlElementName(string $name = null)
197
    {
198 1
        $this->xmlElementName = $name;
199 1
        return $this;
200
    }
201
202
    /**
203
     * @return XmlConvertibleInterface[]|\DOMNode[]|\DOMElement[]|null
204
     */
205 23
    public function getXmlChildren()
206
    {
207 23
        return $this->xmlChildren;
208
    }
209
210
    /**
211
     * @param XmlConvertibleInterface[]|\DOMNode[]|\DOMElement[]|null $xmlChildren
212
     * @return static
213
     */
214 14
    public function setXmlChildren(array $xmlChildren = null)
215
    {
216 14
        $this->xmlChildren = $xmlChildren ?: null;
217 14
        return $this;
218
    }
219
220
    /**
221
     * Getting array of property names which will be used as attributes in created XML
222
     *
223
     * @param array|null $properties
224
     * @return array|string[]
225
     */
226 23
    public function getXmlProperties(array $properties = null): array
227
    {
228 23
        $properties = $properties
229
            ?: array_map(function(\ReflectionProperty $property) {
230 18
                return $property->getName();
231 23
            }, (new \ReflectionClass(get_called_class()))->getProperties(\ReflectionProperty::IS_PUBLIC));
232
233
234
        return array_filter($properties, function(string $property) {
235 23
            return !in_array($property, ['xmlChildren', 'xmlElementName']);
236 23
        });
237
    }
238
239
    /**
240
     * Cloning all children by default
241
     */
242
    public function __clone()
243
    {
244 19
        $this->xmlChildren = array_map(function ($xmlChild) {
245 5
            return clone $xmlChild;
246 19
        }, $this->xmlChildren ?? []) ?: null;
247
    }
248
}