Completed
Push — master ( d8465d...85c396 )
by Stefano
03:17
created

XmlLocation   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 316
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 95.8%

Importance

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

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A visit() 0 16 2
B createRootElement() 0 24 5
D 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
B addXmlObject() 0 20 5
A visitWithValue() 0 11 2
C after() 0 48 11

How to fix   Complexity   

Complex Class

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
        if ($additional && $additional->getLocation() == $this->locationName) {
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