Skip to content

Commit d584a2f

Browse files
Merge pull request #679 from HichemTab-tech/feature/add-deep-merge-props
Add Inertia::deepMerge Method for Handling Complex Data Merges in Responses
2 parents 12c4f73 + bb812e1 commit d584a2f

File tree

8 files changed

+170
-15
lines changed

8 files changed

+170
-15
lines changed

src/Inertia.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* @method static \Inertia\DeferProp defer(callable $callback, string $group = 'default')
1919
* @method static \Inertia\AlwaysProp always(mixed $value)
2020
* @method static \Inertia\MergeProp merge(mixed $value)
21+
* @method static \Inertia\MergeProp deepMerge(mixed $value)
2122
* @method static \Inertia\Response render(string $component, array|\Illuminate\Contracts\Support\Arrayable $props = [])
2223
* @method static \Symfony\Component\HttpFoundation\Response location(string|\Symfony\Component\HttpFoundation\RedirectResponse $url)
2324
* @method static void macro(string $name, object|callable $macro)

src/MergesProps.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,29 @@ trait MergesProps
66
{
77
protected bool $merge = false;
88

9+
protected bool $deepMerge = false;
10+
911
public function merge(): static
1012
{
1113
$this->merge = true;
1214

1315
return $this;
1416
}
1517

18+
public function deepMerge(): static
19+
{
20+
$this->deepMerge = true;
21+
22+
return $this->merge();
23+
}
24+
1625
public function shouldMerge(): bool
1726
{
1827
return $this->merge;
1928
}
29+
30+
public function shouldDeepMerge(): bool
31+
{
32+
return $this->deepMerge;
33+
}
2034
}

src/Response.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -305,18 +305,22 @@ public function resolveMergeProps(Request $request): array
305305
{
306306
$resetProps = collect(explode(',', $request->header(Header::RESET, '')));
307307
$mergeProps = collect($this->props)
308-
->filter(function ($prop) {
309-
return $prop instanceof Mergeable;
310-
})
311-
->filter(function ($prop) {
312-
return $prop->shouldMerge();
313-
})
314-
->filter(function ($prop, $key) use ($resetProps) {
315-
return ! $resetProps->contains($key);
316-
})
308+
->filter(fn ($prop) => $prop instanceof Mergeable)
309+
->filter(fn ($prop) => $prop->shouldMerge())
310+
->filter(fn ($_, $key) => ! $resetProps->contains($key));
311+
312+
$deepMergeProps = $mergeProps
313+
->filter(fn ($prop) => $prop->shouldDeepMerge())
314+
->keys();
315+
316+
$mergeProps = $mergeProps
317+
->filter(fn ($prop) => ! $prop->shouldDeepMerge())
317318
->keys();
318319

319-
return $mergeProps->isNotEmpty() ? ['mergeProps' => $mergeProps->toArray()] : [];
320+
return array_filter([
321+
'mergeProps' => $mergeProps->toArray(),
322+
'deepMergeProps' => $deepMergeProps->toArray(),
323+
], fn ($prop) => count($prop) > 0);
320324
}
321325

322326
public function resolveDeferredProps(Request $request): array

src/ResponseFactory.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ public function merge($value): MergeProp
122122
return new MergeProp($value);
123123
}
124124

125+
/**
126+
* @param mixed $value
127+
*/
128+
public function deepMerge($value): MergeProp
129+
{
130+
return (new MergeProp($value))->deepMerge();
131+
}
132+
125133
/**
126134
* @param mixed $value
127135
*/

tests/DeepMergePropTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Inertia\Tests;
4+
5+
use Illuminate\Http\Request;
6+
use Inertia\MergeProp;
7+
8+
class DeepMergePropTest extends TestCase
9+
{
10+
public function test_can_invoke_with_a_callback(): void
11+
{
12+
$mergeProp = (new MergeProp(fn () => 'A merge prop value'))->deepMerge();
13+
14+
$this->assertSame('A merge prop value', $mergeProp());
15+
}
16+
17+
public function test_can_invoke_with_a_non_callback(): void
18+
{
19+
$mergeProp = (new MergeProp(['key' => 'value']))->deepMerge();
20+
21+
$this->assertSame(['key' => 'value'], $mergeProp());
22+
}
23+
24+
public function test_can_resolve_bindings_when_invoked(): void
25+
{
26+
$mergeProp = (new MergeProp(fn (Request $request) => $request))->deepMerge();
27+
28+
$this->assertInstanceOf(Request::class, $mergeProp());
29+
}
30+
}

tests/ResponseFactoryTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,16 @@ public function test_can_create_merged_prop(): void
277277
$this->assertInstanceOf(MergeProp::class, $mergedProp);
278278
}
279279

280+
public function test_can_create_deep_merged_prop(): void
281+
{
282+
$factory = new ResponseFactory;
283+
$mergedProp = $factory->deepMerge(function () {
284+
return 'A merged value';
285+
});
286+
287+
$this->assertInstanceOf(MergeProp::class, $mergedProp);
288+
}
289+
280290
public function test_can_create_deferred_and_merged_prop(): void
281291
{
282292
$factory = new ResponseFactory;
@@ -287,6 +297,16 @@ public function test_can_create_deferred_and_merged_prop(): void
287297
$this->assertInstanceOf(DeferProp::class, $deferredProp);
288298
}
289299

300+
public function test_can_create_deferred_and_deep_merged_prop(): void
301+
{
302+
$factory = new ResponseFactory;
303+
$deferredProp = $factory->defer(function () {
304+
return 'A deferred + merged value';
305+
})->deepMerge();
306+
307+
$this->assertInstanceOf(DeferProp::class, $deferredProp);
308+
}
309+
290310
public function test_can_create_optional_prop(): void
291311
{
292312
$factory = new ResponseFactory;

tests/ResponseTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,41 @@ public function test_server_response_with_merge_props(): void
165165
$this->assertSame('<div id="app" data-page="{&quot;component&quot;:&quot;User\/Edit&quot;,&quot;props&quot;:{&quot;user&quot;:{&quot;name&quot;:&quot;Jonathan&quot;},&quot;foo&quot;:&quot;foo value&quot;,&quot;bar&quot;:&quot;bar value&quot;},&quot;url&quot;:&quot;\/user\/123&quot;,&quot;version&quot;:&quot;123&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false,&quot;mergeProps&quot;:[&quot;foo&quot;,&quot;bar&quot;]}"></div>', $view->render());
166166
}
167167

168+
public function test_server_response_with_deep_merge_props(): void
169+
{
170+
$request = Request::create('/user/123', 'GET');
171+
172+
$user = ['name' => 'Jonathan'];
173+
$response = new Response(
174+
'User/Edit',
175+
[
176+
'user' => $user,
177+
'foo' => (new MergeProp('foo value'))->deepMerge(),
178+
'bar' => (new MergeProp('bar value'))->deepMerge(),
179+
],
180+
'app',
181+
'123'
182+
);
183+
$response = $response->toResponse($request);
184+
$view = $response->getOriginalContent();
185+
$page = $view->getData()['page'];
186+
187+
$this->assertInstanceOf(BaseResponse::class, $response);
188+
$this->assertInstanceOf(View::class, $view);
189+
190+
$this->assertSame('User/Edit', $page['component']);
191+
$this->assertSame('Jonathan', $page['props']['user']['name']);
192+
$this->assertSame('/user/123', $page['url']);
193+
$this->assertSame('123', $page['version']);
194+
$this->assertSame([
195+
'foo',
196+
'bar',
197+
], $page['deepMergeProps']);
198+
$this->assertFalse($page['clearHistory']);
199+
$this->assertFalse($page['encryptHistory']);
200+
$this->assertSame('<div id="app" data-page="{&quot;component&quot;:&quot;User\/Edit&quot;,&quot;props&quot;:{&quot;user&quot;:{&quot;name&quot;:&quot;Jonathan&quot;},&quot;foo&quot;:&quot;foo value&quot;,&quot;bar&quot;:&quot;bar value&quot;},&quot;url&quot;:&quot;\/user\/123&quot;,&quot;version&quot;:&quot;123&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false,&quot;deepMergeProps&quot;:[&quot;foo&quot;,&quot;bar&quot;]}"></div>', $view->render());
201+
}
202+
168203
public function test_server_response_with_defer_and_merge_props(): void
169204
{
170205
$request = Request::create('/user/123', 'GET');
@@ -205,6 +240,46 @@ public function test_server_response_with_defer_and_merge_props(): void
205240
$this->assertSame('<div id="app" data-page="{&quot;component&quot;:&quot;User\/Edit&quot;,&quot;props&quot;:{&quot;user&quot;:{&quot;name&quot;:&quot;Jonathan&quot;},&quot;bar&quot;:&quot;bar value&quot;},&quot;url&quot;:&quot;\/user\/123&quot;,&quot;version&quot;:&quot;123&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false,&quot;mergeProps&quot;:[&quot;foo&quot;,&quot;bar&quot;],&quot;deferredProps&quot;:{&quot;default&quot;:[&quot;foo&quot;]}}"></div>', $view->render());
206241
}
207242

243+
public function test_server_response_with_defer_and_deep_merge_props(): void
244+
{
245+
$request = Request::create('/user/123', 'GET');
246+
247+
$user = ['name' => 'Jonathan'];
248+
$response = new Response(
249+
'User/Edit',
250+
[
251+
'user' => $user,
252+
'foo' => (new DeferProp(function () {
253+
return 'foo value';
254+
}, 'default'))->deepMerge(),
255+
'bar' => (new MergeProp('bar value'))->deepMerge(),
256+
],
257+
'app',
258+
'123'
259+
);
260+
$response = $response->toResponse($request);
261+
$view = $response->getOriginalContent();
262+
$page = $view->getData()['page'];
263+
264+
$this->assertInstanceOf(BaseResponse::class, $response);
265+
$this->assertInstanceOf(View::class, $view);
266+
267+
$this->assertSame('User/Edit', $page['component']);
268+
$this->assertSame('Jonathan', $page['props']['user']['name']);
269+
$this->assertSame('/user/123', $page['url']);
270+
$this->assertSame('123', $page['version']);
271+
$this->assertSame([
272+
'default' => ['foo'],
273+
], $page['deferredProps']);
274+
$this->assertSame([
275+
'foo',
276+
'bar',
277+
], $page['deepMergeProps']);
278+
$this->assertFalse($page['clearHistory']);
279+
$this->assertFalse($page['encryptHistory']);
280+
$this->assertSame('<div id="app" data-page="{&quot;component&quot;:&quot;User\/Edit&quot;,&quot;props&quot;:{&quot;user&quot;:{&quot;name&quot;:&quot;Jonathan&quot;},&quot;bar&quot;:&quot;bar value&quot;},&quot;url&quot;:&quot;\/user\/123&quot;,&quot;version&quot;:&quot;123&quot;,&quot;clearHistory&quot;:false,&quot;encryptHistory&quot;:false,&quot;deepMergeProps&quot;:[&quot;foo&quot;,&quot;bar&quot;],&quot;deferredProps&quot;:{&quot;default&quot;:[&quot;foo&quot;]}}"></div>', $view->render());
281+
}
282+
208283
public function test_xhr_response(): void
209284
{
210285
$request = Request::create('/user/123', 'GET');

tests/ServiceProviderTest.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,13 @@ public function test_route_macro_is_registered(): void
3030
$routes = Route::getRoutes();
3131

3232
$this->assertNotEmpty($routes->getRoutes());
33-
$this->assertEquals($route, $routes->getRoutes()[0]);
34-
$this->assertEquals(['GET', 'HEAD'], $route->methods);
35-
$this->assertEquals('/', $route->uri);
36-
$this->assertEquals(['uses' => '\Inertia\Controller@__invoke', 'controller' => '\Inertia\Controller'], $route->action);
37-
$this->assertEquals(['component' => 'User/Edit', 'props' => ['user' => ['name' => 'Jonathan']]], $route->defaults);
33+
34+
$inertiaRoute = collect($routes->getRoutes())->first(fn ($route) => $route->uri === '/');
35+
36+
$this->assertEquals($route, $inertiaRoute);
37+
$this->assertEquals(['GET', 'HEAD'], $inertiaRoute->methods);
38+
$this->assertEquals('/', $inertiaRoute->uri);
39+
$this->assertEquals(['uses' => '\Inertia\Controller@__invoke', 'controller' => '\Inertia\Controller'], $inertiaRoute->action);
40+
$this->assertEquals(['component' => 'User/Edit', 'props' => ['user' => ['name' => 'Jonathan']]], $inertiaRoute->defaults);
3841
}
3942
}

0 commit comments

Comments
 (0)