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) { |
|
|
|
|
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) { |
|
|
|
|
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'); |
|
|
|
|
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) |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
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.