[FEATURE] Add ArrayUtility::filterRecursive() method 06/55106/14
authorStefan Neufeind <typo3.neufeind@speedpartner.de>
Sat, 16 Dec 2017 10:08:04 +0000 (11:08 +0100)
committerAlexander Opitz <opitz.alexander@googlemail.com>
Tue, 19 Dec 2017 13:50:23 +0000 (14:50 +0100)
Add a new method ArrayUtility::filterRecursive() as an
enhancement to the PHP function array_filter() to filter
nested arrays.

Resolves: #83350
Releases: master
Change-Id: If291f1695eeef934ddf858ad28c54b30185e8bbc
Reviewed-on: https://review.typo3.org/55106
Reviewed-by: Stephan GroƟberndt <stephan@grossberndt.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Mathias Brodala <mbrodala@pagemachine.de>
Tested-by: Mathias Brodala <mbrodala@pagemachine.de>
Reviewed-by: Stefan Neufeind <typo3.neufeind@speedpartner.de>
Reviewed-by: Henning Liebe <h.liebe@neusta.de>
Tested-by: Henning Liebe <h.liebe@neusta.de>
Reviewed-by: Alexander Opitz <opitz.alexander@googlemail.com>
Tested-by: Alexander Opitz <opitz.alexander@googlemail.com>
Reviewed-by: Benjamin Kluge <b.kluge@neusta.de>
typo3/sysext/core/Classes/Utility/ArrayUtility.php
typo3/sysext/core/Documentation/Changelog/master/Feature-83350-AddRecursiveArrayFiltering.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php

index 106025a..13aa3b7 100644 (file)
@@ -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 (file)
index 0000000..d2dcc3c
--- /dev/null
@@ -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
index 0e4740b..b722b3d 100644 (file)
@@ -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);
+    }
 }