Passed
Push — master ( 4b20bd...439edc )
by Alexander
06:09
created

XmlDataResponseFormatter::buildXml()   C

Complexity

Conditions 12
Paths 9

Size

Total Lines 32
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.7571

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 25
c 1
b 0
f 0
nc 9
nop 2
dl 0
loc 32
rs 6.9666
ccs 19
cts 23
cp 0.8261
crap 12.7571

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 Yiisoft\Yii\Web\Data\Formatter;
4
5
use DOMDocument;
6
use DOMElement;
7
use DOMException;
8
use DOMText;
9
use Psr\Http\Message\ResponseInterface;
10
use Yiisoft\Strings\StringHelper;
11
use Yiisoft\Yii\Web\Data\DataResponse;
12
use Yiisoft\Yii\Web\Data\DataResponseFormatterInterface;
13
14
final class XmlDataResponseFormatter implements DataResponseFormatterInterface
15
{
16
    /**
17
     * @var string the Content-Type header for the response
18
     */
19
    private string $contentType = 'application/xml';
20
    /**
21
     * @var string the XML version
22
     */
23
    private string $version = '1.0';
24
    /**
25
     * @var string the XML encoding.
26
     */
27
    private string $encoding = 'UTF-8';
28
    /**
29
     * @var string the name of the root element. If set to false, null or is empty then no root tag should be added.
30
     */
31
    private string $rootTag = 'response';
32
    /**
33
     * @var string the name of the elements that represent the array elements with numeric keys.
34
     */
35
    private string $itemTag = 'item';
36
    /**
37
     * @var bool whether to interpret objects implementing the [[\Traversable]] interface as arrays.
38
     * Defaults to `true`.
39
     */
40
    private bool $useTraversableAsArray = true;
41
    /**
42
     * @var bool if object tags should be added
43
     */
44
    private bool $useObjectTags = true;
45
46 8
    public function format(DataResponse $dataResponse): ResponseInterface
47
    {
48 8
        $content = '';
49 8
        $data = $dataResponse->getData();
50 8
        if ($data !== null) {
51 8
            $dom = new DOMDocument($this->version, $this->encoding);
52 8
            if (!empty($this->rootTag)) {
53 8
                $root = new DOMElement($this->rootTag);
0 ignored issues
show
Bug introduced by
The call to DOMElement::__construct() has too few arguments starting with value. ( Ignorable by Annotation )

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

53
                $root = /** @scrutinizer ignore-call */ new DOMElement($this->rootTag);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
54 8
                $dom->appendChild($root);
55 8
                $this->buildXml($root, $data);
56
            } else {
57
                $this->buildXml($dom, $data);
0 ignored issues
show
Bug introduced by
$dom of type DOMDocument is incompatible with the type DOMElement expected by parameter $element of Yiisoft\Yii\Web\Data\For...seFormatter::buildXml(). ( Ignorable by Annotation )

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

57
                $this->buildXml(/** @scrutinizer ignore-type */ $dom, $data);
Loading history...
58
            }
59 8
            $content = $dom->saveXML();
60
        }
61 8
        $response = $dataResponse->getResponse();
62 8
        $response->getBody()->write($content);
63
64 8
        return $response->withHeader('Content-Type', $this->contentType . '; ' . $this->encoding);
65
    }
66
67 1
    public function withVersion(string $version): self
68
    {
69 1
        $formatter = clone $this;
70 1
        $formatter->version = $version;
71 1
        return $formatter;
72
    }
73
74 1
    public function withEncoding(string $encoding): self
75
    {
76 1
        $formatter = clone $this;
77 1
        $formatter->encoding = $encoding;
78 1
        return $formatter;
79
    }
80
81 1
    public function withRootTag(string $rootTag): self
82
    {
83 1
        $formatter = clone $this;
84 1
        $formatter->rootTag = $rootTag;
85 1
        return $formatter;
86
    }
87
88 1
    public function withItemTag(string $itemTag): self
89
    {
90 1
        $formatter = clone $this;
91 1
        $formatter->itemTag = $itemTag;
92 1
        return $formatter;
93
    }
94
95
    public function withUseTraversableAsArray(bool $useTraversableAsArray): self
96
    {
97
        $formatter = clone $this;
98
        $formatter->useTraversableAsArray = $useTraversableAsArray;
99
        return $formatter;
100
    }
101
102 2
    public function withUseObjectTags(bool $useObjectTags): self
103
    {
104 2
        $formatter = clone $this;
105 2
        $formatter->useObjectTags = $useObjectTags;
106 2
        return $formatter;
107
    }
108
109 1
    public function withContentType(string $contentType): self
110
    {
111 1
        $formatter = clone $this;
112 1
        $formatter->contentType = $contentType;
113 1
        return $formatter;
114
    }
115
116
    /**
117
     * @param DOMElement $element
118
     * @param mixed $data
119
     */
120 8
    protected function buildXml($element, $data): void
121
    {
122 8
        if (is_array($data) ||
123 8
            ($this->useTraversableAsArray && $data instanceof \Traversable)
124
        ) {
125 3
            foreach ($data as $name => $value) {
126 3
                if (is_int($name) && is_object($value)) {
127
                    $this->buildXml($element, $value);
128 3
                } elseif (is_array($value) || is_object($value)) {
129
                    $child = new DOMElement($this->getValidXmlElementName($name));
0 ignored issues
show
Bug introduced by
The call to DOMElement::__construct() has too few arguments starting with value. ( Ignorable by Annotation )

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

129
                    $child = /** @scrutinizer ignore-call */ new DOMElement($this->getValidXmlElementName($name));

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
130
                    $element->appendChild($child);
131
                    $this->buildXml($child, $value);
132
                } else {
133 3
                    $child = new DOMElement($this->getValidXmlElementName($name));
134 3
                    $element->appendChild($child);
135 3
                    $child->appendChild(new DOMText($this->formatScalarValue($value)));
136
                }
137
            }
138 7
        } elseif (is_object($data)) {
139 2
            if ($this->useObjectTags) {
140 1
                $child = new DOMElement(StringHelper::basename(get_class($data)));
141 1
                $element->appendChild($child);
142
            } else {
143 1
                $child = $element;
144
            }
145 2
            $array = [];
146 2
            foreach ($data as $name => $value) {
147 2
                $array[$name] = $value;
148
            }
149 2
            $this->buildXml($child, $array);
150
        } else {
151 5
            $element->appendChild(new DOMText($this->formatScalarValue($data)));
152
        }
153 8
    }
154
155
    /**
156
     * Formats scalar value to use in XML text node.
157
     *
158
     * @param int|string|bool|float $value a scalar value.
159
     * @return string string representation of the value.
160
     */
161 8
    protected function formatScalarValue($value): string
162
    {
163 8
        if ($value === true) {
164
            return 'true';
165
        }
166 8
        if ($value === false) {
167
            return 'false';
168
        }
169 8
        if (is_float($value)) {
170
            return StringHelper::floatToString($value);
171
        }
172 8
        return (string)$value;
173
    }
174
175
    /**
176
     * Returns element name ready to be used in DOMElement if
177
     * name is not empty, is not int and is valid.
178
     *
179
     * Falls back to [[itemTag]] otherwise.
180
     *
181
     * @param mixed $name
182
     * @return string
183
     */
184 3
    protected function getValidXmlElementName($name): string
185
    {
186 3
        if (empty($name) || is_int($name) || !$this->isValidXmlName($name)) {
187 1
            return $this->itemTag;
188
        }
189
190 3
        return $name;
191
    }
192
193
    /**
194
     * Checks if name is valid to be used in XML.
195
     *
196
     * @param mixed $name
197
     * @return bool
198
     * @see http://stackoverflow.com/questions/2519845/how-to-check-if-string-is-a-valid-xml-element-name/2519943#2519943
199
     */
200 3
    protected function isValidXmlName($name): bool
201
    {
202
        try {
203 3
            new DOMElement($name);
0 ignored issues
show
Bug introduced by
The call to DOMElement::__construct() has too few arguments starting with value. ( Ignorable by Annotation )

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

203
            /** @scrutinizer ignore-call */ 
204
            new DOMElement($name);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
204 1
        } catch (DOMException $e) {
205 1
            return false;
206
        }
207 3
        return true;
208
    }
209
}
210