Map::pathToValue()   C
last analyzed

Complexity

Conditions 14
Paths 17

Size

Total Lines 54
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 14

Importance

Changes 9
Bugs 2 Features 3
Metric Value
c 9
b 2
f 3
dl 0
loc 54
ccs 32
cts 32
cp 1
rs 6.7343
cc 14
eloc 37
nc 17
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
/**
3
 * Fwk
4
 *
5
 * Copyright (c) 2011-2012, Julien Ballestracci <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
12
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
13
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
15
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
16
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
17
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
21
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
22
 * POSSIBILITY OF SUCH DAMAGE.
23
 *
24
 * PHP Version 5.3
25
 *
26
 * @category  XML
27
 * @package   Fwk\Xml
28
 * @author    Julien Ballestracci <[email protected]>
29
 * @copyright 2011-2014 Julien Ballestracci <[email protected]>
30
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
31
 * @link      http://www.nitronet.org/fwk
32
 */
33
namespace Fwk\Xml;
34
35
use \SimpleXMLElement;
36
37
/**
38
 * Map
39
 *
40
 * Once executed on a XmlFile, it transforms XML data into an Array according
41
 * to defined Paths.
42
 *
43
 * @category Library
44
 * @package  Fwk\Xml
45
 * @author   Julien Ballestracci <[email protected]>
46
 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD License
47
 * @link     http://www.nitronet.org/fwk
48
 */
49
class Map
50
{
51
    /**
52
     * List of Paths
53
     * @var array
54
     */
55
    protected $paths = array();
56
57
    /**
58
     * List of registered namespaces
59
     * @var array
60
     */
61
    protected $namespaces = array();
62
63
    /**
64
     * Adds a Path to this map
65
     *
66
     * @param Path|array $path Path (or list of Paths) to be added
67
     *
68
     * @throws \InvalidArgumentException if $path is not Path and not an array
69
     * @return Map
70
     */
71 14
    public function add($path)
72
    {
73 14
        if (!is_array($path)) {
74 12
            $path = array($path);
75
        }
76
77 14
        foreach ($path as $current) {
78 14
            if (!$current instanceof Path) {
79 1
                throw new \InvalidArgumentException('Argument is not a Path');
80
            }
81 13
            $this->paths[] = $current;
82
        }
83
84 13
        return $this;
85
    }
86
87
    /**
88
     * Removes a Path from this map
89
     *
90
     * @param Path $path Path object to be removed
91
     *
92
     * @return Map
93
     */
94 1
    public function remove(Path $path)
95
    {
96 1
        $paths = array();
97 1
        foreach ($this->paths as $current) {
98 1
            if ($current !== $path) {
99 1
                array_push($paths, $current);
100
            }
101
        }
102
103 1
        $this->paths = $paths;
104
105 1
        return $this;
106
    }
107
108
    /**
109
     * Registers a namespace prefix
110
     * 
111
     * @param string $nsPrefix     NS prefix 
112
     * @param string $namespaceUrl URL to namespace definition
113
     *
114
     * @return Map
115
     */
116 1
    public function registerNamespace($nsPrefix, $namespaceUrl)
117
    {
118 1
        $this->namespaces[$nsPrefix] = $namespaceUrl;
119
120 1
        return $this;
121
    }
122
123
    /**
124
     * Executes the Map against the XmlFile and return parsed values as a PHP
125
     * array.
126
     *
127
     * @param XmlFile $file The XML file
128
     *
129
     * @return array
130
     */
131 12
    public function execute(XmlFile $file)
132
    {
133 12
        $paths = $this->paths;
134 12
        $final = array();
135
136 12
        $this->registerNamespaces($file);
137
138 12
        foreach ($paths as $path) {
139 12
            $key    = $path->getKey();
140 12
            $sxml   = $file->xpath($path->getXpath());
141 12
            $value  = $path->getDefault();
142 12
            if (is_array($sxml) && count($sxml)) {
143 11
                $value = $this->pathToValue($sxml, $path);
144
            } 
145
            
146 12
            $final[$key]    = $value;
147
        }
148
149 12
        return $final;
150
    }
151
152
    /**
153
     * Transform a Path to a value.
154
     *
155
     * @param array $sxml Current Xpath result
156
     * @param Path  $path Current Path
157
     *
158
     * @return mixed
159
     */
160 11
    protected function pathToValue(array $sxml, Path $path)
161
    {
162 11
        $value = null;
163
164 11
        $this->registerNamespaces(null, $sxml);
165
166 11
        foreach ($sxml as $result) {
167 11
            $current = $this->getAttributesArray($path, $result);
168 11
            if ($path->isLoop()) {
169 7
                if (!count($path->getAttributes())
170 7
                    && !count($path->getChildrens())
171
                ) {
172 5
                    $current = $this->getFilteredValue($path, trim((string)$result));
173 5
                } elseif (!count($path->getChildrens()) && $path->hasValueKey()) {
174 1
                    $val = $this->getFilteredValue($path, trim((string)$result));
175 1
                    $current[$path->getValueKey()] = $val;
176 4
                } elseif (count($path->getChildrens())) {
177 4
                    $current += $this->getChildrens($path, $result);
178
                }
179
180 7
                $loopId = $path->getLoopId();
181 7
                if (empty($loopId)) {
182 5
                    $value[] = $current;
183
                } else {
184 2
                    $idValue = $result->xpath($loopId);
185 2
                    if (!count($idValue)) {
186 1
                        $value[] = $current;
187
                    } else {
188 7
                        $value[trim((string)$idValue[0])] = $current;
189
                    }
190
                }
191
            } else {
192 7
                if (!count($path->getAttributes())
193 7
                    && !count($path->getChildrens())
194
                ) {
195 6
                    $current = $this->getFilteredValue($path, trim((string)$result));
196 6
                    if (empty($current)) {
197 6
                        $current = $path->getDefault();
198
                    }
199
                } else {
200 4
                    $current += $this->getChildrens($path, $result);
201 4
                    if ($path->hasValueKey()) {
202 3
                        $current[$path->getValueKey()] = $this->getFilteredValue(
203
                            $path,
204 3
                            trim((string)$result)
205
                        );
206
                    }
207
                }
208 11
                $value = $current;
209
            }
210
        }
211
212 11
        return $value;
213
    }
214
215
    /**
216
     * Returns a value, filtered if needed.
217
     *
218
     * @param Path   $path  Current Path
219
     * @param string $value Actual value
220
     *
221
     * @return mixed
222
     */
223 9
    protected function getFilteredValue(Path $path, $value)
224
    {
225 9
        if ($path->hasFilter()) {
226 1
            return call_user_func($path->getFilter(), $value);
227
        }
228
        
229 8
        return $value;
230
    }
231
232
233
    /**
234
     * Returns Path attributes list
235
     *
236
     * @param Path             $path Current Path
237
     * @param SimpleXMLElement $node Current SimpleXML node
238
     *
239
     * @return array
240
     */
241 11
    protected function getAttributesArray(Path $path,
242
        SimpleXMLElement $node
243
    ) {
244 11
        $current = array();
245 11
        foreach ($path->getAttributes() as $keyName => $attr) {
246 4
            $val = (isset($node[$attr]) ? trim((string)$node[$attr]) : null);
247 4
            $current[$keyName] = self::getFilteredValue($path, $val);
248
        }
249
250 11
        return $current;
251
    }
252
253
    /**
254
     * Returns childrens values
255
     *
256
     * @param Path             $path Current Path
257
     * @param SimpleXMLElement $node Current SimpleXML node
258
     *
259
     * @return array
260
     */
261 5
    protected function getChildrens(Path $path, SimpleXMLElement $node)
262
    {
263 5
        $current = array();
264
265 5
        foreach ($path->getChildrens() as $child) {
266 5
            $key    = $child->getKey();
267 5
            $csxml  = $node->xpath($child->getXpath());
268 5
            $val    = $child->getDefault();
269
            
270 5
            if (is_array($csxml) && count($csxml)) {
271 3
                $val = $this->pathToValue($csxml, $child);
272
            } 
273
            
274 5
            $current[$key]  = $val;
275 5
            if ($val === null && $child->isLoop()) {
276 5
                $current[$key] = array();
277
            }
278
        }
279
280 5
        return $current;
281
    }
282
283
    /**
284
     * Apply rules for registered namespaces
285
     * 
286
     * @param XmlFile $file The current XmlFile object
0 ignored issues
show
Documentation introduced by
Should the type for parameter $file not be null|XmlFile?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
287
     * @param array   $sxml SimpleXML results
0 ignored issues
show
Documentation introduced by
Should the type for parameter $sxml not be null|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
288
     * 
289
     * @return void
290
     */
291 12
    protected function registerNamespaces(XmlFile $file = null, 
292
        array $sxml = null
293
    ) {
294 12
        if (null === $sxml && $file instanceof XmlFile) {
295 12
            foreach ($this->namespaces as $prefix => $ns) {
296 12
                $file->open()->registerXPathNamespace($prefix, $ns);
297
            }
298 11
        } elseif (null !== $sxml) {
299 11
            foreach ($sxml as $node) {
300 11
                foreach ($this->namespaces as $prefix => $ns) {
301 11
                    $node->registerXPathNamespace($prefix, $ns);
302
                }
303
            }
304
        }
305 12
    }
306
307
    /**
308
     * Returns Map's Paths
309
     *
310
     * @return array
311
     */
312 1
    public function getPaths()
313
    {
314 1
        return $this->paths;
315
    }
316
}