From d2c5372bc659d2bb976211f713548b13e2049e0b Mon Sep 17 00:00:00 2001 From: Theo D Date: Sun, 3 Mar 2024 22:24:00 +0100 Subject: [PATCH] feat: Add possibility to recursive merge data in Context --- CHANGELOG.md | 1 + examples/context.php | 30 +++++++++++-- src/Context.php | 45 +++++++++++++++++-- .../ContextContextDynamicTest.php.output.txt | 1 + ...ContextContextMyDefaultTest.php.output.txt | 1 + .../ContextContextPathTest.php.output.txt | 1 + ...ontextContextProductionTest.php.output.txt | 1 + .../ContextContextRunTest.php.output.txt | 1 + .../ContextContextTest.php.output.txt | 1 + .../ContextContextWithTest.php.output.txt | 1 + 10 files changed, 76 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5111371e..d285a1ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Add a `yaml_parse()` function to parse a YAML string to a PHP value * Remove the default timeout of 60 seconds from the Context * Add `bool` return type to `fingerprint()` function to indicate if the callable was run +* Add a `recursive` parameter to the `withData()` method of `Context` to allow recursive merging for nested arrays ## 0.13.1 (2024-02-27) diff --git a/examples/context.php b/examples/context.php index 27154bc0..3a608f93 100644 --- a/examples/context.php +++ b/examples/context.php @@ -21,6 +21,16 @@ function defaultContext(): Context 'name' => 'my_default', 'production' => false, 'foo' => 'bar', + 'nested' => [ + 'merge' => [ + 'key' => [ + 'value' => 'should keep this', + 'replaced' => 'should be replaced', + ], + 'another' => 'should keep', + ], + 'another' => 'should keep', + ], ]); } @@ -28,10 +38,21 @@ function defaultContext(): Context function productionContext(): Context { return defaultContext() - ->withData([ - 'name' => 'production', - 'production' => true, - ]) + ->withData( + [ + 'name' => 'production', + 'production' => true, + 'nested' => [ + 'merge' => [ + 'key' => [ + 'replaced' => 'replaced value', + 'new' => 'new value', + ], + ], + ], + ], + recursive: true + ) ; } @@ -83,6 +104,7 @@ function contextInfo(): void echo 'Production? ' . (variable('production', false) ? 'yes' : 'no') . "\n"; echo "verbosity: {$context->verbosityLevel->value}\n"; echo 'context: ' . variable('foo', 'N/A') . "\n"; + echo 'nested merge recursive: ' . json_encode(variable('nested', []), \JSON_THROW_ON_ERROR) . "\n"; } /** diff --git a/src/Context.php b/src/Context.php index a1b4c837..de201432 100644 --- a/src/Context.php +++ b/src/Context.php @@ -45,11 +45,29 @@ public function __debugInfo() ]; } - /** @param array<(int|string), mixed> $data */ - public function withData(array $data, bool $keepExisting = true): self + /** + * @param array<(int|string), mixed> $data + * + * @throws \Exception + */ + public function withData(array $data, bool $keepExisting = true, bool $recursive = true): self { + if (false === $keepExisting && true === $recursive) { + throw new \Exception('You cannot use the recursive option without keeping the existing data'); + } + + if ($keepExisting) { + if ($recursive) { + /* @var array<(int|string), mixed> */ + $data = $this->arrayMergeRecursiveDistinct($this->data, $data); + } else { + /* @var array<(int|string), mixed> */ + $data = array_merge($this->data, $data); + } + } + return new self( - $keepExisting ? array_merge($this->data, $data) : $data, + $data, $this->environment, $this->currentDirectory, $this->tty, @@ -257,4 +275,25 @@ public function offsetUnset(mixed $offset): void { throw new \LogicException('Context is immutable'); } + + /** + * @param array<(int|string), mixed> $array1 + * @param array<(int|string), mixed> $array2 + * + * @return array<(int|string), mixed> + */ + private function arrayMergeRecursiveDistinct(array $array1, array $array2): array + { + /** @var array<(int|string), mixed> $merged */ + $merged = $array1; + foreach ($array2 as $key => $value) { + if (\is_array($value) && isset($merged[$key]) && \is_array($merged[$key])) { + $merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value); + } else { + $merged[$key] = $value; + } + } + + return $merged; + } } diff --git a/tests/Examples/Generated/ContextContextDynamicTest.php.output.txt b/tests/Examples/Generated/ContextContextDynamicTest.php.output.txt index b5e52a95..41f37814 100644 --- a/tests/Examples/Generated/ContextContextDynamicTest.php.output.txt +++ b/tests/Examples/Generated/ContextContextDynamicTest.php.output.txt @@ -2,3 +2,4 @@ context name: dynamic Production? no verbosity: 1 context: baz +nested merge recursive: [] diff --git a/tests/Examples/Generated/ContextContextMyDefaultTest.php.output.txt b/tests/Examples/Generated/ContextContextMyDefaultTest.php.output.txt index d9f99d5a..ec169891 100644 --- a/tests/Examples/Generated/ContextContextMyDefaultTest.php.output.txt +++ b/tests/Examples/Generated/ContextContextMyDefaultTest.php.output.txt @@ -2,3 +2,4 @@ context name: my_default Production? no verbosity: 2 context: bar +nested merge recursive: {"merge":{"key":{"value":"should keep this","replaced":"should be replaced"},"another":"should keep"},"another":"should keep"} diff --git a/tests/Examples/Generated/ContextContextPathTest.php.output.txt b/tests/Examples/Generated/ContextContextPathTest.php.output.txt index bf1dfcde..9682015f 100644 --- a/tests/Examples/Generated/ContextContextPathTest.php.output.txt +++ b/tests/Examples/Generated/ContextContextPathTest.php.output.txt @@ -2,3 +2,4 @@ context name: path Production? yes verbosity: 1 context: bar +nested merge recursive: [] diff --git a/tests/Examples/Generated/ContextContextProductionTest.php.output.txt b/tests/Examples/Generated/ContextContextProductionTest.php.output.txt index e30eb5ba..b165b460 100644 --- a/tests/Examples/Generated/ContextContextProductionTest.php.output.txt +++ b/tests/Examples/Generated/ContextContextProductionTest.php.output.txt @@ -2,3 +2,4 @@ context name: production Production? yes verbosity: 1 context: bar +nested merge recursive: {"merge":{"key":{"value":"should keep this","replaced":"replaced value","new":"new value"},"another":"should keep"},"another":"should keep"} diff --git a/tests/Examples/Generated/ContextContextRunTest.php.output.txt b/tests/Examples/Generated/ContextContextRunTest.php.output.txt index b371862f..10168f4a 100644 --- a/tests/Examples/Generated/ContextContextRunTest.php.output.txt +++ b/tests/Examples/Generated/ContextContextRunTest.php.output.txt @@ -2,3 +2,4 @@ context name: run Production? no verbosity: 1 context: no defined +nested merge recursive: [] diff --git a/tests/Examples/Generated/ContextContextTest.php.output.txt b/tests/Examples/Generated/ContextContextTest.php.output.txt index 0d2e5e60..afc29c98 100644 --- a/tests/Examples/Generated/ContextContextTest.php.output.txt +++ b/tests/Examples/Generated/ContextContextTest.php.output.txt @@ -2,3 +2,4 @@ context name: my_default Production? no verbosity: 1 context: bar +nested merge recursive: {"merge":{"key":{"value":"should keep this","replaced":"should be replaced"},"another":"should keep"},"another":"should keep"} diff --git a/tests/Examples/Generated/ContextContextWithTest.php.output.txt b/tests/Examples/Generated/ContextContextWithTest.php.output.txt index 280f111d..5d932fe3 100644 --- a/tests/Examples/Generated/ContextContextWithTest.php.output.txt +++ b/tests/Examples/Generated/ContextContextWithTest.php.output.txt @@ -2,4 +2,5 @@ context name: dynamic Production? no verbosity: -1 context: bar +nested merge recursive: [] bar \ No newline at end of file