Test Failed
Pull Request — master (#174)
by
unknown
04:41
created

XmlLocation::writeElement()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.4326

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 7
cts 11
cp 0.6364
rs 9.7998
c 0
b 0
f 0
cc 3
nc 4
nop 5
crap 3.4326
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 3
    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 3
        if ($param->getData('xmlAttribute')) {
52
            array_unshift($this->buffered, $param);
53
        } else {
54 3
            $this->buffered[] = $param;
55
        }
56
57 3
        return $request;
58
    }
59
60
    /**
61
     * @param CommandInterface $command
62
     * @param RequestInterface $request
63
     * @param Operation        $operation
64
     *
65
     * @return RequestInterface
66
     */
67 4
    public function after(
68
        CommandInterface $command,
69
        RequestInterface $request,
70
        Operation $operation
71
    ) {
72 4
        foreach ($this->buffered as $param) {
73 3
            $this->visitWithValue(
74 3
                $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 3
                $param,
76
                $operation
77 3
            );
78 4
        }
79
80 4
        $this->buffered = [];
81
82 4
        $additional = $operation->getAdditionalParameters();
83 4 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 4
        $xml = '';
95 4
        if ($this->writer) {
96 3
            $xml = $this->finishDocument($this->writer);
97 4
        } 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 4
        if ($xml !== '') {
104 4
            $request = $request->withBody(Psr7\stream_for($xml));
105
            // Don't overwrite the Content-Type if one is set
106 4
            if ($this->contentType && !$request->hasHeader('Content-Type')) {
107 4
                $request = $request->withHeader('Content-Type', $this->contentType);
108 4
            }
109 4
        }
110
111 4
        $this->writer = null;
112
113 4
        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 4
    protected function createRootElement(Operation $operation)
124
    {
125 4
        static $defaultRoot = ['name' => 'Request'];
126
        // If no root element was specified, then just wrap the XML in 'Request'
127 4
        $root = $operation->getData('xmlRoot') ?: $defaultRoot;
128
        // Allow the XML declaration to be customized with xmlEncoding
129 4
        $encoding = $operation->getData('xmlEncoding');
130 4
        $writer = $this->startDocument($encoding);
131 4
        $writer->startElement($root['name']);
132
133
        // Create the wrapping element with no namespaces if no namespaces were present
134 4
        if (!empty($root['namespaces'])) {
135
            // Create the wrapping element with an array of one or more namespaces
136
            foreach ((array) $root['namespaces'] as $prefix => $uri) {
137
                $nsLabel = 'xmlns';
138
                if (!is_numeric($prefix)) {
139
                    $nsLabel .= ':'.$prefix;
140
                }
141
                $writer->writeAttribute($nsLabel, $uri);
142
            }
143
        }
144
145 4
        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 3
    protected function addXml(\XMLWriter $writer, Parameter $param, $value)
156
    {
157 3
        $value = $param->filter($value);
158 3
        $type = $param->getType();
159 3
        $name = $param->getWireName();
160 3
        $prefix = null;
161 3
        $namespace = $param->getData('xmlNamespace');
162 3
        if (false !== strpos($name, ':')) {
163
            list($prefix, $name) = explode(':', $name, 2);
164
        }
165
166 3
        if ($type == 'object' || $type == 'array') {
167
            if (!$param->getData('xmlFlattened')) {
168
                if ($namespace) {
169
                    $writer->startElementNS(null, $name, $namespace);
170
                } else {
171
                    $writer->startElement($name);
172
                }
173
            }
174
            if ($param->getType() == 'array') {
175
                $this->addXmlArray($writer, $param, $value);
176
            } elseif ($param->getType() == 'object') {
177
                $this->addXmlObject($writer, $param, $value);
178
            }
179
            if (!$param->getData('xmlFlattened')) {
180
                $writer->endElement();
181
            }
182
            return;
183
        }
184 3
        if ($param->getData('xmlAttribute')) {
185
            $this->writeAttribute($writer, $prefix, $name, $namespace, $value);
186
        } else {
187 3
            $this->writeElement($writer, $prefix, $name, $namespace, $value);
188
        }
189 3
    }
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
    protected function writeAttribute($writer, $prefix, $name, $namespace, $value)
201
    {
202
        if ($namespace) {
203
            $writer->writeAttributeNS($prefix, $name, $namespace, $value);
204
        } else {
205
            $writer->writeAttribute($name, $value);
206
        }
207
    }
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 3
    protected function writeElement(\XMLWriter $writer, $prefix, $name, $namespace, $value)
219
    {
220 3
        if ($namespace) {
221
            $writer->startElementNS($prefix, $name, $namespace);
222
        } else {
223 3
            $writer->startElement($name);
224
        }
225 3
        if (strpbrk($value, '<>&')) {
226
            $writer->writeCData($value);
227
        } else {
228 3
            $writer->writeRaw($value);
229
        }
230 3
        $writer->endElement();
231 3
    }
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 4
    protected function startDocument($encoding)
242
    {
243 4
        $this->writer = new \XMLWriter();
244 4
        if (!$this->writer->openMemory()) {
245
            throw new \RuntimeException('Unable to open XML document in memory');
246
        }
247 4
        if (!$this->writer->startDocument('1.0', $encoding)) {
248
            throw new \RuntimeException('Unable to start XML document');
249
        }
250
251 4
        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 4
    protected function finishDocument($writer)
262
    {
263 4
        $writer->endDocument();
264
265 4
        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
    protected function addXmlArray(\XMLWriter $writer, Parameter $param, &$value)
276
    {
277
        if ($items = $param->getItems()) {
278
            foreach ($value as $v) {
279
                $this->addXml($writer, $items, $v);
280
            }
281
        }
282
    }
283
284
    /**
285
     * Add an object to the XML
286
     *
287
     * @param \XMLWriter $writer
288
     * @param Parameter $param
289
     * @param $value
290
     */
291
    protected function addXmlObject(\XMLWriter $writer, Parameter $param, &$value)
292
    {
293
        $noAttributes = [];
294
295
        // add values which have attributes
296
        foreach ($value as $name => $v) {
297
            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
                if ($property->getData('xmlAttribute')) {
299
                    $this->addXml($writer, $property, $v);
300
                } else {
301
                    $noAttributes[] = ['value' => $v, 'property' => $property];
302
                }
303
            }
304
        }
305
306
        // now add values with no attributes
307
        foreach ($noAttributes as $element) {
308
            $this->addXml($writer, $element['property'], $element['value']);
309
        }
310
    }
311
312
    /**
313
     * @param $value
314
     * @param Parameter $param
315
     * @param Operation $operation
316
     */
317 3
    private function visitWithValue(
318
        $value,
319
        Parameter $param,
320
        Operation $operation
321
    ) {
322 3
        if (!$this->writer) {
323 3
            $this->createRootElement($operation);
324 3
        }
325
326 3
        $this->addXml($this->writer, $param, $value);
327 3
    }
328
}
329