[FEATURE] arrayExport() should recognize int keys
[Packages/TYPO3.CMS.git] / t3lib / utility / class.t3lib_utility_array.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2011 Susanne Moog <typo3@susanne-moog.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * Class with helper functions for array handling
30 *
31 * @author Susanne Moog <typo3@susanne-moog.de>
32 * @package TYPO3
33 * @subpackage t3lib
34 */
35 class t3lib_utility_Array {
36
37 /**
38 * Reduce an array by a search value and keep the array structure.
39 *
40 * Comparison is type strict:
41 * - For a given needle of type string, integer, array or boolean,
42 * value and value type must match to occur in result array
43 * - For a given object, a object within the array must be a reference to
44 * the same object to match (not just different instance of same class)
45 *
46 * Example:
47 * - Needle: 'findMe'
48 * - Given array:
49 * array(
50 * 'foo' => 'noMatch',
51 * 'bar' => 'findMe',
52 * 'foobar => array(
53 * 'foo' => 'findMe',
54 * ),
55 * );
56 * - Result:
57 * array(
58 * 'bar' => 'findMe',
59 * 'foobar' => array(
60 * 'foo' => findMe',
61 * ),
62 * );
63 *
64 * See the unit tests for more examples and expected behaviour
65 *
66 * @param mixed $needle The value to search for
67 * @param array $haystack The array in which to search
68 * @return array $haystack array reduced matching $needle values
69 */
70 public static function filterByValueRecursive($needle = '', array $haystack = array()) {
71 $resultArray = array();
72
73 // Define a lambda function to be applied to all members of this array dimension
74 // Call recursive if current value is of type array
75 // Write to $resultArray (by reference!) if types and value match
76 $callback = function(&$value, $key) use ($needle, &$resultArray) {
77 if ($value === $needle) {
78 $resultArray[$key] = $value;
79 } elseif (is_array($value)) {
80 // self does not work in lambda functions, use t3lib_utility_Array for recursion
81 $subArrayMatches = t3lib_utility_Array::filterByValueRecursive($needle, $value);
82 if (count($subArrayMatches) > 0) {
83 $resultArray[$key] = $subArrayMatches;
84 }
85 }
86 };
87
88 // array_walk() is not affected by the internal pointers, no need to reset
89 array_walk($haystack, $callback);
90
91 // Pointers to result array are reset internally
92 return $resultArray;
93 }
94
95 /**
96 * Checks if a given path exists in array
97 *
98 * array:
99 * array(
100 * 'foo' => array(
101 * 'bar' = 'test',
102 * )
103 * );
104 * path: 'foo/bar'
105 * return: TRUE
106 *
107 * @param array $array Given array
108 * @param string $path Path to test, 'foo/bar/foobar'
109 * @param string $delimiter Delimeter for path, default /
110 * @return boolean TRUE if path exists in array
111 */
112 public static function isValidPath(array $array, $path, $delimiter = '/') {
113 $isValid = TRUE;
114 try {
115 // Use late static binding to enable mocking of this call in unit tests
116 static::getValueByPath(
117 $array,
118 $path,
119 $delimiter
120 );
121 } catch (RuntimeException $e) {
122 $isValid = FALSE;
123 }
124 return $isValid;
125 }
126
127 /**
128 * Returns a value by given path
129 *
130 * Simple example
131 * Input array:
132 * array(
133 * 'foo' => array(
134 * 'bar' => array(
135 * 'baz' => 42
136 * )
137 * )
138 * );
139 * Path to get: foo/bar/baz
140 * Will return: 42
141 *
142 * If a path segments contains a delimeter character, the path segment
143 * must be enclodsed by " (doubletick), see unit tests for details
144 *
145 * @param array $array Input array
146 * @param string $path Path within the array
147 * @param string $delimiter Defined path delimeter, default /
148 * @return mixed
149 * @throws RuntimeException
150 */
151 public static function getValueByPath(array $array, $path, $delimiter = '/') {
152 if (empty($path)) {
153 throw new RuntimeException(
154 'Path must not be empty',
155 1341397767
156 );
157 }
158
159 // Extract parts of the path
160 $path = str_getcsv($path, $delimiter);
161
162 // Loop through each part and extract its value
163 $value = $array;
164 foreach ($path as $segment) {
165 if (array_key_exists($segment, $value)) {
166 // Replace current value with child
167 $value = $value[$segment];
168 } else {
169 // Fail if key does not exist
170 throw new RuntimeException(
171 'Path does not exist in array',
172 1341397869
173 );
174 }
175 }
176
177 return $value;
178 }
179
180 /**
181 * Modifies or sets a new value in an array by given path
182 *
183 * Input array:
184 * array(
185 * 'foo' => array(
186 * 'bar' => 42,
187 * ),
188 * );
189 * Path to get: foo/bar
190 * Value to set: 23
191 * Will return:
192 * array(
193 * 'foo' => array(
194 * 'bar' => 23,
195 * ),
196 * );
197 *
198 * @param array $array Input array to manipulate
199 * @param string $path Path in array to search for
200 * @param mixed $value Value to set at path location in array
201 * @param string $delimiter Path delimeter
202 * @return array Modified array
203 * @throws RuntimeException
204 */
205 public static function setValueByPath(array $array, $path, $value, $delimiter = '/') {
206 if (empty($path)) {
207 throw new RuntimeException(
208 'Path must not be empty',
209 1341406194
210 );
211 }
212 if (!is_string($path)) {
213 throw new RuntimeException(
214 'Path must be a string',
215 1341406402
216 );
217 }
218
219 // Extract parts of the path
220 $path = str_getcsv($path, $delimiter);
221
222 // Point to the root of the array
223 $pointer = &$array;
224
225 // Find path in given array
226 foreach ($path as $segment) {
227 // Fail if the part is empty
228 if (empty($segment)) {
229 throw new RuntimeException(
230 'Invalid path specified: ' . $path,
231 1341406846
232 );
233 }
234
235 // Create cell if it doesn't exist
236 if (!array_key_exists($segment, $pointer)) {
237 $pointer[$segment] = array();
238 }
239
240 // Set pointer to new cell
241 $pointer = &$pointer[$segment];
242 }
243
244 // Set value of target cell
245 $pointer = $value;
246
247 return $array;
248 }
249
250 /**
251 * Sorts an array recursively by key
252 *
253 * @param $array Array to sort recursively by key
254 * @return array Sorted array
255 */
256 public static function sortByKeyRecursive(array $array) {
257 ksort($array);
258 foreach ($array as $key => $value) {
259 if (is_array($value) && !empty($value)) {
260 $array[$key] = self::sortByKeyRecursive($value);
261 }
262 }
263 return $array;
264 }
265
266 /**
267 * Exports an array as string.
268 * Similar to var_export(), but representation follows the TYPO3 core CGL.
269 *
270 * @param array $array Array to export
271 * @param integer $level Internal level used for recursion, do *not* set from outside!
272 * @return string String representation of array
273 */
274 public static function arrayExport(array $array = array(), $level = 0) {
275 $lines = 'array(' . LF;
276 $level ++;
277
278 $writeKeyIndex = FALSE;
279 $expectedKeyIndex = 0;
280 foreach ($array as $key => $value) {
281 if ($key === $expectedKeyIndex) {
282 $expectedKeyIndex ++;
283 } else {
284 // Found a non integer or non consecutive key, so we can break here
285 $writeKeyIndex = TRUE;
286 break;
287 }
288 }
289
290 foreach ($array as $key => $value) {
291 // Indention
292 $lines .= str_repeat(TAB, $level);
293
294 if ($writeKeyIndex) {
295 // Numeric / string keys
296 $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
297 }
298
299 if (is_array($value)) {
300 if (count($value) > 0) {
301 $lines .= self::arrayExport($value, $level);
302 } else {
303 $lines .= 'array(),' . LF;
304 }
305 } elseif (is_int($value)) {
306 $lines .= $value . ',' . LF;
307 } elseif (is_null($value)) {
308 $lines .= 'NULL' . ',' . LF;
309 } elseif (is_bool($value)) {
310 $lines .= $value ? 'TRUE' : 'FALSE';
311 $lines .= ',' . LF;
312 } elseif (is_string($value)) {
313 // Quote \ to \\
314 $stringContent = str_replace('\\', '\\\\', $value);
315 // Quote ' to \'
316 $stringContent = str_replace('\'', '\\\'', $stringContent);
317 $lines .= '\'' . $stringContent . '\'' . ',' . LF;
318 } else {
319 throw new RuntimeException(
320 'Objects are not supported',
321 1342294986
322 );
323 }
324 }
325 $lines .= str_repeat(TAB, $level - 1) . ')' . ($level - 1 == 0 ? '' : ',' . LF);
326 return $lines;
327 }
328 }
329
330 ?>