diff --git a/typo3/sysext/core/Classes/Utility/ArrayUtility.php b/typo3/sysext/core/Classes/Utility/ArrayUtility.php index 106025a56907ea347803fed5310adf7d171f298a..13aa3b7c00a370bd88770b75634cb41effc302e4 100644 --- a/typo3/sysext/core/Classes/Utility/ArrayUtility.php +++ b/typo3/sysext/core/Classes/Utility/ArrayUtility.php @@ -830,4 +830,31 @@ class ArrayUtility } return $result; } + + /** + * Recursively filter an array + * + * @param array $array + * @param callable|null $callback + * @return array the filtered array + * @see https://secure.php.net/manual/en/function.array-filter.php + */ + public static function filterRecursive(array $array, callable $callback = null): array + { + $callback = $callback ?: function ($value) { + return (bool)$value; + }; + + foreach ($array as $key => $value) { + if (is_array($value)) { + $array[$key] = self::filterRecursive($value, $callback); + } + + if (!$callback($value)) { + unset($array[$key]); + } + } + + return $array; + } } diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-83350-AddRecursiveArrayFiltering.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-83350-AddRecursiveArrayFiltering.rst new file mode 100644 index 0000000000000000000000000000000000000000..d2dcc3c00f92bd0fb2fc7c6a188a5615acf6656b --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-83350-AddRecursiveArrayFiltering.rst @@ -0,0 +1,24 @@ +.. include:: ../../Includes.txt + +=================================================== +Feature: #83350 - Add recursive filtering of arrays +=================================================== + +See :issue:`83350` + +Description +=========== + +The new method :php:`\TYPO3\CMS\Core\Utility\ArrayUtility::filterRecursive()` has been added as an enhancement to the `PHP function`_ :php:`array_filter()` +to filter multidimensional arrays. The method :php:`ArrayUtility::filterRecursive()` behaves just like :php:`array_filter()`, if no callback is defined, values +are removed if they equal to boolean :php:`false`. See `converting to boolean`_. + +.. _`PHP function`: https://secure.php.net/manual/en/function.array-filter.php +.. _`converting to boolean`: https://secure.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting + +Impact +====== + +Arrays can be filtered recursively using the new method. + +.. index:: PHP-API, NotScanned diff --git a/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php index 0e4740b0645003734ac9c4a80fbe5b58e37d9923..b722b3d70ab14eb7348462895046949941c9ffa2 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php @@ -2665,4 +2665,166 @@ class ArrayUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase $this->assertSame($expected, ArrayUtility::convertBooleanStringsToBooleanRecursive($input)); } + + /** + * Data provider for arrayFilterRecursiveFiltersFalseElements + * @return array + */ + public function filterRecursiveFiltersFalseElementsDataProvider() + { + return [ + 'filter all values which will be false when converted to boolean' => [ + // input + [ + true, + false, + 'foo1' => [ + 'bar' => [ + 'baz' => [ + '1', + null, + '', + ], + '' => 1, + 'bbd' => 0, + ] + ], + 'foo2' => 'foo', + 'foo3' => '', + 'foo4' => [ + 'z' => 'bar', + 'bar' => 0, + 'baz' => [ + 'foo' => [ + 'bar' => '', + 'boo' => [], + 'bamboo' => 5, + 'fooAndBoo' => [0], + ] + ], + ], + ], + // expected + [ + true, + 'foo1' => [ + 'bar' => [ + 'baz' => [ + '1', + ], + '' => 1, + ] + ], + 'foo2' => 'foo', + 'foo4' => [ + 'z' => 'bar', + 'baz' => [ + 'foo' => [ + 'bamboo' => 5, + 'fooAndBoo' => [], + ] + ], + ], + ], + ], + ]; + } + + /** + * @test + * @dataProvider filterRecursiveFiltersFalseElementsDataProvider + * @param array $input + * @param array $expectedResult + */ + public function filterRecursiveFiltersFalseElements(array $input, array $expectedResult) + { + // If no callback is supplied, all entries of array equal to FALSE (see converting to boolean) will be removed. + $result = ArrayUtility::filterRecursive($input); + $this->assertEquals($expectedResult, $result); + } + + /** + * Data provider for filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerByCallback + * @return array + */ + public function filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerZeroByCallbackDataProvider() + { + return [ + 'filter empty values, keep zero integers' => [ + // input + [ + true, + false, + 'foo1' => [ + 'bar' => [ + 'baz' => [ + '1', + null, + '', + ], + '' => 1, + 'bbd' => 0, + ] + ], + 'foo2' => 'foo', + 'foo3' => '', + 'foo4' => [ + 'z' => 'bar', + 'bar' => 0, + 'baz' => [ + 'foo' => [ + 'bar' => '', + 'boo' => [], + 'bamboo' => 5, + 'fooAndBoo' => [0], + ] + ], + ], + ], + // expected + [ + true, + false, + 'foo1' => [ + 'bar' => [ + 'baz' => [ + '1', + ], + '' => 1, + 'bbd' => 0, + ] + ], + 'foo2' => 'foo', + 'foo4' => [ + 'z' => 'bar', + 'bar' => 0, + 'baz' => [ + 'foo' => [ + 'bamboo' => 5, + 'fooAndBoo' => [0], + ] + ], + ], + ], + ], + ]; + } + + /** + * @test + * @dataProvider filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerZeroByCallbackDataProvider + * @param array $input + * @param array $expectedResult + */ + public function filterRecursiveCallbackFiltersEmptyElementsWithoutIntegerByCallback(array $input, array $expectedResult) + { + // callback filters empty strings, array and null but keeps zero integers + $result = ArrayUtility::filterRecursive( + $input, + function ($item) { + return $item !== '' && $item !== [] && $item !== null; + } + ); + $this->assertEquals($expectedResult, $result); + } }