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/Attribute/AsCommandArgument.php b/src/Attribute/AsCommandArgument.php index e1ea8963..180f7ae2 100644 --- a/src/Attribute/AsCommandArgument.php +++ b/src/Attribute/AsCommandArgument.php @@ -5,7 +5,7 @@ abstract class AsCommandArgument { public function __construct( - public readonly string|null $name = null, + public readonly ?string $name = null, ) { } } diff --git a/src/Attribute/AsOption.php b/src/Attribute/AsOption.php index 870e1e68..fa93cfa4 100644 --- a/src/Attribute/AsOption.php +++ b/src/Attribute/AsOption.php @@ -12,7 +12,7 @@ class AsOption extends AsCommandArgument public function __construct( ?string $name = null, public readonly string|array|null $shortcut = null, - public readonly int|null $mode = null, + public readonly ?int $mode = null, public readonly string $description = '', public readonly array $suggestedValues = [], ) { diff --git a/src/Attribute/AsTask.php b/src/Attribute/AsTask.php index 391bcae1..e681d0a1 100644 --- a/src/Attribute/AsTask.php +++ b/src/Attribute/AsTask.php @@ -11,7 +11,7 @@ class AsTask */ public function __construct( public string $name = '', - public string|null $namespace = null, + public ?string $namespace = null, public string $description = '', public array $aliases = [], public array $onSignals = [], diff --git a/src/Context.php b/src/Context.php index c34fbd12..1a4f0afb 100644 --- a/src/Context.php +++ b/src/Context.php @@ -17,7 +17,7 @@ public function __construct( ?string $currentDirectory = null, public readonly bool $tty = false, public readonly bool $pty = true, - public readonly float|null $timeout = null, + public readonly ?float $timeout = null, public readonly bool $quiet = false, public readonly bool $allowFailure = false, public readonly bool $notify = false, @@ -45,11 +45,25 @@ 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 ($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, @@ -132,7 +146,7 @@ public function withPty(bool $pty = true): self ); } - public function withTimeout(float|null $timeout): self + public function withTimeout(?float $timeout): self { return new self( $this->data, @@ -257,4 +271,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/src/SectionOutput.php b/src/SectionOutput.php index b3c0a24e..3a37a6a0 100644 --- a/src/SectionOutput.php +++ b/src/SectionOutput.php @@ -14,7 +14,7 @@ class SectionOutput private OutputInterface|ConsoleSectionOutput $consoleOutput; - private ConsoleOutput|null $mainOutput; + private ?ConsoleOutput $mainOutput; /** @var \SplObjectStorage */ private \SplObjectStorage $sections; 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