diff --git a/src/ProvidesInertiaProperties.php b/src/ProvidesInertiaProperties.php new file mode 100644 index 00000000..15e0cc01 --- /dev/null +++ b/src/ProvidesInertiaProperties.php @@ -0,0 +1,8 @@ +props[] = $key; + } elseif (is_array($key)) { $this->props = array_merge($this->props, $key); } else { $this->props[$key] = $value; @@ -140,6 +142,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); @@ -148,13 +151,37 @@ public function resolveProperties(Request $request, array $props): array return $props; } + /** + * Resolve the ProvidesInertiaProperties 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 ProvidesInertiaProperties) { + // Pipe into a Collection to leverage Collection::getArrayableItems() + $newProps = array_merge( + $newProps, + collect($value->toInertiaProperties($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 d6ed86b2..0278f24f 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -170,7 +170,7 @@ protected function findComponentOrFail(string $component): void } /** - * @param array|Arrayable $props + * @param array|Arrayable|ProvidesInertiaProperties $props */ public function render(string $component, $props = []): Response { @@ -180,6 +180,9 @@ public function render(string $component, $props = []): Response if ($props instanceof Arrayable) { $props = $props->toArray(); + } elseif ($props instanceof ProvidesInertiaProperties) { + // Will be resolved in Response::resolveResponsableProperties() + $props = [$props]; } return new Response( diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 52970279..acf9de8c 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -18,6 +18,8 @@ use Inertia\LazyProp; use Inertia\MergeProp; use Inertia\OptionalProp; +use Inertia\ProvidesInertiaProperties; +use Inertia\RenderContext; use Inertia\ResponseFactory; use Inertia\Tests\Stubs\ExampleMiddleware; @@ -378,6 +380,30 @@ public function toArray() ]); } + public function test_will_accept_instances_of_provides_inertia_props() + { + Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + return Inertia::render('User/Edit', new class implements ProvidesInertiaProperties + { + public function toInertiaProperties(RenderContext $context): iterable + { + return [ + 'foo' => 'bar', + ]; + } + }); + }); + + $response = $this->withoutExceptionHandling()->get('/', ['X-Inertia' => 'true']); + $response->assertSuccessful(); + $response->assertJson([ + 'component' => 'User/Edit', + 'props' => [ + 'foo' => 'bar', + ], + ]); + } + public function test_will_throw_exception_if_component_does_not_exist_when_ensuring_is_enabled(): void { config()->set('inertia.ensure_pages_exist', true); diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index c10c6dc8..7db3af05 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -17,6 +17,8 @@ use Inertia\Inertia; use Inertia\LazyProp; use Inertia\MergeProp; +use Inertia\ProvidesInertiaProperties; +use Inertia\RenderContext; use Inertia\Response; use Inertia\Tests\Stubs\FakeResource; use Inertia\Tests\Stubs\MergeWithSharedProp; @@ -822,6 +824,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 ProvidesInertiaProperties + { + public function toInertiaProperties(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'); @@ -899,6 +928,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 ProvidesInertiaProperties + { + public function toInertiaProperties(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');