XmlLocation   B
last analyzed

Complexity

Total Complexity 48

Size/Duplication

Total Lines 316
Duplicated Lines 2.85 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 95.8%

Importance

Changes 0
Metric Value
wmc 48
lcom 1
cbo 3
dl 9
loc 316
ccs 137
cts 143
cp 0.958
rs 8.5599
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A visit() 0 16 2
B after() 9 48 11
A createRootElement() 0 24 5
B addXml() 0 35 10
A writeAttribute() 0 8 2
A writeElement() 0 14 3
A startDocument() 0 12 3
A finishDocument() 0 6 1
A addXmlArray() 0 8 3
A addXmlObject() 0 20 5
A visitWithValue() 0 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like XmlLocation often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use XmlLocation, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace GuzzleHttp\Command\Guzzle\RequestLocation;
3
4
use GuzzleHttp\Command\CommandInterface;
5
use GuzzleHttp\Command\Guzzle\Operation;
6
use GuzzleHttp\Command\Guzzle\Parameter;
7
use GuzzleHttp\Psr7;
8
use Psr\Http\Message\RequestInterface;
9
10
/**
11
 * Creates an XML document
12
 */
13
class XmlLocation extends AbstractLocation
14
{
15
    /** @var \XMLWriter XML writer resource */
16
    private $writer;
17
18
    /** @var string Content-Type header added when XML is found */
19
    private $contentType;
20
21
    /** @var Parameter[] Buffered elements to write */
22
    private $buffered = [];
23
24
    /**
25
     * @param string $locationName Name of the location
26
     * @param string $contentType  Set to a non-empty string to add a
27
     *     Content-Type header to a request if any XML content is added to the
28
     *     body. Pass an empty string to disable the addition of the header.
29
     */
30 4
    public function __construct($locationName = 'xml', $contentType = 'application/xml')
31
    {
32 4
        parent::__construct($locationName);
33 4
        $this->contentType = $contentType;
34 4
    }
35
36
    /**
37
     * @param CommandInterface $command
38
     * @param RequestInterface $request
39
     * @param Parameter        $param
40
     *
41
     * @return RequestInterface
42
     */
43 17
    public function visit(
44
        CommandInterface $command,
45
        RequestInterface $request,
46
        Parameter $param
47
    ) {
48
        // Buffer and order the parameters to visit based on if they are
49
        // top-level attributes or child nodes.
50
        // @link https://github.com/guzzle/guzzle/pull/494
51 17
        if ($param->getData('xmlAttribute')) {
52 2
            array_unshift($this->buffered, $param);
53 2
        } else {
54 16
            $this->buffered[] = $param;
55
        }
56
57 17
        return $request;
58
    }
59
60
    /**
61
     * @param CommandInterface $command
62
     * @param RequestInterface $request
63
     * @param Operation        $operation
64
     *
65
     * @return RequestInterface
66
     */
67 18
    public function after(
68
        CommandInterface $command,
69
        RequestInterface $request,
70
        Operation $operation
71
    ) {
72 18
        foreach ($this->buffered as $param) {
73 17
            $this->visitWithValue(
74 17
                $command[$param->getName()],
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
75 17
                $param,
76
                $operation
77 17
            );
78 18
        }
79
80 18
        $this->buffered = [];
81
82 18
        $additional = $operation->getAdditionalParameters();
83 18 View Code Duplication
        if ($additional && $additional->getLocation() == $this->locationName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
84 1
            foreach ($command->toArray() as $key => $value) {
85 1
                if (!$operation->hasParam($key)) {
86 1
                    $additional->setName($key);
87 1
                    $this->visitWithValue($value, $additional, $operation);
88 1
                }
89 1
            }
90 1
            $additional->setName(null);
91 1
        }
92
93
        // If data was found that needs to be serialized, then do so
94 18
        $xml = '';
95 18
        if ($this->writer) {
96 17
            $xml = $this->finishDocument($this->writer);
97 18
        } elseif ($operation->getData('xmlAllowEmpty')) {
98
            // Check if XML should always be sent for the command
99 1
            $writer = $this->createRootElement($operation);
100 1
            $xml = $this->finishDocument($writer);
101 1
        }
102
103 18
        if ($xml !== '') {
104 18
            $request = $request->withBody(Psr7\stream_for($xml));
105
            // Don't overwrite the Content-Type if one is set
106 18
            if ($this->contentType && !$request->hasHeader('Content-Type')) {
107 18
                $request = $request->withHeader('Content-Type', $this->contentType);
108 18
            }
109 18
        }
110
111 18
        $this->writer = null;
112
113 18
        return $request;
114
    }
115
116
    /**
117
     * Create the root XML element to use with a request
118
     *
119
     * @param Operation $operation Operation object
120
     *
121
     * @return \XMLWriter
122
     */
123 18
    protected function createRootElement(Operation $operation)
124
    {
125 18
        static $defaultRoot = ['name' => 'Request'];
126
        // If no root element was specified, then just wrap the XML in 'Request'
127 18
        $root = $operation->getData('xmlRoot') ?: $defaultRoot;
128
        // Allow the XML declaration to be customized with xmlEncoding
129 18
        $encoding = $operation->getData('xmlEncoding');
130 18
        $writer = $this->startDocument($encoding);
131 18
        $writer->startElement($root['name']);
132
133
        // Create the wrapping element with no namespaces if no namespaces were present
134 18
        if (!empty($root['namespaces'])) {
135
            // Create the wrapping element with an array of one or more namespaces
136 1
            foreach ((array) $root['namespaces'] as $prefix => $uri) {
137 1
                $nsLabel = 'xmlns';
138 1
                if (!is_numeric($prefix)) {
139
                    $nsLabel .= ':'.$prefix;
140
                }
141 1
                $writer->writeAttribute($nsLabel, $uri);
142 1
            }
143 1
        }
144
145 18
        return $writer;
146
    }
147
148
    /**
149
     * Recursively build the XML body
150
     *
151
     * @param \XMLWriter $writer XML to modify
152
     * @param Parameter  $param     API Parameter
153
     * @param mixed      $value     Value to add
154
     */
155 17
    protected function addXml(\XMLWriter $writer, Parameter $param, $value)
156
    {
157 17
        $value = $param->filter($value);
158 17
        $type = $param->getType();
159 17
        $name = $param->getWireName();
160 17
        $prefix = null;
161 17
        $namespace = $param->getData('xmlNamespace');
162 17
        if (false !== strpos($name, ':')) {
163 2
            list($prefix, $name) = explode(':', $name, 2);
164 2
        }
165
166 17
        if ($type == 'object' || $type == 'array') {
167 9
            if (!$param->getData('xmlFlattened')) {
168 9
                if ($namespace) {
169
                    $writer->startElementNS(null, $name, $namespace);
170
                } else {
171 9
                    $writer->startElement($name);
172
                }
173 9
            }
174 9
            if ($param->getType() == 'array') {
175 4
                $this->addXmlArray($writer, $param, $value);
176 9
            } elseif ($param->getType() == 'object') {
177 8
                $this->addXmlObject($writer, $param, $value);
178 8
            }
179 9
            if (!$param->getData('xmlFlattened')) {
180 9
                $writer->endElement();
181 9
            }
182 9
            return;
183
        }
184 17
        if ($param->getData('xmlAttribute')) {
185 5
            $this->writeAttribute($writer, $prefix, $name, $namespace, $value);
186 5
        } else {
187 15
            $this->writeElement($writer, $prefix, $name, $namespace, $value);
188
        }
189 17
    }
190
191
    /**
192
     * Write an attribute with namespace if used
193
     *
194
     * @param  \XMLWriter $writer XMLWriter instance
195
     * @param  string     $prefix    Namespace prefix if any
196
     * @param  string     $name      Attribute name
197
     * @param  string     $namespace The uri of the namespace
198
     * @param  string     $value     The attribute content
199
     */
200 5
    protected function writeAttribute($writer, $prefix, $name, $namespace, $value)
201
    {
202 5
        if ($namespace) {
203 1
            $writer->writeAttributeNS($prefix, $name, $namespace, $value);
204 1
        } else {
205 4
            $writer->writeAttribute($name, $value);
206
        }
207 5
    }
208
209
    /**
210
     * Write an element with namespace if used
211
     *
212
     * @param  \XMLWriter $writer XML writer resource
213
     * @param  string     $prefix    Namespace prefix if any
214
     * @param  string     $name      Element name
215
     * @param  string     $namespace The uri of the namespace
216
     * @param  string     $value     The element content
217
     */
218 15
    protected function writeElement(\XMLWriter $writer, $prefix, $name, $namespace, $value)
219
    {
220 15
        if ($namespace) {
221 3
            $writer->startElementNS($prefix, $name, $namespace);
222 3
        } else {
223 12
            $writer->startElement($name);
224
        }
225 15
        if (strpbrk($value, '<>&')) {
226 1
            $writer->writeCData($value);
227 1
        } else {
228 14
            $writer->writeRaw($value);
229
        }
230 15
        $writer->endElement();
231 15
    }
232
233
    /**
234
     * Create a new xml writer and start a document
235
     *
236
     * @param  string $encoding document encoding
237
     *
238
     * @return \XMLWriter the writer resource
239
     * @throws \RuntimeException if the document cannot be started
240
     */
241 18
    protected function startDocument($encoding)
242
    {
243 18
        $this->writer = new \XMLWriter();
244 18
        if (!$this->writer->openMemory()) {
245
            throw new \RuntimeException('Unable to open XML document in memory');
246
        }
247 18
        if (!$this->writer->startDocument('1.0', $encoding)) {
248
            throw new \RuntimeException('Unable to start XML document');
249
        }
250
251 18
        return $this->writer;
252
    }
253
254
    /**
255
     * End the document and return the output
256
     *
257
     * @param \XMLWriter $writer
258
     *
259
     * @return string the writer resource
260
     */
261 18
    protected function finishDocument($writer)
262
    {
263 18
        $writer->endDocument();
264
265 18
        return $writer->outputMemory();
266
    }
267
268
    /**
269
     * Add an array to the XML
270
     *
271
     * @param \XMLWriter $writer
272
     * @param Parameter $param
273
     * @param $value
274
     */
275 4
    protected function addXmlArray(\XMLWriter $writer, Parameter $param, &$value)
276
    {
277 4
        if ($items = $param->getItems()) {
278 4
            foreach ($value as $v) {
279 4
                $this->addXml($writer, $items, $v);
280 4
            }
281 4
        }
282 4
    }
283
284
    /**
285
     * Add an object to the XML
286
     *
287
     * @param \XMLWriter $writer
288
     * @param Parameter $param
289
     * @param $value
290
     */
291 8
    protected function addXmlObject(\XMLWriter $writer, Parameter $param, &$value)
292
    {
293 8
        $noAttributes = [];
294
295
        // add values which have attributes
296 8
        foreach ($value as $name => $v) {
297 8
            if ($property = $param->getProperty($name)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $property is correct as $param->getProperty($name) (which targets GuzzleHttp\Command\Guzzle\Parameter::getProperty()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
298 8
                if ($property->getData('xmlAttribute')) {
299 3
                    $this->addXml($writer, $property, $v);
300 3
                } else {
301 7
                    $noAttributes[] = ['value' => $v, 'property' => $property];
302
                }
303 8
            }
304 8
        }
305
306
        // now add values with no attributes
307 8
        foreach ($noAttributes as $element) {
308 7
            $this->addXml($writer, $element['property'], $element['value']);
309 8
        }
310 8
    }
311
312
    /**
313
     * @param $value
314
     * @param Parameter $param
315
     * @param Operation $operation
316
     */
317 17
    private function visitWithValue(
318
        $value,
319
        Parameter $param,
320
        Operation $operation
321
    ) {
322 17
        if (!$this->writer) {
323 17
            $this->createRootElement($operation);
324 17
        }
325
326 17
        $this->addXml($this->writer, $param, $value);
327 17
    }
328
}
329