Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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