TYPO3  7.6
core/Classes/Utility/ArrayUtility.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Utility;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
21 {
55  public static function filterByValueRecursive($needle = '', array $haystack = array())
56  {
57  $resultArray = array();
58  // Define a lambda function to be applied to all members of this array dimension
59  // Call recursive if current value is of type array
60  // Write to $resultArray (by reference!) if types and value match
61  $callback = function (&$value, $key) use ($needle, &$resultArray) {
62  if ($value === $needle) {
63  ($resultArray[$key] = $value);
64  } elseif (is_array($value)) {
65  ($subArrayMatches = static::filterByValueRecursive($needle, $value));
66  if (!empty($subArrayMatches)) {
67  ($resultArray[$key] = $subArrayMatches);
68  }
69  }
70  };
71  // array_walk() is not affected by the internal pointers, no need to reset
72  array_walk($haystack, $callback);
73  // Pointers to result array are reset internally
74  return $resultArray;
75  }
76 
95  public static function isValidPath(array $array, $path, $delimiter = '/')
96  {
97  $isValid = true;
98  try {
99  // Use late static binding to enable mocking of this call in unit tests
100  static::getValueByPath($array, $path, $delimiter);
101  } catch (\RuntimeException $e) {
102  $isValid = false;
103  }
104  return $isValid;
105  }
106 
131  public static function getValueByPath(array $array, $path, $delimiter = '/')
132  {
133  if (empty($path)) {
134  throw new \RuntimeException('Path must not be empty', 1341397767);
135  }
136  // Extract parts of the path
137  $path = str_getcsv($path, $delimiter);
138  // Loop through each part and extract its value
139  $value = $array;
140  foreach ($path as $segment) {
141  if (array_key_exists($segment, $value)) {
142  // Replace current value with child
143  $value = $value[$segment];
144  } else {
145  // Fail if key does not exist
146  throw new \RuntimeException('Path does not exist in array', 1341397869);
147  }
148  }
149  return $value;
150  }
151 
178  public static function setValueByPath(array $array, $path, $value, $delimiter = '/')
179  {
180  if (empty($path)) {
181  throw new \RuntimeException('Path must not be empty', 1341406194);
182  }
183  if (!is_string($path)) {
184  throw new \RuntimeException('Path must be a string', 1341406402);
185  }
186  // Extract parts of the path
187  $path = str_getcsv($path, $delimiter);
188  // Point to the root of the array
189  $pointer = &$array;
190  // Find path in given array
191  foreach ($path as $segment) {
192  // Fail if the part is empty
193  if (empty($segment)) {
194  throw new \RuntimeException('Invalid path segment specified', 1341406846);
195  }
196  // Create cell if it doesn't exist
197  if (!array_key_exists($segment, $pointer)) {
198  $pointer[$segment] = array();
199  }
200  // Set pointer to new cell
201  $pointer = &$pointer[$segment];
202  }
203  // Set value of target cell
204  $pointer = $value;
205  return $array;
206  }
207 
217  public static function removeByPath(array $array, $path, $delimiter = '/')
218  {
219  if (empty($path)) {
220  throw new \RuntimeException('Path must not be empty', 1371757718);
221  }
222  if (!is_string($path)) {
223  throw new \RuntimeException('Path must be a string', 1371757719);
224  }
225  // Extract parts of the path
226  $path = str_getcsv($path, $delimiter);
227  $pathDepth = count($path);
228  $currentDepth = 0;
229  $pointer = &$array;
230  // Find path in given array
231  foreach ($path as $segment) {
232  $currentDepth++;
233  // Fail if the part is empty
234  if (empty($segment)) {
235  throw new \RuntimeException('Invalid path segment specified', 1371757720);
236  }
237  if (!array_key_exists($segment, $pointer)) {
238  throw new \RuntimeException('Path segment ' . $segment . ' does not exist in array', 1371758436);
239  }
240  if ($currentDepth === $pathDepth) {
241  unset($pointer[$segment]);
242  } else {
243  $pointer = &$pointer[$segment];
244  }
245  }
246  return $array;
247  }
248 
255  public static function sortByKeyRecursive(array $array)
256  {
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 
275  public static function sortArraysByKey(array $arrays, $key, $ascending = true)
276  {
277  if (empty($arrays)) {
278  return $arrays;
279  }
280  $sortResult = uasort($arrays, function (array $a, array $b) use ($key, $ascending) {
281  if (!isset($a[$key]) || !isset($b[$key])) {
282  throw new \RuntimeException('The specified sorting key "' . $key . '" is not available in the given array.', 1373727309);
283  }
284  return ($ascending) ? strcasecmp($a[$key], $b[$key]) : strcasecmp($b[$key], $a[$key]);
285  });
286  if (!$sortResult) {
287  throw new \RuntimeException('The function uasort() failed for unknown reasons.', 1373727329);
288  }
289  return $arrays;
290  }
291 
303  public static function arrayExport(array $array = array(), $level = 0)
304  {
305  $lines = '[' . LF;
306  $level++;
307  $writeKeyIndex = false;
308  $expectedKeyIndex = 0;
309  foreach ($array as $key => $value) {
310  if ($key === $expectedKeyIndex) {
311  $expectedKeyIndex++;
312  } else {
313  // Found a non integer or non consecutive key, so we can break here
314  $writeKeyIndex = true;
315  break;
316  }
317  }
318  foreach ($array as $key => $value) {
319  // Indention
320  $lines .= str_repeat(' ', $level);
321  if ($writeKeyIndex) {
322  // Numeric / string keys
323  $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
324  }
325  if (is_array($value)) {
326  if (!empty($value)) {
327  $lines .= self::arrayExport($value, $level);
328  } else {
329  $lines .= '[],' . LF;
330  }
331  } elseif (is_int($value) || is_float($value)) {
332  $lines .= $value . ',' . LF;
333  } elseif (is_null($value)) {
334  $lines .= 'null' . ',' . LF;
335  } elseif (is_bool($value)) {
336  $lines .= $value ? 'true' : 'false';
337  $lines .= ',' . LF;
338  } elseif (is_string($value)) {
339  // Quote \ to \\
340  $stringContent = str_replace('\\', '\\\\', $value);
341  // Quote ' to \'
342  $stringContent = str_replace('\'', '\\\'', $stringContent);
343  $lines .= '\'' . $stringContent . '\'' . ',' . LF;
344  } else {
345  throw new \RuntimeException('Objects are not supported', 1342294987);
346  }
347  }
348  $lines .= str_repeat(' ', ($level - 1)) . ']' . ($level - 1 == 0 ? '' : ',' . LF);
349  return $lines;
350  }
351 
385  public static function flatten(array $array, $prefix = '')
386  {
387  $flatArray = array();
388  foreach ($array as $key => $value) {
389  // Ensure there is no trailling dot:
390  $key = rtrim($key, '.');
391  if (!is_array($value)) {
392  $flatArray[$prefix . $key] = $value;
393  } else {
394  $flatArray = array_merge($flatArray, self::flatten($value, $prefix . $key . '.'));
395  }
396  }
397  return $flatArray;
398  }
399 
435  public static function intersectRecursive(array $source, array $mask = array())
436  {
437  $intersection = array();
438  foreach ($source as $key => $_) {
439  if (!array_key_exists($key, $mask)) {
440  continue;
441  }
442  if (is_array($source[$key]) && is_array($mask[$key])) {
443  $value = self::intersectRecursive($source[$key], $mask[$key]);
444  if (!empty($value)) {
445  $intersection[$key] = $value;
446  }
447  } else {
448  $intersection[$key] = $source[$key];
449  }
450  }
451  return $intersection;
452  }
453 
479  public static function renumberKeysToAvoidLeapsIfKeysAreAllNumeric(array $array = array(), $level = 0)
480  {
481  $level++;
482  $allKeysAreNumeric = true;
483  foreach ($array as $key => $_) {
484  if (is_numeric($key) === false) {
485  $allKeysAreNumeric = false;
486  break;
487  }
488  }
489  $renumberedArray = $array;
490  if ($allKeysAreNumeric === true) {
491  $renumberedArray = array_values($array);
492  }
493  foreach ($renumberedArray as $key => $value) {
494  if (is_array($value)) {
495  $renumberedArray[$key] = self::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($value, $level);
496  }
497  }
498  return $renumberedArray;
499  }
500 
501 
522  public static function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
523  {
524  foreach ($overrule as $key => $_) {
525  if ($enableUnsetFeature && $overrule[$key] === '__UNSET') {
526  unset($original[$key]);
527  continue;
528  }
529  if (isset($original[$key]) && is_array($original[$key])) {
530  if (is_array($overrule[$key])) {
531  self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature);
532  }
533  } elseif (
534  ($addKeys || isset($original[$key])) &&
535  ($includeEmptyValues || $overrule[$key])
536  ) {
537  $original[$key] = $overrule[$key];
538  }
539  }
540  // This line is kept for backward compatibility reasons.
541  reset($original);
542  }
543 
566  public static function inArray(array $in_array, $item)
567  {
568  foreach ($in_array as $val) {
569  if (!is_array($val) && (string)$val === (string)$item) {
570  return true;
571  }
572  }
573  return false;
574  }
575 
583  public static function removeArrayEntryByValue(array $array, $cmpValue)
584  {
585  foreach ($array as $k => $v) {
586  if (is_array($v)) {
587  $array[$k] = self::removeArrayEntryByValue($v, $cmpValue);
588  } elseif ((string)$v === (string)$cmpValue) {
589  unset($array[$k]);
590  }
591  }
592  return $array;
593  }
594 
618  public static function keepItemsInArray(array $array, $keepItems, $getValueFunc = null)
619  {
620  if ($array) {
621  // Convert strings to arrays:
622  if (is_string($keepItems)) {
623  $keepItems = GeneralUtility::trimExplode(',', $keepItems);
624  }
625  // Check if valueFunc can be executed:
626  if (!is_callable($getValueFunc)) {
627  $getValueFunc = null;
628  }
629  // Do the filtering:
630  if (is_array($keepItems) && !empty($keepItems)) {
631  foreach ($array as $key => $value) {
632  // Get the value to compare by using the callback function:
633  $keepValue = isset($getValueFunc) ? call_user_func($getValueFunc, $value) : $value;
634  if (!in_array($keepValue, $keepItems)) {
635  unset($array[$key]);
636  }
637  }
638  }
639  }
640  return $array;
641  }
642 
649  public static function remapArrayKeys(array &$array, array $mappingTable)
650  {
651  foreach ($mappingTable as $old => $new) {
652  if ($new && isset($array[$old])) {
653  $array[$new] = $array[$old];
654  unset($array[$old]);
655  }
656  }
657  }
658 
667  public static function arrayDiffAssocRecursive(array $array1, array $array2)
668  {
669  $differenceArray = array();
670  foreach ($array1 as $key => $value) {
671  if (!array_key_exists($key, $array2)) {
672  $differenceArray[$key] = $value;
673  } elseif (is_array($value)) {
674  if (is_array($array2[$key])) {
675  $differenceArray[$key] = self::arrayDiffAssocRecursive($value, $array2[$key]);
676  }
677  }
678  }
679  return $differenceArray;
680  }
681 
688  public static function naturalKeySortRecursive(array &$array)
689  {
690  uksort($array, 'strnatcasecmp');
691  foreach ($array as $key => &$value) {
692  if (is_array($value)) {
693  self::naturalKeySortRecursive($value);
694  }
695  }
696 
697  return true;
698  }
699 }