From 0f4bdcfec692256a830f8d979831ee4d90a3783c Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Thu, 19 Jun 2025 15:22:20 +0200 Subject: [PATCH 1/2] Adds `ProvidesInertiaProps` interface and support in responses --- src/ProvidesInertiaProps.php | 8 ++++++ src/RenderContext.php | 15 ++++++++++ src/Response.php | 33 ++++++++++++++++++++-- src/ResponseFactory.php | 7 ++++- tests/ResponseFactoryTest.php | 28 ++++++++++++++++++ tests/ResponseTest.php | 53 +++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 src/ProvidesInertiaProps.php create mode 100644 src/RenderContext.php diff --git a/src/ProvidesInertiaProps.php b/src/ProvidesInertiaProps.php new file mode 100644 index 00000000..6147320a --- /dev/null +++ b/src/ProvidesInertiaProps.php @@ -0,0 +1,8 @@ +props[] = $key; + } elseif (is_array($key)) { $this->props = array_merge($this->props, $key); } else { $this->props[$key] = $value; @@ -131,6 +133,7 @@ public function toResponse($request) */ public function resolveProperties(Request $request, array $props): array { + $props = $this->resolveInertiaPropsProviders($props, $request); $props = $this->resolvePartialProperties($props, $request); $props = $this->resolveArrayableProperties($props, $request); $props = $this->resolveAlways($props); @@ -139,13 +142,37 @@ public function resolveProperties(Request $request, array $props): array return $props; } + /** + * Resolve the ProvidesInertiaProps props. + */ + public function resolveInertiaPropsProviders(array $props, Request $request): array + { + $newProps = []; + + $renderContext = new RenderContext($this->component, $request); + + foreach ($props as $key => $value) { + if (is_numeric($key) && $value instanceof ProvidesInertiaProps) { + // Pipe into a Collection to leverage Collection::getArrayableItems() + $newProps = array_merge( + $newProps, + collect($value->toInertiaProps($renderContext))->all() + ); + } else { + $newProps[$key] = $value; + } + } + + return $newProps; + } + /** * Resolve the `only` and `except` partial request props. */ public function resolvePartialProperties(array $props, Request $request): array { if (! $this->isPartial($request)) { - return array_filter($this->props, static function ($prop) { + return array_filter($props, static function ($prop) { return ! ($prop instanceof IgnoreFirstLoad); }); } diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index a0ba7947..99b212a2 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -151,7 +151,7 @@ public function always($value): AlwaysProp } /** - * @param array|Arrayable $props + * @param array|Arrayable|ProvidesInertiaProps $props */ public function render(string $component, $props = []): Response { @@ -159,6 +159,11 @@ public function render(string $component, $props = []): Response $props = $props->toArray(); } + if ($props instanceof ProvidesInertiaProps) { + // Will be resolved in Response::resolveResponsableProperties() + $props = [$props]; + } + return new Response( $component, array_merge($this->sharedProps, $props), diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 65e7df46..caec4486 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -17,6 +17,8 @@ use Inertia\LazyProp; use Inertia\MergeProp; use Inertia\OptionalProp; +use Inertia\ProvidesInertiaProps; +use Inertia\RenderContext; use Inertia\ResponseFactory; use Inertia\Tests\Stubs\ExampleMiddleware; @@ -352,4 +354,30 @@ public function toArray() ], ]); } + + public function test_will_accept_instances_of_provides_inertia_props() + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + Inertia::share('foo', 'bar'); + + return Inertia::render('User/Edit', new class implements ProvidesInertiaProps + { + public function toInertiaProps(RenderContext $context): iterable + { + return [ + 'foo' => 'bar', + ]; + } + }); + }); + + $response = $this->withoutExceptionHandling()->get('/', ['X-Inertia' => 'true']); + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'props' => [ + 'foo' => 'bar', + ], + ]); + } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 316cd74a..09ca5df0 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -17,6 +17,8 @@ use Inertia\Inertia; use Inertia\LazyProp; use Inertia\MergeProp; +use Inertia\ProvidesInertiaProps; +use Inertia\RenderContext; use Inertia\Response; use Inertia\Tests\Stubs\FakeResource; use Inertia\Tests\Stubs\MergeWithSharedProp; @@ -759,6 +761,33 @@ public function test_always_props_are_included_on_partial_reload(): void $this->assertFalse(isset($page->props->user)); } + public function test_inertia_responsable_objects(): void + { + $request = Request::create('/user/123', 'GET'); + + $response = new Response('User/Edit', [ + 'foo' => 'bar', + new class implements ProvidesInertiaProps + { + public function toInertiaProps(RenderContext $context): iterable + { + return collect([ + 'baz' => 'qux', + ]); + } + }, + 'quux' => 'corge', + + ], 'app', '123'); + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertSame('bar', $page['props']['foo']); + $this->assertSame('qux', $page['props']['baz']); + $this->assertSame('corge', $page['props']['quux']); + } + public function test_inertia_response_type_prop(): void { $request = Request::create('/user/123', 'GET'); @@ -836,6 +865,30 @@ public function test_nested_dot_props_do_not_get_unpacked(): void $this->assertFalse(array_key_exists('can', $auth)); } + public function test_props_can_be_added_using_the_with_method(): void + { + $request = Request::create('/user/123', 'GET'); + $response = new Response('User/Edit', [], 'app', '123'); + + $response->with(['foo' => 'bar', 'baz' => 'qux']) + ->with(['quux' => 'corge']) + ->with(new class implements ProvidesInertiaProps + { + public function toInertiaProps(RenderContext $context): iterable + { + return collect(['grault' => 'garply']); + } + }); + + $response = $response->toResponse($request); + $view = $response->getOriginalContent(); + $page = $view->getData()['page']; + + $this->assertSame('bar', $page['props']['foo']); + $this->assertSame('qux', $page['props']['baz']); + $this->assertSame('corge', $page['props']['quux']); + } + public function test_responsable_with_invalid_key(): void { $request = Request::create('/user/123', 'GET'); From c8ea24bda809fe26ec8d9651afdc1e4ebcd0e854 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Thu, 19 Jun 2025 16:08:43 +0200 Subject: [PATCH 2/2] Minor cleanup --- src/ResponseFactory.php | 4 +--- tests/ResponseFactoryTest.php | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 99b212a2..4b263686 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -157,9 +157,7 @@ public function render(string $component, $props = []): Response { if ($props instanceof Arrayable) { $props = $props->toArray(); - } - - if ($props instanceof ProvidesInertiaProps) { + } elseif ($props instanceof ProvidesInertiaProps) { // Will be resolved in Response::resolveResponsableProperties() $props = [$props]; } diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index caec4486..1dbb8cab 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -358,8 +358,6 @@ public function toArray() public function test_will_accept_instances_of_provides_inertia_props() { Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { - Inertia::share('foo', 'bar'); - return Inertia::render('User/Edit', new class implements ProvidesInertiaProps { public function toInertiaProps(RenderContext $context): iterable