TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
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 {
31  public static function assertAllArrayKeysAreValid(array $arrayToTest, array $allowedArrayKeys)
32  {
33  $notAllowedArrayKeys = array_keys(array_diff_key($arrayToTest, array_flip($allowedArrayKeys)));
34  if (count($notAllowedArrayKeys) !== 0) {
35  throw new \InvalidArgumentException(
36  sprintf(
37  'The options "%s" were not allowed (allowed were: "%s")',
38  implode(', ', $notAllowedArrayKeys),
39  implode(', ', $allowedArrayKeys)
40  ), 1325697085
41  );
42  }
43  }
44 
51  public static function convertBooleanStringsToBooleanRecursive(array $array): array
52  {
53  $result = $array;
54  foreach ($result as $key => $value) {
55  if (is_array($value)) {
56  $result[$key] = self::convertBooleanStringsToBooleanRecursive($value);
57  } else {
58  if ($value === 'true') {
59  $result[$key] = true;
60  } elseif ($value === 'false') {
61  $result[$key] = false;
62  }
63  }
64  }
65  return $result;
66  }
67 
101  public static function filterByValueRecursive($needle = '', array $haystack = [])
102  {
103  $resultArray = [];
104  // Define a lambda function to be applied to all members of this array dimension
105  // Call recursive if current value is of type array
106  // Write to $resultArray (by reference!) if types and value match
107  $callback = function (&$value, $key) use ($needle, &$resultArray) {
108  if ($value === $needle) {
109  ($resultArray[$key] = $value);
110  } elseif (is_array($value)) {
111  ($subArrayMatches = static::filterByValueRecursive($needle, $value));
112  if (!empty($subArrayMatches)) {
113  ($resultArray[$key] = $subArrayMatches);
114  }
115  }
116  };
117  // array_walk() is not affected by the internal pointers, no need to reset
118  array_walk($haystack, $callback);
119  // Pointers to result array are reset internally
120  return $resultArray;
121  }
122 
141  public static function isValidPath(array $array, $path, $delimiter = '/')
142  {
143  $isValid = true;
144  try {
145  // Use late static binding to enable mocking of this call in unit tests
146  static::getValueByPath($array, $path, $delimiter);
147  } catch (\RuntimeException $e) {
148  $isValid = false;
149  }
150  return $isValid;
151  }
152 
178  public static function getValueByPath(array $array, $path, $delimiter = '/')
179  {
180  // Extract parts of the path
181  if (is_string($path)) {
182  if ($path === '') {
183  throw new \RuntimeException('Path must not be empty', 1341397767);
184  }
185  $path = str_getcsv($path, $delimiter);
186  } elseif (!is_array($path)) {
187  throw new \InvalidArgumentException('getValueByPath() expects $path to be string or array, "' . gettype($path) . '" given.', 1476557628);
188  }
189  // Loop through each part and extract its value
190  $value = $array;
191  foreach ($path as $segment) {
192  if (array_key_exists($segment, $value)) {
193  // Replace current value with child
194  $value = $value[$segment];
195  } else {
196  // Fail if key does not exist
197  throw new \RuntimeException('Path does not exist in array', 1341397869);
198  }
199  }
200  return $value;
201  }
202 
210  public static function reIndexNumericArrayKeysRecursive(array $array): array
211  {
212  if (count(array_filter(array_keys($array), 'is_string')) === 0) {
213  $array = array_values($array);
214  }
215  foreach ($array as $key => $value) {
216  if (is_array($value) && !empty($value)) {
217  $array[$key] = self::reIndexNumericArrayKeysRecursive($value);
218  }
219  }
220  return $array;
221  }
222 
229  public static function removeNullValuesRecursive(array $array): array
230  {
231  $result = $array;
232  foreach ($result as $key => $value) {
233  if (is_array($value)) {
234  $result[$key] = self::removeNullValuesRecursive($value);
235  } elseif ($value === null) {
236  unset($result[$key]);
237  }
238  }
239  return $result;
240  }
241 
268  public static function setValueByPath(array $array, $path, $value, $delimiter = '/')
269  {
270  if (is_string($path)) {
271  if ($path === '') {
272  throw new \RuntimeException('Path must not be empty', 1341406194);
273  }
274  // Extract parts of the path
275  $path = str_getcsv($path, $delimiter);
276  } elseif (!is_array($path) && !$path instanceof \ArrayAccess) {
277  throw new \InvalidArgumentException('setValueByPath() expects $path to be string, array or an object implementing \\ArrayAccess, "' . (is_object($path) ? get_class($path) : gettype($path)) . '" given.', 1478781081);
278  }
279  // Point to the root of the array
280  $pointer = &$array;
281  // Find path in given array
282  foreach ($path as $segment) {
283  // Fail if the part is empty
284  if ($segment === '') {
285  throw new \RuntimeException('Invalid path segment specified', 1341406846);
286  }
287  // Create cell if it doesn't exist
288  if (!array_key_exists($segment, $pointer)) {
289  $pointer[$segment] = [];
290  }
291  // Set pointer to new cell
292  $pointer = &$pointer[$segment];
293  }
294  // Set value of target cell
295  $pointer = $value;
296  return $array;
297  }
298 
308  public static function removeByPath(array $array, $path, $delimiter = '/')
309  {
310  if (!is_string($path)) {
311  throw new \RuntimeException('Path must be a string', 1371757719);
312  }
313  if ($path === '') {
314  throw new \RuntimeException('Path must not be empty', 1371757718);
315  }
316  // Extract parts of the path
317  $path = str_getcsv($path, $delimiter);
318  $pathDepth = count($path);
319  $currentDepth = 0;
320  $pointer = &$array;
321  // Find path in given array
322  foreach ($path as $segment) {
323  $currentDepth++;
324  // Fail if the part is empty
325  if ($segment === '') {
326  throw new \RuntimeException('Invalid path segment specified', 1371757720);
327  }
328  if (!array_key_exists($segment, $pointer)) {
329  throw new \RuntimeException('Path segment ' . $segment . ' does not exist in array', 1371758436);
330  }
331  if ($currentDepth === $pathDepth) {
332  unset($pointer[$segment]);
333  } else {
334  $pointer = &$pointer[$segment];
335  }
336  }
337  return $array;
338  }
339 
346  public static function sortByKeyRecursive(array $array)
347  {
348  ksort($array);
349  foreach ($array as $key => $value) {
350  if (is_array($value) && !empty($value)) {
351  $array[$key] = self::sortByKeyRecursive($value);
352  }
353  }
354  return $array;
355  }
356 
366  public static function sortArraysByKey(array $arrays, $key, $ascending = true)
367  {
368  if (empty($arrays)) {
369  return $arrays;
370  }
371  $sortResult = uasort($arrays, function (array $a, array $b) use ($key, $ascending) {
372  if (!isset($a[$key]) || !isset($b[$key])) {
373  throw new \RuntimeException('The specified sorting key "' . $key . '" is not available in the given array.', 1373727309);
374  }
375  return $ascending ? strcasecmp($a[$key], $b[$key]) : strcasecmp($b[$key], $a[$key]);
376  });
377  if (!$sortResult) {
378  throw new \RuntimeException('The function uasort() failed for unknown reasons.', 1373727329);
379  }
380  return $arrays;
381  }
382 
394  public static function arrayExport(array $array = [], $level = 0)
395  {
396  $lines = '[' . LF;
397  $level++;
398  $writeKeyIndex = false;
399  $expectedKeyIndex = 0;
400  foreach ($array as $key => $value) {
401  if ($key === $expectedKeyIndex) {
402  $expectedKeyIndex++;
403  } else {
404  // Found a non integer or non consecutive key, so we can break here
405  $writeKeyIndex = true;
406  break;
407  }
408  }
409  foreach ($array as $key => $value) {
410  // Indention
411  $lines .= str_repeat(' ', $level);
412  if ($writeKeyIndex) {
413  // Numeric / string keys
414  $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
415  }
416  if (is_array($value)) {
417  if (!empty($value)) {
418  $lines .= self::arrayExport($value, $level);
419  } else {
420  $lines .= '[],' . LF;
421  }
422  } elseif (is_int($value) || is_float($value)) {
423  $lines .= $value . ',' . LF;
424  } elseif (is_null($value)) {
425  $lines .= 'null' . ',' . LF;
426  } elseif (is_bool($value)) {
427  $lines .= $value ? 'true' : 'false';
428  $lines .= ',' . LF;
429  } elseif (is_string($value)) {
430  // Quote \ to \\
431  $stringContent = str_replace('\\', '\\\\', $value);
432  // Quote ' to \'
433  $stringContent = str_replace('\'', '\\\'', $stringContent);
434  $lines .= '\'' . $stringContent . '\'' . ',' . LF;
435  } else {
436  throw new \RuntimeException('Objects are not supported', 1342294987);
437  }
438  }
439  $lines .= str_repeat(' ', ($level - 1)) . ']' . ($level - 1 == 0 ? '' : ',' . LF);
440  return $lines;
441  }
442 
476  public static function flatten(array $array, $prefix = '')
477  {
478  $flatArray = [];
479  foreach ($array as $key => $value) {
480  // Ensure there is no trailing dot:
481  $key = rtrim($key, '.');
482  if (!is_array($value)) {
483  $flatArray[$prefix . $key] = $value;
484  } else {
485  $flatArray = array_merge($flatArray, self::flatten($value, $prefix . $key . '.'));
486  }
487  }
488  return $flatArray;
489  }
490 
526  public static function intersectRecursive(array $source, array $mask = [])
527  {
528  $intersection = [];
529  foreach ($source as $key => $_) {
530  if (!array_key_exists($key, $mask)) {
531  continue;
532  }
533  if (is_array($source[$key]) && is_array($mask[$key])) {
534  $value = self::intersectRecursive($source[$key], $mask[$key]);
535  if (!empty($value)) {
536  $intersection[$key] = $value;
537  }
538  } else {
539  $intersection[$key] = $source[$key];
540  }
541  }
542  return $intersection;
543  }
544 
570  public static function renumberKeysToAvoidLeapsIfKeysAreAllNumeric(array $array = [], $level = 0)
571  {
572  $level++;
573  $allKeysAreNumeric = true;
574  foreach ($array as $key => $_) {
575  if (is_numeric($key) === false) {
576  $allKeysAreNumeric = false;
577  break;
578  }
579  }
580  $renumberedArray = $array;
581  if ($allKeysAreNumeric === true) {
582  $renumberedArray = array_values($array);
583  }
584  foreach ($renumberedArray as $key => $value) {
585  if (is_array($value)) {
586  $renumberedArray[$key] = self::renumberKeysToAvoidLeapsIfKeysAreAllNumeric($value, $level);
587  }
588  }
589  return $renumberedArray;
590  }
591 
612  public static function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
613  {
614  foreach ($overrule as $key => $_) {
615  if ($enableUnsetFeature && $overrule[$key] === '__UNSET') {
616  unset($original[$key]);
617  continue;
618  }
619  if (isset($original[$key]) && is_array($original[$key])) {
620  if (is_array($overrule[$key])) {
621  self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature);
622  }
623  } elseif (
624  ($addKeys || isset($original[$key])) &&
625  ($includeEmptyValues || $overrule[$key])
626  ) {
627  $original[$key] = $overrule[$key];
628  }
629  }
630  // This line is kept for backward compatibility reasons.
631  reset($original);
632  }
633 
656  public static function inArray(array $in_array, $item)
657  {
658  foreach ($in_array as $val) {
659  if (!is_array($val) && (string)$val === (string)$item) {
660  return true;
661  }
662  }
663  return false;
664  }
665 
673  public static function removeArrayEntryByValue(array $array, $cmpValue)
674  {
675  foreach ($array as $k => $v) {
676  if (is_array($v)) {
677  $array[$k] = self::removeArrayEntryByValue($v, $cmpValue);
678  } elseif ((string)$v === (string)$cmpValue) {
679  unset($array[$k]);
680  }
681  }
682  return $array;
683  }
684 
708  public static function keepItemsInArray(array $array, $keepItems, $getValueFunc = null)
709  {
710  if ($array) {
711  // Convert strings to arrays:
712  if (is_string($keepItems)) {
713  $keepItems = GeneralUtility::trimExplode(',', $keepItems);
714  }
715  // Check if valueFunc can be executed:
716  if (!is_callable($getValueFunc)) {
717  $getValueFunc = null;
718  }
719  // Do the filtering:
720  if (is_array($keepItems) && !empty($keepItems)) {
721  foreach ($array as $key => $value) {
722  // Get the value to compare by using the callback function:
723  $keepValue = isset($getValueFunc) ? call_user_func($getValueFunc, $value) : $value;
724  if (!in_array($keepValue, $keepItems)) {
725  unset($array[$key]);
726  }
727  }
728  }
729  }
730  return $array;
731  }
732 
739  public static function remapArrayKeys(array &$array, array $mappingTable)
740  {
741  foreach ($mappingTable as $old => $new) {
742  if ($new && isset($array[$old])) {
743  $array[$new] = $array[$old];
744  unset($array[$old]);
745  }
746  }
747  }
748 
757  public static function arrayDiffAssocRecursive(array $array1, array $array2)
758  {
759  $differenceArray = [];
760  foreach ($array1 as $key => $value) {
761  if (!array_key_exists($key, $array2)) {
762  $differenceArray[$key] = $value;
763  } elseif (is_array($value)) {
764  if (is_array($array2[$key])) {
765  $differenceArray[$key] = self::arrayDiffAssocRecursive($value, $array2[$key]);
766  }
767  }
768  }
769  return $differenceArray;
770  }
771 
778  public static function naturalKeySortRecursive(array &$array)
779  {
780  uksort($array, 'strnatcasecmp');
781  foreach ($array as $key => &$value) {
782  if (is_array($value)) {
783  self::naturalKeySortRecursive($value);
784  }
785  }
786 
787  return true;
788  }
789 
798  public static function filterAndSortByNumericKeys($setupArr, $acceptAnyKeys = false)
799  {
800  $filteredKeys = [];
801  $keys = array_keys($setupArr);
802  foreach ($keys as $key) {
803  if ($acceptAnyKeys || MathUtility::canBeInterpretedAsInteger($key)) {
804  $filteredKeys[] = (int)$key;
805  }
806  }
807  $filteredKeys = array_unique($filteredKeys);
808  sort($filteredKeys);
809  return $filteredKeys;
810  }
811 
819  public static function sortArrayWithIntegerKeys(array $array)
820  {
821  if (count(array_filter(array_keys($array), 'is_string')) === 0) {
822  ksort($array);
823  }
824  return $array;
825  }
826 
834  public static function sortArrayWithIntegerKeysRecursive(array $array): array
835  {
836  $array = static::sortArrayWithIntegerKeys($array);
837  foreach ($array as $key => $value) {
838  if (is_array($value) && !empty($value)) {
839  $array[$key] = self::sortArrayWithIntegerKeysRecursive($value);
840  }
841  }
842  return $array;
843  }
844 
851  public static function stripTagsFromValuesRecursive(array $array): array
852  {
853  $result = $array;
854  foreach ($result as $key => $value) {
855  if (is_array($value)) {
856  $result[$key] = self::stripTagsFromValuesRecursive($value);
857  } else {
858  if (!is_bool($value)) {
859  $result[$key] = strip_tags($value);
860  }
861  }
862  }
863  return $result;
864  }
865 }
static assertAllArrayKeysAreValid(array $arrayToTest, array $allowedArrayKeys)
static renumberKeysToAvoidLeapsIfKeysAreAllNumeric(array $array=[], $level=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static sortArraysByKey(array $arrays, $key, $ascending=true)
static keepItemsInArray(array $array, $keepItems, $getValueFunc=null)
static arrayDiffAssocRecursive(array $array1, array $array2)
static filterByValueRecursive($needle= '', array $haystack=[])
static remapArrayKeys(array &$array, array $mappingTable)
static getValueByPath(array $array, $path, $delimiter= '/')
static intersectRecursive(array $source, array $mask=[])
static removeByPath(array $array, $path, $delimiter= '/')
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
static filterAndSortByNumericKeys($setupArr, $acceptAnyKeys=false)
static setValueByPath(array $array, $path, $value, $delimiter= '/')
static isValidPath(array $array, $path, $delimiter= '/')
static removeArrayEntryByValue(array $array, $cmpValue)