Skip to content
This repository
Newer
Older
100644 790 lines (729 sloc) 22.947 kb
e7f3c313 »
2009-10-12 going lithium
1 <?php
2 /**
3 * Lithium: the most rad php framework
4 *
4f1a9c0d »
2011-03-28 Updating copyright year.
5 * @copyright Copyright 2011, Union of RAD (http://union-of-rad.org)
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
6 * @license http://opensource.org/licenses/bsd-license.php The BSD License
e7f3c313 »
2009-10-12 going lithium
7 */
8
9 namespace lithium\util;
10
e441ce59 »
2010-02-03 Adding class doc block, with XPath overview.
11 /**
12 * Used for complex manipulation, comparison, and access of array data. Some methods allow for
13 * XPath-like data access, as follows:
8de98e98 »
2010-08-24 Fixing coding standards violations. Closes ticket #143.
14 *
e441ce59 »
2010-02-03 Adding class doc block, with XPath overview.
15 * - `'/User/id'`: Similar to the classic {n}.User.id.
16 * - `'/User[2]/name'`: Selects the name of the second User.
17 * - `'/User[id>2]'`: Selects all Users with an id > 2.
18 * - `'/User[id>2][<5]'`: Selects all Users with an id > 2 but < 5.
19 * - `'/Post/Comment[author_name=John]/../name'`: Selects the name of
20 * all posts that have at least one comment written by John.
21 * - `'/Posts[name]'`: Selects all Posts that have a `'name'` key.
22 * - `'/Comment/.[1]'`: Selects the contents of the first comment.
23 * - `'/Comment/.[:last]'`: Selects the last comment.
24 * - `'/Comment/.[:first]'`: Selects the first comment.
25 * - `'/Comment[text=/lithium/i]`': Selects the all comments that have
26 * a text matching the regex `/lithium/i`.
27 * - `'/Comment/@*'`: Selects all key names of all comments.
28 */
e7f3c313 »
2009-10-12 going lithium
29 class Set {
30
31 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
32 * Add the keys/values in `$array2` that are not found in `$array` onto the end of `$array`.
33 *
34 * @param mixed $array Original array.
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
35 * @param mixed $array2 Second array to add onto the original.
36 * @return array An array containing all the keys of the second array not already present in the
37 * first.
9084d697 »
2010-02-11 initial refactoring \util\Set
38 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
39 public static function append(array $array, array $array2) {
40 if (!$array && $array2) {
9084d697 »
2010-02-11 initial refactoring \util\Set
41 return $array2;
42 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
43 foreach ($array2 as $key => $value) {
44 if (!isset($array[$key])) {
45 $array[$key] = $value;
46 } elseif (is_array($value)) {
47 $array[$key] = static::append($array[$key], $array2[$key]);
9084d697 »
2010-02-11 initial refactoring \util\Set
48 }
49 }
50 return $array;
51 }
52
53 /**
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
54 * Checks if a particular path is set in an array.
e7f3c313 »
2009-10-12 going lithium
55 *
5a40337c »
2009-10-20 Updating Set dockblocks.
56 * @param mixed $data Data to check on.
57 * @param mixed $path A dot-delimited string.
58 * @return boolean `true` if path is found, `false` otherwise.
e7f3c313 »
2009-10-12 going lithium
59 */
60 public static function check($data, $path = null) {
2ff7d356 »
2010-09-06 Removing unused code from `\util\Set`, adding code coverage.
61 if (!$path) {
e7f3c313 »
2009-10-12 going lithium
62 return $data;
63 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
64 $path = is_array($path) ? $path : explode('.', $path);
65
e7f3c313 »
2009-10-12 going lithium
66 foreach ($path as $i => $key) {
67 if (is_numeric($key) && intval($key) > 0 || $key === '0') {
68 $key = intval($key);
69 }
70 if ($i === count($path) - 1) {
64f60394 »
2009-11-16 Replacing various instances of array_key_exists with isset().
71 return (is_array($data) && isset($data[$key]));
e7f3c313 »
2009-10-12 going lithium
72 } else {
64f60394 »
2009-11-16 Replacing various instances of array_key_exists with isset().
73 if (!is_array($data) || !isset($data[$key])) {
e7f3c313 »
2009-10-12 going lithium
74 return false;
75 }
76 $data =& $data[$key];
77 }
78 }
79 }
80
81 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
82 * Creates an associative array using a `$path1` as the path to build its keys, and optionally
83 * `$path2` as path to get the values. If `$path2` is not specified, all values will be
84 * initialized to `null` (useful for `Set::merge()`). You can optionally group the values by
85 * what is obtained when following the path specified in `$groupPath`.
e7f3c313 »
2009-10-12 going lithium
86 *
9084d697 »
2010-02-11 initial refactoring \util\Set
87 * @param array $data Array from where to extract keys and values.
88 * @param mixed $path1 As an array, or as a dot-delimited string.
89 * @param mixed $path2 As an array, or as a dot-delimited string.
90 * @param string $groupPath As an array, or as a dot-delimited string.
91 * @return array Combined array.
e7f3c313 »
2009-10-12 going lithium
92 */
9084d697 »
2010-02-11 initial refactoring \util\Set
93 public static function combine($data, $path1 = null, $path2 = null, $groupPath = null) {
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
94 if (!$data) {
9084d697 »
2010-02-11 initial refactoring \util\Set
95 return array();
e7f3c313 »
2009-10-12 going lithium
96 }
9084d697 »
2010-02-11 initial refactoring \util\Set
97 if (is_object($data)) {
98 $data = get_object_vars($data);
e7f3c313 »
2009-10-12 going lithium
99 }
9084d697 »
2010-02-11 initial refactoring \util\Set
100 if (is_array($path1)) {
101 $format = array_shift($path1);
102 $keys = static::format($data, $format, $path1);
103 } else {
104 $keys = static::extract($data, $path1);
e7f3c313 »
2009-10-12 going lithium
105 }
9084d697 »
2010-02-11 initial refactoring \util\Set
106 $vals = array();
107 if (!empty($path2) && is_array($path2)) {
108 $format = array_shift($path2);
109 $vals = static::format($data, $format, $path2);
110 } elseif (!empty($path2)) {
111 $vals = static::extract($data, $path2);
e7f3c313 »
2009-10-12 going lithium
112 }
9084d697 »
2010-02-11 initial refactoring \util\Set
113 $valCount = count($vals);
e7f3c313 »
2009-10-12 going lithium
114 $count = count($keys);
115
9084d697 »
2010-02-11 initial refactoring \util\Set
116 for ($i = $valCount; $i < $count; $i++) {
117 $vals[$i] = null;
e7f3c313 »
2009-10-12 going lithium
118 }
9084d697 »
2010-02-11 initial refactoring \util\Set
119 if ($groupPath != null) {
120 $group = static::extract($data, $groupPath);
121 if (!empty($group)) {
122 $c = count($keys);
123 for ($i = 0; $i < $c; $i++) {
124 if (!isset($group[$i])) {
125 $group[$i] = 0;
e7f3c313 »
2009-10-12 going lithium
126 }
9084d697 »
2010-02-11 initial refactoring \util\Set
127 if (!isset($out[$group[$i]])) {
128 $out[$group[$i]] = array();
e7f3c313 »
2009-10-12 going lithium
129 }
9084d697 »
2010-02-11 initial refactoring \util\Set
130 $out[$group[$i]][$keys[$i]] = $vals[$i];
e7f3c313 »
2009-10-12 going lithium
131 }
9084d697 »
2010-02-11 initial refactoring \util\Set
132 return $out;
e7f3c313 »
2009-10-12 going lithium
133 }
134 }
9084d697 »
2010-02-11 initial refactoring \util\Set
135 return array_combine($keys, $vals);
e7f3c313 »
2009-10-12 going lithium
136 }
137
138 /**
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
139 * Determines if the array elements in `$array2` are wholly contained within `$array1`. Works
140 * recursively.
e7f3c313 »
2009-10-12 going lithium
141 *
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
142 * @param array $array1 First value.
143 * @param array $array2 Second value.
144 * @return boolean Returns `true` if `$array1` wholly contains the keys and values of `$array2`,
145 * otherwise, returns `false`. Returns `false` if either array is empty.
e7f3c313 »
2009-10-12 going lithium
146 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
147 public static function contains(array $array1, array $array2) {
148 if (!$array1 || !$array2) {
9084d697 »
2010-02-11 initial refactoring \util\Set
149 return false;
e7f3c313 »
2009-10-12 going lithium
150 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
151 foreach ($array2 as $key => $val) {
152 if (!isset($array1[$key]) || $array1[$key] != $val) {
153 return false;
154 }
155 if (is_array($val) && !static::contains($array1[$key], $val)) {
e7f3c313 »
2009-10-12 going lithium
156 return false;
157 }
158 }
159 return true;
160 }
161
162 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
163 * Counts the dimensions of an array. If `$all` is set to `false` (which is the default) it will
164 * only consider the dimension of the first element in the array.
e7f3c313 »
2009-10-12 going lithium
165 *
9084d697 »
2010-02-11 initial refactoring \util\Set
166 * @param array $data Array to count dimensions on.
167 * @param array $options
168 * @return integer The number of dimensions in `$array`.
e7f3c313 »
2009-10-12 going lithium
169 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
170 public static function depth($data, array $options = array()) {
171 $defaults = array('all' => false, 'count' => 0);
172 $options += $defaults;
173
174 if (!$data) {
9084d697 »
2010-02-11 initial refactoring \util\Set
175 return 0;
176 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
177
178 if (!$options['all']) {
179 return (is_array(reset($data))) ? static::depth(reset($data)) + 1 : 1;
e7f3c313 »
2009-10-12 going lithium
180 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
181 $depth = array($options['count']);
e7f3c313 »
2009-10-12 going lithium
182
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
183 if (is_array($data) && reset($data) !== false) {
184 foreach ($data as $value) {
185 $depth[] = static::depth($value, array(
186 'all' => $options['all'],
187 'count' => $options['count'] + 1
188 ));
e7f3c313 »
2009-10-12 going lithium
189 }
190 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
191 return max($depth);
e7f3c313 »
2009-10-12 going lithium
192 }
193
194 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
195 * Computes the difference between two arrays.
e7f3c313 »
2009-10-12 going lithium
196 *
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
197 * @param array $val1 First value.
198 * @param array $val2 Second value.
9084d697 »
2010-02-11 initial refactoring \util\Set
199 * @return array Computed difference.
e7f3c313 »
2009-10-12 going lithium
200 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
201 public static function diff(array $val1, array $val2) {
202 if (!$val1 || !$val2) {
203 return $val2 ?: $val1;
e7f3c313 »
2009-10-12 going lithium
204 }
9084d697 »
2010-02-11 initial refactoring \util\Set
205 $out = array();
206
207 foreach ($val1 as $key => $val) {
208 $exists = isset($val2[$key]);
209
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
210 if (($exists && $val2[$key] != $val) || !$exists) {
9084d697 »
2010-02-11 initial refactoring \util\Set
211 $out[$key] = $val;
e7f3c313 »
2009-10-12 going lithium
212 }
9084d697 »
2010-02-11 initial refactoring \util\Set
213 unset($val2[$key]);
e7f3c313 »
2009-10-12 going lithium
214 }
9084d697 »
2010-02-11 initial refactoring \util\Set
215
216 foreach ($val2 as $key => $val) {
217 if (!isset($out[$key])) {
218 $out[$key] = $val;
219 }
220 }
221 return $out;
e7f3c313 »
2009-10-12 going lithium
222 }
223
224 /**
5a40337c »
2009-10-20 Updating Set dockblocks.
225 * Implements partial support for XPath 2.0.
e7f3c313 »
2009-10-12 going lithium
226 *
a15ea2ac »
2009-12-10 hooks fixes for lithium/util
227 * @param array $data An array of data to extract from.
5a40337c »
2009-10-20 Updating Set dockblocks.
228 * @param string $path An absolute XPath 2.0 path. Only absolute paths starting with a
229 * single slash are supported right now. Implemented selectors:
230 * - `'/User/id'`: Similar to the classic {n}.User.id.
231 * - `'/User[2]/name'`: Selects the name of the second User.
232 * - `'/User[id>2]'`: Selects all Users with an id > 2.
233 * - `'/User[id>2][<5]'`: Selects all Users with an id > 2 but < 5.
234 * - `'/Post/Comment[author_name=John]/../name'`: Selects the name of
235 * all posts that have at least one comment written by John.
236 * - `'/Posts[name]'`: Selects all Posts that have a `'name'` key.
237 * - `'/Comment/.[1]'`: Selects the contents of the first comment.
238 * - `'/Comment/.[:last]'`: Selects the last comment.
239 * - `'/Comment/.[:first]'`: Selects the first comment.
240 * - `'/Comment[text=/lithium/i]`': Selects the all comments that have
241 * a text matching the regex `/lithium/i`.
242 * - `'/Comment/@*'`: Selects all key names of all comments.
a15ea2ac »
2009-12-10 hooks fixes for lithium/util
243 * @param array $options Currently only supports `'flatten'` which can be
5a40337c »
2009-10-20 Updating Set dockblocks.
244 * disabled for higher XPath-ness.
245 * @return array An array of matched items.
e7f3c313 »
2009-10-12 going lithium
246 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
247 public static function extract(array $data, $path = null, array $options = array()) {
248 if (!$data) {
e7f3c313 »
2009-10-12 going lithium
249 return array();
250 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
251
e7f3c313 »
2009-10-12 going lithium
252 if (is_string($data)) {
253 $tmp = $path;
254 $path = $data;
255 $data = $tmp;
256 unset($tmp);
257 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
258
e7f3c313 »
2009-10-12 going lithium
259 if ($path === '/') {
6c890942 »
2010-01-10 Removing `util\Set::filter()`, and updating `Set` tests.
260 return array_filter($data, function($data) {
261 return ($data === 0 || $data === '0' || !empty($data));
262 });
e7f3c313 »
2009-10-12 going lithium
263 }
264 $contexts = $data;
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
265 $defaults = array('flatten' => true);
266 $options += $defaults;
6c890942 »
2010-01-10 Removing `util\Set::filter()`, and updating `Set` tests.
267
e7f3c313 »
2009-10-12 going lithium
268 if (!isset($contexts[0])) {
269 $contexts = array($data);
270 }
271 $tokens = array_slice(preg_split('/(?<!=)\/(?![a-z-]*\])/', $path), 1);
272
273 do {
274 $token = array_shift($tokens);
275 $conditions = false;
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
276
e7f3c313 »
2009-10-12 going lithium
277 if (preg_match_all('/\[([^=]+=\/[^\/]+\/|[^\]]+)\]/', $token, $m)) {
278 $conditions = $m[1];
279 $token = substr($token, 0, strpos($token, '['));
280 }
281 $matches = array();
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
282
e7f3c313 »
2009-10-12 going lithium
283 foreach ($contexts as $key => $context) {
284 if (!isset($context['trace'])) {
285 $context = array('trace' => array(null), 'item' => $context, 'key' => $key);
286 }
287 if ($token === '..') {
288 if (count($context['trace']) == 1) {
289 $context['trace'][] = $context['key'];
290 }
291 $parent = join('/', $context['trace']) . '/.';
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
292 $context['item'] = static::extract($data, $parent);
e7f3c313 »
2009-10-12 going lithium
293 $context['key'] = array_pop($context['trace']);
294 if (isset($context['trace'][1]) && $context['trace'][1] > 0) {
295 $context['item'] = $context['item'][0];
296 } elseif (!empty($context['item'][$key])) {
297 $context['item'] = $context['item'][$key];
298 } else {
299 $context['item'] = array_shift($context['item']);
300 }
301 $matches[] = $context;
302 continue;
303 }
304 $match = false;
305 if ($token === '@*' && is_array($context['item'])) {
306 $matches[] = array(
9decac0c »
2009-12-13 insert space after type casting parenthesis
307 'trace' => array_merge($context['trace'], (array) $key),
e7f3c313 »
2009-10-12 going lithium
308 'key' => $key,
26c2192d »
2011-03-23 QA: Removing trailing comma in arrays, lowercasing some keywords.
309 'item' => array_keys($context['item'])
e7f3c313 »
2009-10-12 going lithium
310 );
64f60394 »
2009-11-16 Replacing various instances of array_key_exists with isset().
311 } elseif (is_array($context['item']) && isset($context['item'][$token])) {
e7f3c313 »
2009-10-12 going lithium
312 $items = $context['item'][$token];
313 if (!is_array($items)) {
314 $items = array($items);
315 } elseif (!isset($items[0])) {
316 $current = current($items);
317 if ((is_array($current) && count($items) <= 1) || !is_array($current)) {
318 $items = array($items);
319 }
320 }
321
322 foreach ($items as $key => $item) {
323 $ctext = array($context['key']);
324 if (!is_numeric($key)) {
325 $ctext[] = $token;
326 $token = array_shift($tokens);
327 if (isset($items[$token])) {
328 $ctext[] = $token;
329 $item = $items[$token];
330 $matches[] = array(
331 'trace' => array_merge($context['trace'], $ctext),
332 'key' => $key,
26c2192d »
2011-03-23 QA: Removing trailing comma in arrays, lowercasing some keywords.
333 'item' => $item
e7f3c313 »
2009-10-12 going lithium
334 );
335 break;
336 } else {
337 array_unshift($tokens, $token);
338 }
339 } else {
340 $key = $token;
341 }
342
343 $matches[] = array(
344 'trace' => array_merge($context['trace'], $ctext),
345 'key' => $key,
26c2192d »
2011-03-23 QA: Removing trailing comma in arrays, lowercasing some keywords.
346 'item' => $item
e7f3c313 »
2009-10-12 going lithium
347 );
348 }
8de98e98 »
2010-08-24 Fixing coding standards violations. Closes ticket #143.
349 } elseif (
350 ($key === $token || (ctype_digit($token) && $key == $token) || $token === '.')
351 ) {
e7f3c313 »
2009-10-12 going lithium
352 $context['trace'][] = $key;
353 $matches[] = array(
354 'trace' => $context['trace'],
355 'key' => $key,
26c2192d »
2011-03-23 QA: Removing trailing comma in arrays, lowercasing some keywords.
356 'item' => $context['item']
e7f3c313 »
2009-10-12 going lithium
357 );
358 }
359 }
360 if ($conditions) {
361 foreach ($conditions as $condition) {
362 $filtered = array();
363 $length = count($matches);
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
364
e7f3c313 »
2009-10-12 going lithium
365 foreach ($matches as $i => $match) {
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
366 if (static::matches($match['item'], array($condition), $i + 1, $length)) {
e7f3c313 »
2009-10-12 going lithium
367 $filtered[] = $match;
368 }
369 }
370 $matches = $filtered;
371 }
372 }
373 $contexts = $matches;
374
375 if (empty($tokens)) {
376 break;
377 }
378 } while (1);
379
380 $r = array();
381
382 foreach ($matches as $match) {
383 if ((!$options['flatten'] || is_array($match['item'])) && !is_int($match['key'])) {
384 $r[] = array($match['key'] => $match['item']);
385 } else {
386 $r[] = $match['item'];
387 }
388 }
389 return $r;
390 }
391
392 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
393 * Collapses a multi-dimensional array into a single dimension, using a delimited array path
394 * for each array element's key, i.e. array(array('Foo' => array('Bar' => 'Far'))) becomes
395 * array('0.Foo.Bar' => 'Far').
396 *
397 * @param array $data array to flatten
398 * @param array $options Available options are:
399 * - `'separator'`: String to separate array keys in path (defaults to `'.'`).
400 * - `'path'`: Starting point (defaults to null).
401 * @return array
402 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
403 public static function flatten($data, array $options = array()) {
9084d697 »
2010-02-11 initial refactoring \util\Set
404 $defaults = array('separator' => '.', 'path' => null);
405 $options += $defaults;
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
406 $result = array();
9084d697 »
2010-02-11 initial refactoring \util\Set
407
408 if (!is_null($options['path'])) {
409 $options['path'] .= $options['separator'];
410 }
411 foreach ($data as $key => $val) {
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
412 if (!is_array($val)) {
9084d697 »
2010-02-11 initial refactoring \util\Set
413 $result[$options['path'] . $key] = $val;
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
414 continue;
9084d697 »
2010-02-11 initial refactoring \util\Set
415 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
416 $opts = array('separator' => $options['separator'], 'path' => $options['path'] . $key);
417 $result += (array) static::flatten($val, $opts);
9084d697 »
2010-02-11 initial refactoring \util\Set
418 }
419 return $result;
420 }
421
422 /**
a26fcc18 »
2011-05-01 Fixing issue with `\data\Model::create()` where default values for ne…
423 * Accepts a one-dimensional array where the keys are separated by a delimiter.
424 *
425 * @param array $data The one-dimensional array to expand.
426 * @param array $options The options used when expanding the array:
427 * - `'separator'` _string_: The delimiter to use when separating keys. Defaults
428 * to `'.'`.
429 * @return array Returns a multi-dimensional array expanded from a one dimensional dot-separated
430 * array.
431 */
432 public static function expand(array $data, array $options = array()) {
433 $defaults = array('separator' => '.');
434 $options += $defaults;
435 $result = array();
436
437 foreach ($data as $key => $val) {
438 if (strpos($key, $options['separator']) === false) {
439 $result[$key] = $val;
440 continue;
441 }
442 list($path, $key) = explode($options['separator'], $key, 2);
443 $path = is_numeric($path) ? intval($path) : $path;
444 $result[$path][$key] = $val;
445 }
446 foreach ($result as $key => $value) {
447 if (is_array($value)) {
448 $result[$key] = static::expand($value, $options);
449 }
450 }
451 return $result;
452 }
453
454 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
455 * Returns a series of values extracted from an array, formatted in a format string.
456 *
457 * @param array $data Source array from which to extract the data.
458 * @param string $format Format string into which values will be inserted using `sprintf()`.
459 * @param array $keys An array containing one or more `Set::extract()`-style key paths.
460 * @return array An array of strings extracted from `$keys` and formatted with `$format`.
461 * @link http://php.net/sprintf
462 */
463 public static function format($data, $format, $keys) {
464 $extracted = array();
465 $count = count($keys);
466
467 if (!$count) {
468 return;
469 }
470 for ($i = 0; $i < $count; $i++) {
471 $extracted[] = static::extract($data, $keys[$i]);
472 }
473 $out = array();
474 $data = $extracted;
475 $count = count($data[0]);
476
477 if (preg_match_all('/\{([0-9]+)\}/msi', $format, $keys2) && isset($keys2[1])) {
478 $keys = $keys2[1];
479 $format = preg_split('/\{([0-9]+)\}/msi', $format);
480 $count2 = count($format);
481
482 for ($j = 0; $j < $count; $j++) {
483 $formatted = '';
484 for ($i = 0; $i <= $count2; $i++) {
485 if (isset($format[$i])) {
486 $formatted .= $format[$i];
487 }
488 if (isset($keys[$i]) && isset($data[$keys[$i]][$j])) {
489 $formatted .= $data[$keys[$i]][$j];
490 }
491 }
492 $out[] = $formatted;
493 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
494 return $out;
495 }
496 $count2 = count($data);
497
498 for ($j = 0; $j < $count; $j++) {
499 $args = array();
500
501 for ($i = 0; $i < $count2; $i++) {
502 if (isset($data[$i][$j])) {
503 $args[] = $data[$i][$j];
9084d697 »
2010-02-11 initial refactoring \util\Set
504 }
505 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
506 $out[] = vsprintf($format, $args);
9084d697 »
2010-02-11 initial refactoring \util\Set
507 }
508 return $out;
509 }
510
511 /**
5a40337c »
2009-10-20 Updating Set dockblocks.
512 * Inserts `$data` into an array as defined by `$path`.
e7f3c313 »
2009-10-12 going lithium
513 *
5a40337c »
2009-10-20 Updating Set dockblocks.
514 * @param mixed $list Where to insert into.
515 * @param mixed $path A dot-delimited string.
516 * @param array $data Data to insert.
e7f3c313 »
2009-10-12 going lithium
517 * @return array
518 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
519 public static function insert($list, $path, $data = array()) {
e7f3c313 »
2009-10-12 going lithium
520 if (!is_array($path)) {
521 $path = explode('.', $path);
522 }
523 $_list =& $list;
524
525 foreach ($path as $i => $key) {
526 if (is_numeric($key) && intval($key) > 0 || $key === '0') {
527 $key = intval($key);
528 }
529 if ($i === count($path) - 1) {
530 $_list[$key] = $data;
531 } else {
532 if (!isset($_list[$key])) {
533 $_list[$key] = array();
534 }
535 $_list =& $_list[$key];
536 }
537 }
538 return $list;
539 }
540
541 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
542 * Checks to see if all the values in the array are numeric.
e7f3c313 »
2009-10-12 going lithium
543 *
9084d697 »
2010-02-11 initial refactoring \util\Set
544 * @param array $array The array to check. If null, the value of the current Set object.
545 * @return boolean `true` if values are numeric, `false` otherwise.
e7f3c313 »
2009-10-12 going lithium
546 */
9084d697 »
2010-02-11 initial refactoring \util\Set
547 public static function isNumeric($array = null) {
548 if (empty($array)) {
549 return null;
e7f3c313 »
2009-10-12 going lithium
550 }
9084d697 »
2010-02-11 initial refactoring \util\Set
551 if ($array === range(0, count($array) - 1)) {
552 return true;
e7f3c313 »
2009-10-12 going lithium
553 }
9084d697 »
2010-02-11 initial refactoring \util\Set
554 $numeric = true;
555 $keys = array_keys($array);
556 $count = count($keys);
e7f3c313 »
2009-10-12 going lithium
557
9084d697 »
2010-02-11 initial refactoring \util\Set
558 for ($i = 0; $i < $count; $i++) {
559 if (!is_numeric($array[$keys[$i]])) {
560 $numeric = false;
561 break;
e7f3c313 »
2009-10-12 going lithium
562 }
563 }
9084d697 »
2010-02-11 initial refactoring \util\Set
564 return $numeric;
e7f3c313 »
2009-10-12 going lithium
565 }
566
567 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
568 * This function can be used to see if a single item or a given XPath
569 * match certain conditions.
e7f3c313 »
2009-10-12 going lithium
570 *
9084d697 »
2010-02-11 initial refactoring \util\Set
571 * @param array $data An array of data to execute the match on.
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
572 * @param mixed $conditions An array of condition strings or an XPath expression.
9084d697 »
2010-02-11 initial refactoring \util\Set
573 * @param integer $i Optional: The 'nth'-number of the item being matched.
574 * @param integer $length
575 * @return boolean
e7f3c313 »
2009-10-12 going lithium
576 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
577 public static function matches($data = array(), $conditions, $i = null, $length = null) {
578 if (!$conditions) {
9084d697 »
2010-02-11 initial refactoring \util\Set
579 return true;
e7f3c313 »
2009-10-12 going lithium
580 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
581 if (is_string($conditions)) {
b9f08989 »
2011-01-30 QA: Fixing short type names.
582 return (boolean) static::extract($data, $conditions);
9084d697 »
2010-02-11 initial refactoring \util\Set
583 }
584 foreach ($conditions as $condition) {
585 if ($condition === ':last') {
586 if ($i != $length) {
587 return false;
588 }
589 continue;
590 } elseif ($condition === ':first') {
591 if ($i != 1) {
592 return false;
593 }
594 continue;
595 }
596 if (!preg_match('/(.+?)([><!]?[=]|[><])(.*)/', $condition, $match)) {
597 if (ctype_digit($condition)) {
598 if ($i != $condition) {
599 return false;
600 }
601 } elseif (preg_match_all('/(?:^[0-9]+|(?<=,)[0-9]+)/', $condition, $matches)) {
602 return in_array($i, $matches[0]);
603 } elseif (!isset($data[$condition])) {
604 return false;
605 }
606 continue;
607 }
608 list(,$key,$op,$expected) = $match;
e7f3c313 »
2009-10-12 going lithium
609
9084d697 »
2010-02-11 initial refactoring \util\Set
610 if (!isset($data[$key])) {
611 return false;
e7f3c313 »
2009-10-12 going lithium
612 }
9084d697 »
2010-02-11 initial refactoring \util\Set
613 $val = $data[$key];
e7f3c313 »
2009-10-12 going lithium
614
9084d697 »
2010-02-11 initial refactoring \util\Set
615 if ($op === '=' && $expected && $expected{0} === '/') {
616 return preg_match($expected, $val);
617 } elseif ($op === '=' && $val != $expected) {
618 return false;
619 } elseif ($op === '!=' && $val == $expected) {
620 return false;
621 } elseif ($op === '>' && $val <= $expected) {
622 return false;
623 } elseif ($op === '<' && $val >= $expected) {
624 return false;
625 } elseif ($op === '<=' && $val > $expected) {
626 return false;
627 } elseif ($op === '>=' && $val < $expected) {
628 return false;
e7f3c313 »
2009-10-12 going lithium
629 }
630 }
9084d697 »
2010-02-11 initial refactoring \util\Set
631 return true;
e7f3c313 »
2009-10-12 going lithium
632 }
633
634 /**
a9136eb3 »
2010-02-12 Updating `util\Set::merge()` to accept only two parameters.
635 * This method can be thought of as a hybrid between PHP's `array_merge()`
9084d697 »
2010-02-11 initial refactoring \util\Set
636 * and `array_merge_recursive()`. The difference to the two is that if an
637 * array key contains another array then the function behaves recursive
638 * (unlike `array_merge()`) but does not do if for keys containing strings
639 * (unlike `array_merge_recursive()`). Please note: This function will work
640 * with an unlimited amount of arguments and typecasts non-array parameters
641 * into arrays.
e7f3c313 »
2009-10-12 going lithium
642 *
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
643 * @param array $array1 The base array.
644 * @param array $array2 The array to be merged on top of the base array.
9084d697 »
2010-02-11 initial refactoring \util\Set
645 * @return array Merged array of all passed params.
e7f3c313 »
2009-10-12 going lithium
646 */
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
647 public static function merge(array $array1, array $array2) {
648 $args = array($array1, $array2);
9084d697 »
2010-02-11 initial refactoring \util\Set
649
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
650 if (!$array1 || !$array2) {
651 return $array1 ?: $array2;
a9136eb3 »
2010-02-12 Updating `util\Set::merge()` to accept only two parameters.
652 }
9084d697 »
2010-02-11 initial refactoring \util\Set
653 $result = (array) current($args);
e7f3c313 »
2009-10-12 going lithium
654
9084d697 »
2010-02-11 initial refactoring \util\Set
655 while (($arg = next($args)) !== false) {
656 foreach ((array) $arg as $key => $val) {
657 if (is_array($val) && isset($result[$key]) && is_array($result[$key])) {
658 $result[$key] = static::merge($result[$key], $val);
659 } elseif (is_int($key)) {
660 $result[] = $val;
661 } else {
662 $result[$key] = $val;
e7f3c313 »
2009-10-12 going lithium
663 }
664 }
665 }
9084d697 »
2010-02-11 initial refactoring \util\Set
666 return $result;
e7f3c313 »
2009-10-12 going lithium
667 }
668
669 /**
670 * Normalizes a string or array list.
671 *
5a40337c »
2009-10-20 Updating Set dockblocks.
672 * @param mixed $list List to normalize.
673 * @param boolean $assoc If `true`, `$list` will be converted to an associative array.
674 * @param string $sep If `$list` is a string, it will be split into an array with `$sep`.
675 * @param boolean $trim If `true`, separated strings will be trimmed.
e7f3c313 »
2009-10-12 going lithium
676 * @return array
677 */
678 public static function normalize($list, $assoc = true, $sep = ',', $trim = true) {
679 if (is_string($list)) {
680 $list = explode($sep, $list);
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
681 $list = ($trim) ? array_map('trim', $list) : $list;
682 return ($assoc) ? static::normalize($list) : $list;
683 }
684
685 if (!is_array($list)) {
686 return $list;
687 }
688
689 $keys = array_keys($list);
690 $count = count($keys);
691 $numeric = true;
692
693 if (!$assoc) {
694 for ($i = 0; $i < $count; $i++) {
695 if (!is_int($keys[$i])) {
696 $numeric = false;
697 break;
e7f3c313 »
2009-10-12 going lithium
698 }
699 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
700 }
e7f3c313 »
2009-10-12 going lithium
701
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
702 if (!$numeric || $assoc) {
703 $newList = array();
704 for ($i = 0; $i < $count; $i++) {
705 if (is_int($keys[$i]) && is_scalar($list[$keys[$i]])) {
706 $newList[$list[$keys[$i]]] = null;
707 } else {
708 $newList[$keys[$i]] = $list[$keys[$i]];
e7f3c313 »
2009-10-12 going lithium
709 }
710 }
5ce9dc83 »
2011-01-18 WARNING: API of `\util\Set` class has changed. Parameter order has ch…
711 $list = $newList;
e7f3c313 »
2009-10-12 going lithium
712 }
713 return $list;
714 }
715
716 /**
9084d697 »
2010-02-11 initial refactoring \util\Set
717 * Removes an element from an array as defined by `$path`.
e7f3c313 »
2009-10-12 going lithium
718 *
9084d697 »
2010-02-11 initial refactoring \util\Set
719 * @param mixed $list From where to remove.
720 * @param mixed $path A dot-delimited string.
721 * @return array Array with `$path` removed from its value.
e7f3c313 »
2009-10-12 going lithium
722 */
9084d697 »
2010-02-11 initial refactoring \util\Set
723 public static function remove($list, $path = null) {
724 if (empty($path)) {
725 return $list;
e7f3c313 »
2009-10-12 going lithium
726 }
9084d697 »
2010-02-11 initial refactoring \util\Set
727 if (!is_array($path)) {
728 $path = explode('.', $path);
e7f3c313 »
2009-10-12 going lithium
729 }
9084d697 »
2010-02-11 initial refactoring \util\Set
730 $_list =& $list;
e7f3c313 »
2009-10-12 going lithium
731
9084d697 »
2010-02-11 initial refactoring \util\Set
732 foreach ($path as $i => $key) {
733 if (is_numeric($key) && intval($key) > 0 || $key === '0') {
734 $key = intval($key);
735 }
736 if ($i === count($path) - 1) {
737 unset($_list[$key]);
738 } else {
739 if (!isset($_list[$key])) {
740 return $list;
741 }
742 $_list =& $_list[$key];
743 }
e7f3c313 »
2009-10-12 going lithium
744 }
9084d697 »
2010-02-11 initial refactoring \util\Set
745 return $list;
746 }
e7f3c313 »
2009-10-12 going lithium
747
9084d697 »
2010-02-11 initial refactoring \util\Set
748 /**
749 * Sorts an array by any value, determined by a `Set`-compatible path.
750 *
751 * @param array $data
752 * @param string $path A `Set`-compatible path to the array value.
753 * @param string $dir Either `'asc'` (the default) or `'desc'`.
754 * @return array
755 */
756 public static function sort($data, $path, $dir = 'asc') {
757 $flatten = function($flatten, $results, $key = null) {
758 $stack = array();
759 foreach ((array) $results as $k => $r) {
760 $id = $k;
761 if (!is_null($key)) {
762 $id = $key;
763 }
764 if (is_array($r)) {
765 $stack = array_merge($stack, $flatten($flatten, $r, $id));
766 } else {
767 $stack[] = array('id' => $id, 'value' => $r);
e7f3c313 »
2009-10-12 going lithium
768 }
769 }
9084d697 »
2010-02-11 initial refactoring \util\Set
770 return $stack;
771 };
772 $extract = static::extract($data, $path);
773 $result = $flatten($flatten, $extract);
774
2ff7d356 »
2010-09-06 Removing unused code from `\util\Set`, adding code coverage.
775 $keys = static::extract($result, '/id');
776 $values = static::extract($result, '/value');
777
9084d697 »
2010-02-11 initial refactoring \util\Set
778 $dir = ($dir === 'desc') ? SORT_DESC : SORT_ASC;
779 array_multisort($values, $dir, $keys, $dir);
780 $sorted = array();
781 $keys = array_unique($keys);
782
783 foreach ($keys as $k) {
784 $sorted[] = $data[$k];
e7f3c313 »
2009-10-12 going lithium
785 }
9084d697 »
2010-02-11 initial refactoring \util\Set
786 return $sorted;
e7f3c313 »
2009-10-12 going lithium
787 }
788 }
789
790 ?>
Something went wrong with that request. Please try again.