Skip to content

[2.x] Introduce configurable merge strategy for shared props #744

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

Open
wants to merge 2 commits into
base: 2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/DeepMergeStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Inertia;

use Closure;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;
use ReflectionFunction;

class DeepMergeStrategy implements MergeStrategy
{
/**
* Recursively merges multiple shared Inertia props within the current request.
* This method ensures that overlapping keys between multiple sets of props
* are merged deeply instead of overwritten, preserving nested structures.
*/
public function merge(array $original, string|array|Arrayable $key, mixed $value = null): array
{
$mergedProps = $original;

$newProps = match (true) {
is_string($key) => [$key => $value],
is_array($key) => $key,
$key instanceof Arrayable => $value->toArray(),
};

foreach ($newProps as $key => $prop) {
$propArray = $this->attemptArrayCast($prop);
$mergedPropArray = $this->attemptArrayCast(Arr::get($mergedProps, $key));

$shouldFlattenPropArray = is_int($key) && is_array($propArray);
if ($shouldFlattenPropArray) {
$mergedProps = $this->merge($propArray, $mergedProps);

continue;
}

$shouldOverride = ! is_array($propArray) || ! is_array($mergedPropArray);
if ($shouldOverride) {
Arr::set($mergedProps, $key, $propArray);

continue;
}

$shouldConcatenate = $this->isIndexedArray($propArray) && $this->isIndexedArray($mergedPropArray);
if ($shouldConcatenate) {
Arr::set($mergedProps, $key, array_merge($mergedPropArray, $propArray));

continue;
}

Arr::set($mergedProps, $key, $this->merge($propArray, $mergedPropArray));
}

return $mergedProps;
}

protected function isIndexedArray(array $array): bool
{
return array_keys($array) === range(0, count($array) - 1);
}

protected function attemptArrayCast(mixed $value): mixed
{
if ($value instanceof Closure) {
$reflection = new ReflectionFunction($value);

if (! $reflection->getNumberOfRequiredParameters()) {
$value = call_user_func($value);
}
}

if ($value instanceof Arrayable) {
return $value->toArray();
}

return $value;
}
}
1 change: 1 addition & 0 deletions src/Inertia.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @method static void setRootView(string $name)
* @method static void share(string|array|\Illuminate\Contracts\Support\Arrayable $key, mixed $value = null)
* @method static mixed getShared(string|null $key = null, mixed $default = null)
* @method static self setSharedPropMerger(MergeStrategy $mergeStrategy)
* @method static void clearHistory()
* @method static void encryptHistory($encrypt = true)
* @method static void flushShared()
Expand Down
10 changes: 10 additions & 0 deletions src/MergeStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Inertia;

use Illuminate\Contracts\Support\Arrayable;

interface MergeStrategy
{
public function merge(array $original, string|array|Arrayable $key, mixed $value = null): array;
}
29 changes: 22 additions & 7 deletions src/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class ResponseFactory
/** @var array */
protected $sharedProps = [];

protected ?MergeStrategy $sharedPropMerger = null;

/** @var Closure|string|null */
protected $version;

Expand All @@ -46,13 +48,17 @@ public function setRootView(string $name): void
*/
public function share($key, $value = null): void
{
if (is_array($key)) {
$this->sharedProps = array_merge($this->sharedProps, $key);
} elseif ($key instanceof Arrayable) {
$this->sharedProps = array_merge($this->sharedProps, $key->toArray());
} else {
Arr::set($this->sharedProps, $key, $value);
if ($value instanceof MergeStrategy) {
$this->sharedProps = $value->merge($this->sharedProps, $key);

return;
}

if (is_null($this->sharedPropMerger)) {
$this->sharedPropMerger = App::make(MergeStrategy::class);
}

$this->sharedProps = $this->sharedPropMerger->merge($this->sharedProps, $key, $value);
}

/**
Expand All @@ -68,6 +74,13 @@ public function getShared(?string $key = null, $default = null)
return $this->sharedProps;
}

public function setSharedPropMerger(MergeStrategy $mergeStrategy): self
{
$this->sharedPropMerger = $mergeStrategy;

return $this;
}

/**
* @return void
*/
Expand Down Expand Up @@ -157,9 +170,11 @@ public function render(string $component, $props = []): Response
$props = $props->toArray();
}

$this->share($props);

return new Response(
$component,
array_merge($this->sharedProps, $props),
$this->sharedProps,
$this->rootView,
$this->getVersion(),
$this->encryptHistory ?? config('inertia.history.encrypt', false),
Expand Down
1 change: 1 addition & 0 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function register(): void
{
$this->app->singleton(ResponseFactory::class);
$this->app->bind(Gateway::class, HttpGateway::class);
$this->app->bind(MergeStrategy::class, ShallowMergeStrategy::class);

$this->mergeConfigFrom(
__DIR__.'/../config/inertia.php',
Expand Down
22 changes: 22 additions & 0 deletions src/ShallowMergeStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Inertia;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Arr;

class ShallowMergeStrategy implements MergeStrategy
{
public function merge(array $original, string|array|Arrayable $key, mixed $value = null): array
{
if (is_array($key)) {
$original = array_merge($original, $key);
} elseif ($key instanceof Arrayable) {
$original = array_merge($original, $key->toArray());
} else {
Arr::set($original, $key, $value);
}

return $original;
}
}
Loading