ArrayPathDefinition::unsetValue()   C
last analyzed

Complexity

Conditions 13
Paths 8

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 22
nc 8
nop 2
dl 0
loc 31
rs 5.1234
c 0
b 0
f 0

How to fix   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
// +---------------------------------------------------------------------------+
4
// | This file is part of the Agavi package.                                   |
5
// | Copyright (c) 2005-2011 the Agavi Project.                                |
6
// |                                                                           |
7
// | For the full copyright and license information, please view the LICENSE   |
8
// | file that was distributed with this source code. You can also view the    |
9
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
10
// |   vi: set noexpandtab:                                                    |
11
// |   Local Variables:                                                        |
12
// |   indent-tabs-mode: t                                                     |
13
// |   End:                                                                    |
14
// +---------------------------------------------------------------------------+
15
16
namespace Agavi\Util;
17
18
/**
19
 * PathDefinition implements handling of virtual paths
20
 *
21
 * This class does not implement real filesystem path handling, but uses virtual
22
 * paths. It is primary used in the validation system for handling arrays of
23
 * input.
24
 *
25
 * @package    agavi
26
 * @subpackage util
27
 *
28
 * @author     Uwe Mesecke <[email protected]>
29
 * @author     Dominik del Bondio <[email protected]>
30
 * @copyright  Authors
31
 * @copyright  The Agavi Project
32
 *
33
 * @since      0.11.0
34
 *
35
 * @version    $Id$
36
 */
37
final class ArrayPathDefinition
38
{
39
    /**
40
     * constructor
41
     *
42
     * @author     Dominik del Bondio <[email protected]>
43
     * @since      0.11.0
44
     */
45
    private function __construct()
46
    {
47
    }
48
49
    /**
50
     * Converts the given argument to an array of parts for use in the path getter/setters
51
     * @param      array|string $partsArrayOrPathString The path string or an array containing the path
52
     *                          divided into its individual parts.
53
     *
54
     * @return     array        The array of parts.
55
     *
56
     * @author     Dominik del Bondio <[email protected]>
57
     * @since      0.11.6
58
     */
59
    protected static function preparePartsArray($partsArrayOrPathString)
60
    {
61
        if (is_array($partsArrayOrPathString)) {
62
            return $partsArrayOrPathString;
63
        } else {
64
            $partInfo = self::getPartsFromPath($partsArrayOrPathString);
65
            $parts = $partInfo['parts'];
66
            if (!$partInfo['absolute']) {
67
                // the value wasn't absolute, so an empty string is used for the first part
68
                array_unshift($parts, '');
69
            }
70
            return $parts;
71
        }
72
    }
73
    
74
    /**
75
     * Unsets a value at the given path.
76
     *
77
     * @param      array|string $partsArrayOrPathString The path string or an array containing the path
78
     *                          divided into its individual parts.
79
     * @param      array $array The array we should operate on.
80
     *
81
     * @return     mixed The previously stored value.
82
     *
83
     * @author     Dominik del Bondio <[email protected]>
84
     * @since      0.11.0
85
     */
86
    public static function &unsetValue($partsArrayOrPathString, array &$array)
87
    {
88
        $parts = self::preparePartsArray($partsArrayOrPathString);
89
        
90
        $a =& $array;
91
92
        $c = count($parts);
93
        for ($i = 0; $i < $c; ++$i) {
94
            $part = $parts[$i];
95
            $last = ($i+1 == $c);
96
            if ($part !== null) {
97
                if (is_array($a) && is_numeric($part) && strpos($part, '.') === false && strpos($part, ',') === false && (isset($a[(int)$part]) || array_key_exists((int)$part, $a))) {
98
                    $part = (int)$part;
99
                }
100
                if (is_array($a) && (isset($a[$part]) || array_key_exists($part, $a))) {
101
                    if ($last) {
102
                        $oldValue =& $a[$part];
103
                        unset($a[$part]);
104
                        return $oldValue;
105
                    } else {
106
                        $a =& $a[$part];
107
                    }
108
                } else {
109
                    $retval = null;
110
                    return $retval;
111
                }
112
            }
113
        }
114
        $retval = null;
115
        return $retval;
116
    }
117
118
    /**
119
     * Checks whether the array has a value at the given path.
120
     *
121
     * @param      array|string $partsArrayOrPathString The path string or an array containing the path
122
     *                          divided into its individual parts.
123
     * @param      array $array The array we should operate on.
124
     *
125
     * @return     bool Whether the path exists in this array.
126
     *
127
     * @author     Dominik del Bondio <[email protected]>
128
     * @since      0.11.0
129
     */
130
    public static function hasValue($partsArrayOrPathString, array &$array)
131
    {
132
        $parts = self::preparePartsArray($partsArrayOrPathString);
133
        
134
        $a = $array;
135
136 View Code Duplication
        foreach ($parts as $part) {
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...
137
            if ($part !== null) {
138
                if (is_array($a) && is_numeric($part) && strpos($part, '.') === false && strpos($part, ',') === false && (isset($a[(int)$part]) || array_key_exists((int)$part, $a))) {
139
                    $part = (int)$part;
140
                }
141
                if (is_array($a) && (isset($a[$part]) || array_key_exists($part, $a))) {
142
                    $a = $a[$part];
143
                } else {
144
                    return false;
145
                }
146
            }
147
        }
148
149
        return true;
150
    }
151
152
    /**
153
     * Returns the value at the given path.
154
     *
155
     * @param      array|string $partsArrayOrPathString The path string or an array containing the path
156
     *                          divided into its individual parts.
157
     * @param      array $array The array we should operate on.
158
     * @param      mixed $default A default value if the path doesn't exist in the array.
159
     *
160
     * @return     mixed The value stored at the given path.
161
     *
162
     * @author     Dominik del Bondio <[email protected]>
163
     * @since      0.11.0
164
     */
165
    public static function &getValue($partsArrayOrPathString, array &$array, $default = null)
166
    {
167
        $parts = self::preparePartsArray($partsArrayOrPathString);
168
        
169
        $a = &$array;
170
171 View Code Duplication
        foreach ($parts as $part) {
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...
172
            if ($part !== null) {
173
                if (is_array($a) && is_numeric($part) && strpos($part, '.') === false && strpos($part, ',') === false && (isset($a[(int)$part]) || array_key_exists((int)$part, $a))) {
174
                    $part = (int)$part;
175
                }
176
                if (is_array($a) && (isset($a[$part]) || array_key_exists($part, $a))) {
177
                    $a = &$a[$part];
178
                } else {
179
                    //throw new AgaviException('The part: ' . $part . ' does not exist in the given array');
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
180
                    return $default;
181
                }
182
            }
183
        }
184
185
        return $a;
186
    }
187
188
    /**
189
     * Sets the value at the given path.
190
     *
191
     * @param      array|string $partsArrayOrPathString The path string or an array containing the path
192
     *                          divided into its individual parts.
193
     * @param      array $array The array we should operate on.
194
     * @param      mixed $value The value.
195
     *
196
     * @author     Dominik del Bondio <[email protected]>
197
     * @since      0.11.0
198
     */
199
    public static function setValue($partsArrayOrPathString, array &$array, $value)
200
    {
201
        $parts = self::preparePartsArray($partsArrayOrPathString);
202
        
203
        $a = &$array;
204
205
        foreach ($parts as $part) {
206
            if ($part !== null) {
207
                if (is_array($a) && is_numeric($part) && strpos($part, '.') === false && strpos($part, ',') === false && (isset($a[(int)$part]) || array_key_exists((int)$part, $a))) {
208
                    $part = (int)$part;
209
                }
210
                if (!isset($a[$part]) || !is_array($a[$part]) || (is_array($a) && !(isset($a[$part]) || array_key_exists($part, $a)))) {
211
                    $a[$part] = array();
212
                }
213
                $a = &$a[$part];
214
            }
215
        }
216
217
        $a = $value;
218
    }
219
220
    /**
221
     * Returns an array with the single parts of the given path.
222
     *
223
     * @param      string $path The path.
224
     *
225
     * @return     array The parts of the given path.
226
     *
227
     * @author     Dominik del Bondio <[email protected]>
228
     * @since      0.11.0
229
     */
230
    public static function getPartsFromPath($path)
231
    {
232
        if (strlen($path) == 0) {
233
            return array('parts' => array(), 'absolute' => true);
234
        }
235
236
        $parts = array();
237
        $absolute = ($path[0] != '[');
238
        if (($pos = strpos($path, '[')) === false) {
239
            if (strpos($path, ']') !== false) {
240
                throw new \InvalidArgumentException('Invalid "]" without opening "[" found');
241
            }
242
            $parts[] = $path;
243
        } else {
244
            $state = 0;
245
            $cur = '';
246
            foreach (str_split($path) as $c) {
247
                // this is the fastest way to loop over an string
248
                switch ($state) {
249
                    // the order is significant for performance
250
                    case 2:
251
                        // match all characters between []
252
                        if ($c == ']') {
253
                            $parts[] = $cur;
254
                            $cur = '';
255
                            $state = 1;
256
                        } elseif ($c == '[') {
257
                            throw new \InvalidArgumentException('Invalid "[[" found');
258
                        } else {
259
                            $cur .= $c;
260
                        }
261
                        
262
                        break;
263
264
                    case 0:
265
                        // match everything to the first '['
266
                        if ($c != '[') {
267
                            $cur .= $c;
268
                        } else {
269
                            if ($cur !== '') {
270
                                $parts[] = $cur;
271
                                $cur = '';
272
                            }
273
                            $state = 2;
274
                        }
275
                        break;
276
277
                    case 1:
278
                        // match exactly '['
279
                        if ($c == '[') {
280
                            $state = 2;
281
                        } else {
282
                            throw new \InvalidArgumentException('Invalid character after "]" found');
283
                        }
284
                        break;
285
                }
286
            }
287
            if ($state == 0) {
288
                $parts[] = $cur;
289
            } elseif ($state == 2) {
290
                throw new \InvalidArgumentException('Missing "]" after opening "["');
291
            }
292
        }
293
294
        return array('parts' => $parts, 'absolute' => $absolute);
295
    }
296
297
298
    /**
299
     * Returns the flat key names of an array.
300
     *
301
     * This method calls itself recursively to flatten the keys.
302
     *
303
     * @param      array $array The array which keys should be returned.
304
     * @param      string $prefix The prefix for the name (only for internal use).
305
     *
306
     * @return     array The flattened keys.
307
     *
308
     * @author     Dominik del Bondio <[email protected]>
309
     * @since      0.11.0
310
     */
311 View Code Duplication
    public static function getFlatKeyNames(array $array, $prefix = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
312
    {
313
        $names = array();
314
        foreach ($array as $key => $value) {
315
            if ($prefix === null) {
316
                // create the top node when no prefix was given
317
                if (strlen($key) == 0) {
318
                    // when an empty key was used at top level, create a "relative" path, so the empty string doesn't get lost
319
                    $name = '[' . $key . ']';
320
                } else {
321
                    $name = $key;
322
                }
323
            } else {
324
                $name = $prefix . '[' . $key . ']';
325
            }
326
327
            if (is_array($value)) {
328
                $names = array_merge($names, ArrayPathDefinition::getFlatKeyNames($value, $name));
329
            } else {
330
                $names[] = $name;
331
            }
332
        }
333
        return $names;
334
    }
335
    
336
    /**
337
     * Returns the flattened version of an array. So the returned array
338
     * will be one dimensional with the flattened key names as keys
339
     * and their values from the original array as values.
340
     *
341
     * This method calls itself recursively to flatten the array.
342
     *
343
     * @param      array $array The array which should be flattened.
344
     * @param      string $prefix The prefix for the key names (only for internal use).
345
     *
346
     * @return     array The flattened array.
347
     *
348
     * @author     Dominik del Bondio <[email protected]>
349
     * @since      1.0.0
350
     */
351 View Code Duplication
    public static function flatten($array, $prefix = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
352
    {
353
        $flatArray = array();
354
        foreach ($array as $key => $value) {
355
            if ($prefix === null) {
356
                // create the top node when no prefix was given
357
                if (strlen($key) == 0) {
358
                    // when an empty key was used at top level, create a "relative" path, so the empty string doesn't get lost
359
                    $name = '[' . $key . ']';
360
                } else {
361
                    $name = $key;
362
                }
363
            } else {
364
                $name = $prefix . '[' . $key . ']';
365
            }
366
            
367
            if (is_array($value)) {
368
                $flatArray += ArrayPathDefinition::flatten($value, $name);
369
            } else {
370
                $flatArray[$name] = $value;
371
            }
372
        }
373
        return $flatArray;
374
    }
375
}
376