Passed
Branch master (d805c2)
by
unknown
02:16
created

XmlConvertible::toXml()   C

Complexity

Conditions 11
Paths 14

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 11

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 5.2653
c 0
b 0
f 0
ccs 20
cts 20
cp 1
cc 11
eloc 21
nc 14
nop 1
crap 11

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
namespace Horat1us;
4
5
use Horat1us\Arrays\Collection;
6
7
/**
8
 * Class XmlConvertible
9
 * @package Horat1us
10
 */
11
trait XmlConvertible
12
{
13
    /**
14
     * @var XmlConvertibleInterface[]|\DOMNode[]|\DOMElement[]|null
15
     */
16
    public $xmlChildren;
17
18
    /**
19
     * Name of xml element (class name will be used by default)
20
     *
21
     * @var string
22
     */
23
    public $xmlElementName;
24
25
    /**
26
     * @param \DOMDocument|null $document
27
     * @return \DOMElement
28
     */
29 13
    public function toXml(\DOMDocument $document = null): \DOMElement
30
    {
31 13
        if (!$document) {
32 8
            $document = new \DOMDocument();
33
        }
34
35 13
        $xml = $document->createElement(
36 13
            $this->getXmlElementName()
37
        );
38 13
        if (!is_null($this->xmlChildren)) {
39 7
            foreach ((array)$this->xmlChildren as $child) {
40 7
                if ($child instanceof XmlConvertibleInterface) {
41 5
                    $xml->appendChild($child->toXml($document));
42 3
                } elseif ($child instanceof \DOMNode || $child instanceof \DOMElement) {
43 2
                    $xml->appendChild($child);
44
                } else {
45 1
                    throw new \UnexpectedValueException(
46 7
                        "Each child element must be an instance of " . XmlConvertibleInterface::class
47
                    );
48
                }
49
            }
50
        }
51
52 12
        $properties = $this->getXmlProperties();
53 12
        foreach ($properties as $property) {
54 7
            $value = $this->{$property};
55 7
            if (is_array($value) || is_object($value) || is_null($value)) {
56 3
                continue;
57
            }
58 6
            $xml->setAttribute($property, $value);
59
        }
60
61 12
        return $xml;
62
    }
63
64
    /**
65
     * Converts object to XML and compares it with given
66
     *
67
     * @param XmlConvertibleInterface $xml
68
     * @return bool
69
     */
70 3
    public function xmlEqualTo(XmlConvertibleInterface $xml) :bool
71
    {
72 3
        $document = new \DOMDocument();
73 3
        $document->appendChild($this->toXml($document));
74 3
        $current = $document->saveXML();
75
76 3
        $document = new \DOMDocument();
77 3
        $document->appendChild($xml->toXml($document));
78 3
        $compared = $document->saveXML();
79
80 3
        return $current === $compared;
81
    }
82
83
    /**
84
     * Name of xml element (class name will be used by default)
85
     *
86
     * @return string
87
     */
88 13
    public function getXmlElementName(): string
89
    {
90 13
        return $this->xmlElementName ?? (new \ReflectionClass(get_called_class()))->getShortName();
91
    }
92
93
    /**
94
     * @param \DOMDocument|\DOMElement $document
95
     * @param array $aliases
96
     * @return static
97
     */
98 12
    public static function fromXml($document, array $aliases = [])
99
    {
100 12
        if($document instanceof \DOMDocument) {
101 10
            return static::fromXml($document->firstChild, $aliases);
0 ignored issues
show
Documentation introduced by
$document->firstChild is of type object<DOMNode>, but the function expects a object<DOMDocument>|object<DOMElement>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
102
        }
103
104
        /** @var \DOMElement $document */
105 12
        if (!in_array(get_called_class(), $aliases)) {
106 9
            $aliases[(new \ReflectionClass(get_called_class()))->getShortName()] = get_called_class();
107
        }
108 12
        foreach ($aliases as $key => $alias) {
109 12
            if (is_object($alias)) {
110 3
                if (!$alias instanceof XmlConvertibleInterface) {
111 1
                    throw new \UnexpectedValueException(
112 1
                        "All aliases must be instance or class implements " . XmlConvertibleInterface::class,
113 1
                        1
114
                    );
115
                }
116 2
                $aliases[is_int($key) ? $alias->getXmlElementName() : $key] = $alias;
117 2
                continue;
118
            }
119 11
            if (!is_string($alias)) {
120 1
                throw new \UnexpectedValueException(
121 1
                    "All aliases must be instance or class implements " . XmlConvertibleInterface::class,
122 1
                    2
123
                );
124
            }
125 10
            $instance = new $alias;
126 10
            if (!$instance instanceof XmlConvertibleInterface) {
127 1
                throw new \UnexpectedValueException(
128 1
                    "All aliases must be instance of " . XmlConvertibleInterface::class,
129 1
                    3
130
                );
131
            }
132 9
            unset($aliases[$key]);
133 9
            $aliases[is_int($key) ? $instance->getXmlElementName() : $key] = $instance;
134
        }
135
136 9
        $nodeObject = $aliases[$document->nodeName] ?? new XmlConvertibleObject;
137 9
        $properties = $nodeObject->getXmlProperties();
138
139
        /** @var \DOMAttr $attribute */
140 9
        foreach ($document->attributes as $attribute) {
141 9
            if (!$nodeObject instanceof XmlConvertibleObject) {
142 6
                if (!in_array($attribute->name, $properties)) {
143 1
                    throw new \UnexpectedValueException(
144 1
                        get_class($nodeObject) . ' must have defined ' . $attribute->name . ' XML property',
145 1
                        4
146
                    );
147
                }
148
            }
149 9
            $nodeObject->{$attribute->name} = $attribute->value;
150
        }
151
152 8
        $nodeObject->xmlChildren = [];
153
        /** @var \DOMElement $childNode */
154 8
        foreach ($document->childNodes as $childNode) {
155 7
            $nodeObject->xmlChildren[] = static::fromXml($childNode, $aliases);
156
        }
157 8
        $nodeObject->xmlElementName = $document->nodeName;
158
159 8
        return $nodeObject;
160
    }
161
162
    /**
163
     * Getting array of property names which will be used as attributes in created XML
164
     *
165
     * @param array|null $properties
166
     * @return array|\ReflectionProperty[]
167
     */
168 16
    public function getXmlProperties(array $properties = null): array
169
    {
170 16
        $properties = $properties
171 7
            ? Collection::from($properties)
172 13
            : Collection::from((new \ReflectionClass(get_called_class()))->getProperties(\ReflectionProperty::IS_PUBLIC))
173
                ->map(function (\ReflectionProperty $property) {
174 13
                    return $property->name;
175 16
                });
176
177
        return $properties
178 16
            ->filter(function (string $property): bool {
179 16
                return !in_array($property, ['xmlChildren', 'xmlElementName']);
180 16
            })
181 16
            ->array;
182
    }
183
}