diff --git a/src/core/etl/src/Flow/ETL/Function/OnEach.php b/src/core/etl/src/Flow/ETL/Function/OnEach.php new file mode 100644 index 000000000..6105680eb --- /dev/null +++ b/src/core/etl/src/Flow/ETL/Function/OnEach.php @@ -0,0 +1,50 @@ +ref->eval($row); + + if (!\is_array($value)) { + return null; + } + + $preserveKeys = \is_bool($this->preserveKeys) ? $this->preserveKeys : (bool) $this->preserveKeys->eval($row); + + $output = []; + + foreach ($value as $key => $item) { + if ($preserveKeys) { + try { + $output[$key] = $this->function->eval(array_to_row(['element' => $item])); + } catch (InvalidArgumentException $e) { + $output[$key] = null; + } + } else { + try { + $output[] = $this->function->eval(array_to_row(['element' => $item])); + } catch (InvalidArgumentException $e) { + $output[] = null; + } + } + } + + return $output; + } +} diff --git a/src/core/etl/src/Flow/ETL/Function/ScalarFunctionChain.php b/src/core/etl/src/Flow/ETL/Function/ScalarFunctionChain.php index 877bfa725..47a3c1c58 100644 --- a/src/core/etl/src/Flow/ETL/Function/ScalarFunctionChain.php +++ b/src/core/etl/src/Flow/ETL/Function/ScalarFunctionChain.php @@ -4,7 +4,7 @@ namespace Flow\ETL\Function; -use function Flow\ETL\DSL\lit; +use function Flow\ETL\DSL\{lit, ref, type_string}; use Flow\ETL\Exception\InvalidArgumentException; use Flow\ETL\Function; use Flow\ETL\Function\ArrayExpand\ArrayExpand; @@ -303,6 +303,17 @@ public function numberFormat(?ScalarFunction $decimals = null, ?ScalarFunction $ return new NumberFormat($this, $decimals, $decimalSeparator, $thousandsSeparator); } + /** + * Execute a scalar function on each element of an array/list/map/structure entry. + * In order to use this function, you need to provide a reference to the "element" that will be used in the function. + * + * Example: $df->withEntry('array', ref('array')->onEach(ref('element')->cast(type_string()))) + */ + public function onEach(self $cast, ScalarFunction|bool $preserveKeys = true) : OnEach + { + return new OnEach($this, $cast, $preserveKeys); + } + public function plus(ScalarFunction $ref) : self { return new Plus($this, $ref); diff --git a/src/core/etl/tests/Flow/ETL/Tests/Integration/Function/OnEachTest.php b/src/core/etl/tests/Flow/ETL/Tests/Integration/Function/OnEachTest.php new file mode 100644 index 000000000..f82543a46 --- /dev/null +++ b/src/core/etl/tests/Flow/ETL/Tests/Integration/Function/OnEachTest.php @@ -0,0 +1,31 @@ +read(from_array([ + ['array' => ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5]], + ['array' => ['f' => 1, 'g' => 2.3, 'h' => 3, 'i' => 4, 'j' => null]], + ])) + ->withEntry('array', ref('array')->onEach(ref('element')->cast(type_string()))) + ->fetch() + ->toArray(); + + self::assertEquals( + [ + ['array' => ['a' => '1', 'b' => '2', 'c' => '3', 'd' => '4', 'e' => '5']], + ['array' => ['f' => '1', 'g' => '2.3', 'h' => '3', 'i' => '4', 'j' => null]], + ], + $results + ); + } +} diff --git a/src/core/etl/tests/Flow/ETL/Tests/Unit/Function/OnEachTest.php b/src/core/etl/tests/Flow/ETL/Tests/Unit/Function/OnEachTest.php new file mode 100644 index 000000000..34b2e96e2 --- /dev/null +++ b/src/core/etl/tests/Flow/ETL/Tests/Unit/Function/OnEachTest.php @@ -0,0 +1,75 @@ +onEach(ref('element')->cast(type_string())) + ->eval( + row( + array_entry( + 'array', + [1, 2, 3, 4, 5] + ) + ) + ), + ); + } + + public function test_executing_function_on_each_value_from_empty_array() : void + { + self::assertSame( + [], + ref('array')->onEach(ref('element')->cast(type_string())) + ->eval( + row( + array_entry( + 'array', + [] + ) + ) + ), + ); + } + + public function test_executing_function_on_each_value_with_preserving_keys() : void + { + self::assertSame( + ['a' => '1', 'b' => '2', 'c' => '3', 'd' => '4', 'e' => '5'], + ref('array')->onEach(ref('element')->cast(type_string()), true) + ->eval( + row( + array_entry( + 'array', + ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5] + ) + ) + ), + ); + } + + public function test_executing_function_on_each_value_without_preserving_keys() : void + { + self::assertSame( + ['1', '2', '3', '4', '5'], + ref('array')->onEach(ref('element')->cast(type_string()), false) + ->eval( + row( + array_entry( + 'array', + ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5] + ) + ) + ), + ); + } +}