XmlLocation::processObject()   C
last analyzed

Complexity

Conditions 14
Paths 10

Size

Total Lines 63

Duplication

Lines 5
Ratio 7.94 %

Code Coverage

Tests 43
CRAP Score 14

Importance

Changes 0
Metric Value
dl 5
loc 63
ccs 43
cts 43
cp 1
rs 6.2666
c 0
b 0
f 0
cc 14
nc 10
nop 2
crap 14

How to fix   Long Method    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
namespace GuzzleHttp\Command\Guzzle\ResponseLocation;
3
4
use GuzzleHttp\Command\Guzzle\Parameter;
5
use GuzzleHttp\Command\Result;
6
use GuzzleHttp\Command\ResultInterface;
7
use Psr\Http\Message\ResponseInterface;
8
9
/**
10
 * Extracts elements from an XML document
11
 */
12
class XmlLocation extends AbstractLocation
13
{
14
    /** @var \SimpleXMLElement XML document being visited */
15
    private $xml;
16
17
    /**
18
     * Set the name of the location
19
     *
20
     * @param string $locationName
21
     */
22 17
    public function __construct($locationName = 'xml')
23
    {
24 17
        parent::__construct($locationName);
25 17
    }
26
27
    /**
28
     * @param ResultInterface $result
29
     * @param ResponseInterface $response
30
     * @param Parameter $model
31
     * @return ResultInterface
32
     */
33 17
    public function before(
34
        ResultInterface $result,
35
        ResponseInterface $response,
36
        Parameter $model
37
    ) {
38 17
        $this->xml = simplexml_load_string((string) $response->getBody());
39
40 17
        return $result;
41
    }
42
43
    /**
44
     * @param ResultInterface $result
45
     * @param ResponseInterface $response
46
     * @param Parameter $model
47
     * @return Result|ResultInterface
48
     */
49 17
    public function after(
50
        ResultInterface $result,
51
        ResponseInterface $response,
52
        Parameter $model
53
    ) {
54
        // Handle additional, undefined properties
55 17
        $additional = $model->getAdditionalProperties();
56 17
        if ($additional instanceof Parameter &&
57 1
            $additional->getLocation() == $this->locationName
58 17
        ) {
59 1
            $result = new Result(array_merge(
0 ignored issues
show
Bug Compatibility introduced by
The expression new \GuzzleHttp\Command\...lToArray($this->xml))); of type GuzzleHttp\Command\Result adds the type GuzzleHttp\Command\Result to the return on line 67 which is incompatible with the return type declared by the interface GuzzleHttp\Command\Guzzl...ocationInterface::after of type GuzzleHttp\Command\ResultInterface.
Loading history...
60 1
                $result->toArray(),
61 1
                self::xmlToArray($this->xml)
62 1
            ));
63 1
        }
64
65 17
        $this->xml = null;
66
67 17
        return $result;
68
    }
69
70
    /**
71
     * @param ResultInterface $result
72
     * @param ResponseInterface $response
73
     * @param Parameter $param
74
     * @return ResultInterface
75
     */
76 17
    public function visit(
77
        ResultInterface $result,
78
        ResponseInterface $response,
79
        Parameter $param
80
    ) {
81 17
        $sentAs = $param->getWireName();
82 17
        $ns = null;
83 17
        if (strstr($sentAs, ':')) {
84
            list($ns, $sentAs) = explode(':', $sentAs);
85
        }
86
87
        // Process the primary property
88 17
        if (count($this->xml->children($ns, true)->{$sentAs})) {
89 16
            $result[$param->getName()] = $this->recursiveProcess(
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
90 16
                $param,
91 16
                $this->xml->children($ns, true)->{$sentAs}
92 16
            );
93 16
        }
94
95 17
        return $result;
96
    }
97
98
    /**
99
     * Recursively process a parameter while applying filters
100
     *
101
     * @param Parameter         $param API parameter being processed
102
     * @param \SimpleXMLElement $node  Node being processed
103
     * @return array
104
     */
105 16
    private function recursiveProcess(
106
        Parameter $param,
107
        \SimpleXMLElement $node
108
    ) {
109 16
        $result = [];
110 16
        $type = $param->getType();
111
112 16
        if ($type == 'object') {
113 11
            $result = $this->processObject($param, $node);
114 16
        } elseif ($type == 'array') {
115 12
            $result = $this->processArray($param, $node);
116 12
        } else {
117
            // We are probably handling a flat data node (i.e. string or
118
            // integer), so let's check if it's childless, which indicates a
119
            // node containing plain text.
120 12
            if ($node->children()->count() == 0) {
121
                // Retrieve text from node
122 12
                $result = (string) $node;
123 12
            }
124
        }
125
126
        // Filter out the value
127 16
        if (isset($result)) {
128 16
            $result = $param->filter($result);
129 16
        }
130
131 16
        return $result;
132
    }
133
134
    /**
135
     * @param Parameter $param
136
     * @param \SimpleXMLElement $node
137
     * @return array
138
     */
139 12
    private function processArray(Parameter $param, \SimpleXMLElement $node)
140
    {
141
        // Cast to an array if the value was a string, but should be an array
142 12
        $items = $param->getItems();
143 12
        $sentAs = $items->getWireName();
144 12
        $result = [];
145 12
        $ns = null;
146
147 12 View Code Duplication
        if (strstr($sentAs, ':')) {
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...
148
            // Get namespace from the wire name
149 1
            list($ns, $sentAs) = explode(':', $sentAs);
150 1
        } else {
151
            // Get namespace from data
152 11
            $ns = $items->getData('xmlNs');
153
        }
154
155 12
        if ($sentAs === null) {
156
            // A general collection of nodes
157 3
            foreach ($node as $child) {
158 3
                $result[] = $this->recursiveProcess($items, $child);
159 3
            }
160 3
        } else {
161
            // A collection of named, repeating nodes
162
            // (i.e. <collection><foo></foo><foo></foo></collection>)
163 9
            $children = $node->children($ns, true)->{$sentAs};
164 9
            foreach ($children as $child) {
165 8
                $result[] = $this->recursiveProcess($items, $child);
166 9
            }
167
        }
168
169 12
        return $result;
170
    }
171
172
    /**
173
     * Process an object
174
     *
175
     * @param Parameter         $param API parameter being parsed
176
     * @param \SimpleXMLElement $node  Value to process
177
     * @return array
178
     */
179 11
    private function processObject(Parameter $param, \SimpleXMLElement $node)
180
    {
181 11
        $result = $knownProps = $knownAttributes = [];
182
183
        // Handle known properties
184 11
        if ($properties = $param->getProperties()) {
185 10
            foreach ($properties as $property) {
186 10
                $name = $property->getName();
0 ignored issues
show
Bug introduced by
Consider using $property->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
187 10
                $sentAs = $property->getWireName();
188 10
                $knownProps[$sentAs] = 1;
189 10 View Code Duplication
                if (strpos($sentAs, ':')) {
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...
190 2
                    list($ns, $sentAs) = explode(':', $sentAs);
191 2
                } else {
192 10
                    $ns = $property->getData('xmlNs');
193
                }
194
195 10
                if ($property->getData('xmlAttribute')) {
196
                    // Handle XML attributes
197 2
                    $result[$name] = (string) $node->attributes($ns, true)->{$sentAs};
198 2
                    $knownAttributes[$sentAs] = 1;
199 10
                } elseif (count($node->children($ns, true)->{$sentAs})) {
200
                    // Found a child node matching wire name
201 10
                    $childNode = $node->children($ns, true)->{$sentAs};
202 10
                    $result[$name] = $this->recursiveProcess(
203 10
                        $property,
204
                        $childNode
205 10
                    );
206 10
                }
207 10
            }
208 10
        }
209
210
        // Handle additional, undefined properties
211 11
        $additional = $param->getAdditionalProperties();
212 11
        if ($additional instanceof Parameter) {
213
            // Process all child elements according to the given schema
214 2
            foreach ($node->children($additional->getData('xmlNs'), true) as $childNode) {
215 2
                $sentAs = $childNode->getName();
216 2
                if (!isset($knownProps[$sentAs])) {
217 2
                    $result[$sentAs] = $this->recursiveProcess(
218 2
                        $additional,
219
                        $childNode
220 2
                    );
221 2
                }
222 2
            }
223 11
        } elseif ($additional === null || $additional === true) {
224
            // Blindly transform the XML into an array preserving as much data
225
            // as possible. Remove processed, aliased properties.
226 9
            $array = array_diff_key(self::xmlToArray($node), $knownProps);
227
            // Remove @attributes that were explicitly plucked from the
228
            // attributes list.
229 9
            if (isset($array['@attributes']) && $knownAttributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $knownAttributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
230 1
                $array['@attributes'] = array_diff_key($array['@attributes'], $knownProps);
231 1
                if (!$array['@attributes']) {
232 1
                    unset($array['@attributes']);
233 1
                }
234 1
            }
235
236
            // Merge it together with the original result
237 9
            $result = array_merge($array, $result);
238 9
        }
239
240 11
        return $result;
241
    }
242
243
    /**
244
     * Convert an XML document to an array.
245
     *
246
     * @param \SimpleXMLElement $xml
247
     * @param int               $nesting
248
     * @param null              $ns
249
     *
250
     * @return array
251
     */
252 10
    private static function xmlToArray(
253
        \SimpleXMLElement $xml,
254
        $ns = null,
255
        $nesting = 0
256
    ) {
257 10
        $result = [];
258 10
        $children = $xml->children($ns, true);
259
260 10
        foreach ($children as $name => $child) {
261 10
            $attributes = (array) $child->attributes($ns, true);
262 10
            if (!isset($result[$name])) {
263 10
                $childArray = self::xmlToArray($child, $ns, $nesting + 1);
264 10
                $result[$name] = $attributes
265 10
                    ? array_merge($attributes, $childArray)
266 1
                    : $childArray;
267 10
                continue;
268
            }
269
            // A child element with this name exists so we're assuming
270
            // that the node contains a list of elements
271 2
            if (!is_array($result[$name])) {
272 2
                $result[$name] = [$result[$name]];
273 2
            } elseif (!isset($result[$name][0])) {
274
                // Convert the first child into the first element of a numerically indexed array
275
                $firstResult = $result[$name];
276
                $result[$name] = [];
277
                $result[$name][] = $firstResult;
278
            }
279 2
            $childArray = self::xmlToArray($child, $ns, $nesting + 1);
280 2
            if ($attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
281 1
                $result[$name][] = array_merge($attributes, $childArray);
282 1
            } else {
283 2
                $result[$name][] = $childArray;
284
            }
285 10
        }
286
287
        // Extract text from node
288 10
        $text = trim((string) $xml);
289 10
        if ($text === '') {
290 10
            $text = null;
291 10
        }
292
293
        // Process attributes
294 10
        $attributes = (array) $xml->attributes($ns, true);
295 10
        if ($attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
296 2
            if ($text !== null) {
297
                $result['value'] = $text;
298
            }
299 2
            $result = array_merge($attributes, $result);
300 10
        } elseif ($text !== null) {
301 8
            $result = $text;
302 8
        }
303
304
        // Make sure we're always returning an array
305 10
        if ($nesting == 0 && !is_array($result)) {
306
            $result = [$result];
307
        }
308
309 10
        return $result;
310
    }
311
}
312