Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add possibility to recursive merge data in Context #307

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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)

Expand Down
30 changes: 26 additions & 4 deletions examples/context.php
Expand Up @@ -21,17 +21,38 @@ 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',
],
]);
}

#[AsContext(name: 'production')]
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
)
;
}

Expand Down Expand Up @@ -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";
}

/**
Expand Down
45 changes: 42 additions & 3 deletions src/Context.php
Expand Up @@ -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);
lyrixx marked this conversation as resolved.
Show resolved Hide resolved
} 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,
Expand Down Expand Up @@ -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;
}
}
Expand Up @@ -2,3 +2,4 @@ context name: dynamic
Production? no
verbosity: 1
context: baz
nested merge recursive: []
Expand Up @@ -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"}
Expand Up @@ -2,3 +2,4 @@ context name: path
Production? yes
verbosity: 1
context: bar
nested merge recursive: []
Expand Up @@ -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"}
Expand Up @@ -2,3 +2,4 @@ context name: run
Production? no
verbosity: 1
context: no defined
nested merge recursive: []
1 change: 1 addition & 0 deletions tests/Examples/Generated/ContextContextTest.php.output.txt
Expand Up @@ -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"}
Expand Up @@ -2,4 +2,5 @@ context name: dynamic
Production? no
verbosity: -1
context: bar
nested merge recursive: []
bar