TYPO3 CMS  TYPO3_8-7
ArrayUtility.php
Go to the documentation of this file.
1 <?php
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 {
30  public static function assertAllArrayKeysAreValid(array $arrayToTest, array $allowedArrayKeys)
31  {
32  $notAllowedArrayKeys = array_keys(array_diff_key($arrayToTest, array_flip($allowedArrayKeys)));
33  if (count($notAllowedArrayKeys) !== 0) {
34  throw new \InvalidArgumentException(
35  sprintf(
36  'The options "%s" were not allowed (allowed were: "%s")',
37  implode(', ', $notAllowedArrayKeys),
38  implode(', ', $allowedArrayKeys)
39  ),
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 (is_array($value) && 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 
611  public static function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
612  {
613  foreach ($overrule as $key => $_) {
614  if ($enableUnsetFeature && $overrule[$key] === '__UNSET') {
615  unset($original[$key]);
616  continue;
617  }
618  if (isset($original[$key]) && is_array($original[$key])) {
619  if (is_array($overrule[$key])) {
620  self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature);
621  }
622  } elseif (
623  ($addKeys || isset($original[$key])) &&
624  ($includeEmptyValues || $overrule[$key])
625  ) {
626  $original[$key] = $overrule[$key];
627  }
628  }
629  // This line is kept for backward compatibility reasons.
630  reset($original);
631  }
632 
656  public static function inArray(array $in_array, $item)
657  {
659  foreach ($in_array as $val) {
660  if (!is_array($val) && (string)$val === (string)$item) {
661  return true;
662  }
663  }
664  return false;
665  }
666 
674  public static function removeArrayEntryByValue(array $array, $cmpValue)
675  {
676  foreach ($array as $k => $v) {
677  if (is_array($v)) {
678  $array[$k] = self::removeArrayEntryByValue($v, $cmpValue);
679  } elseif ((string)$v === (string)$cmpValue) {
680  unset($array[$k]);
681  }
682  }
683  return $array;
684  }
685 
709  public static function keepItemsInArray(array $array, $keepItems, $getValueFunc = null)
710  {
711  if ($array) {
712  // Convert strings to arrays:
713  if (is_string($keepItems)) {
714  $keepItems = GeneralUtility::trimExplode(',', $keepItems);
715  }
716  // Check if valueFunc can be executed:
717  if (!is_callable($getValueFunc)) {
718  $getValueFunc = null;
719  }
720  // Do the filtering:
721  if (is_array($keepItems) && !empty($keepItems)) {
722  $keepItems = array_flip($keepItems);
723  foreach ($array as $key => $value) {
724  // Get the value to compare by using the callback function:
725  $keepValue = isset($getValueFunc) ? call_user_func($getValueFunc, $value) : $value;
726  if (!isset($keepItems[$keepValue])) {
727  unset($array[$key]);
728  }
729  }
730  }
731  }
732  return $array;
733  }
734 
741  public static function remapArrayKeys(array &$array, array $mappingTable)
742  {
743  foreach ($mappingTable as $old => $new) {
744  if ($new && isset($array[$old])) {
745  $array[$new] = $array[$old];
746  unset($array[$old]);
747  }
748  }
749  }
750 
759  public static function arrayDiffAssocRecursive(array $array1, array $array2)
760  {
761  $differenceArray = [];
762  foreach ($array1 as $key => $value) {
763  if (!array_key_exists($key, $array2)) {
764  $differenceArray[$key] = $value;
765  } elseif (is_array($value)) {
766  if (is_array($array2[$key])) {
767  $recursiveResult = self::arrayDiffAssocRecursive($value, $array2[$key]);
768  if (!empty($recursiveResult)) {
769  $differenceArray[$key] = $recursiveResult;
770  }
771  }
772  }
773  }
774  return $differenceArray;
775  }
776 
783  public static function naturalKeySortRecursive(array &$array)
784  {
785  uksort($array, 'strnatcasecmp');
786  foreach ($array as $key => &$value) {
787  if (is_array($value)) {
788  self::naturalKeySortRecursive($value);
789  }
790  }
791 
792  return true;
793  }
794 
803  public static function filterAndSortByNumericKeys($setupArr, $acceptAnyKeys = false)
804  {
805  $filteredKeys = [];
806  $keys = array_keys($setupArr);
807  foreach ($keys as $key) {
808  if ($acceptAnyKeys || MathUtility::canBeInterpretedAsInteger($key)) {
809  $filteredKeys[] = (int)$key;
810  }
811  }
812  $filteredKeys = array_unique($filteredKeys);
813  sort($filteredKeys);
814  return $filteredKeys;
815  }
816 
824  public static function sortArrayWithIntegerKeys(array $array)
825  {
826  if (count(array_filter(array_keys($array), 'is_string')) === 0) {
827  ksort($array);
828  }
829  return $array;
830  }
831 
839  public static function sortArrayWithIntegerKeysRecursive(array $array): array
840  {
841  $array = static::sortArrayWithIntegerKeys($array);
842  foreach ($array as $key => $value) {
843  if (is_array($value) && !empty($value)) {
844  $array[$key] = self::sortArrayWithIntegerKeysRecursive($value);
845  }
846  }
847  return $array;
848  }
849 
856  public static function stripTagsFromValuesRecursive(array $array): array
857  {
858  $result = $array;
859  foreach ($result as $key => $value) {
860  if (is_array($value)) {
861  $result[$key] = self::stripTagsFromValuesRecursive($value);
862  } elseif (is_string($value) || (is_object($value) && method_exists($value, '__toString'))) {
863  $result[$key] = strip_tags($value);
864  }
865  }
866  return $result;
867  }
868 }
static filterByValueRecursive($needle='', array $haystack=[])
static setValueByPath(array $array, $path, $value, $delimiter='/')
static getValueByPath(array $array, $path, $delimiter='/')
static sortArrayWithIntegerKeysRecursive(array $array)
static convertBooleanStringsToBooleanRecursive(array $array)
static intersectRecursive(array $source, array $mask=[])
static assertAllArrayKeysAreValid(array $arrayToTest, array $allowedArrayKeys)
static reIndexNumericArrayKeysRecursive(array $array)
static flatten(array $array, $prefix='')
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static remapArrayKeys(array &$array, array $mappingTable)
static arrayExport(array $array=[], $level=0)
static naturalKeySortRecursive(array &$array)
static inArray(array $in_array, $item)
static removeByPath(array $array, $path, $delimiter='/')
static sortByKeyRecursive(array $array)
static renumberKeysToAvoidLeapsIfKeysAreAllNumeric(array $array=[], $level=0)
static isValidPath(array $array, $path, $delimiter='/')
static filterAndSortByNumericKeys($setupArr, $acceptAnyKeys=false)
static removeArrayEntryByValue(array $array, $cmpValue)
static keepItemsInArray(array $array, $keepItems, $getValueFunc=null)
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
static sortArrayWithIntegerKeys(array $array)
static removeNullValuesRecursive(array $array)
static sortArraysByKey(array $arrays, $key, $ascending=true)
static arrayDiffAssocRecursive(array $array1, array $array2)
static stripTagsFromValuesRecursive(array $array)