diff --git a/src/Inertia.php b/src/Inertia.php index 47754ae3..b8085a1a 100644 --- a/src/Inertia.php +++ b/src/Inertia.php @@ -14,6 +14,7 @@ * @method static void version(\Closure|string|null $version) * @method static string getVersion() * @method static void resolveUrlUsing(\Closure|null $urlResolver = null) + * @method static void resolveInitialProps(\Closure|null $initialPropsResolver = null) * @method static \Inertia\OptionalProp optional(callable $callback) * @method static \Inertia\LazyProp lazy(callable $callback) * @method static \Inertia\DeferProp defer(callable $callback, string $group = 'default') diff --git a/src/Middleware.php b/src/Middleware.php index 81b6ab9d..0d8ccbd1 100644 --- a/src/Middleware.php +++ b/src/Middleware.php @@ -75,6 +75,16 @@ public function urlResolver() return null; } + /** + * Defines the initial props that are shared by default. + * + * @return \Closure|null + */ + public function initialPropsResolver() + { + return null; + } + /** * Handle the incoming request. * @@ -93,6 +103,10 @@ public function handle(Request $request, Closure $next) Inertia::resolveUrlUsing($urlResolver); } + if ($initialPropsResolver = $this->initialPropsResolver()) { + Inertia::resolveInitialPropsUsing($initialPropsResolver); + } + $response = $next($request); $response->headers->set('Vary', Header::INERTIA); diff --git a/src/Response.php b/src/Response.php index 9108020f..65ad7e59 100644 --- a/src/Response.php +++ b/src/Response.php @@ -81,6 +81,11 @@ class Response implements Responsable */ protected ?Closure $urlResolver = null; + /** + * The initial props resolver callback. + */ + protected ?Closure $initialPropsResolver = null; + /** * Create a new Inertia response instance. * @@ -92,7 +97,8 @@ public function __construct( string $rootView = 'app', string $version = '', bool $encryptHistory = false, - ?Closure $urlResolver = null + ?Closure $urlResolver = null, + ?Closure $initialPropsResolver = null, ) { $this->component = $component; $this->props = $props; @@ -101,6 +107,7 @@ public function __construct( $this->clearHistory = session()->pull('inertia.clear_history', false); $this->encryptHistory = $encryptHistory; $this->urlResolver = $urlResolver; + $this->initialPropsResolver = $initialPropsResolver; } /** @@ -188,6 +195,7 @@ public function toResponse($request) $this->resolveMergeProps($request), $this->resolveDeferredProps($request), $this->resolveCacheDirections($request), + $this->resolveInitialProps($request), ); if ($request->header(Header::INERTIA)) { @@ -508,6 +516,26 @@ public function resolveDeferredProps(Request $request): array return $deferredProps->isNotEmpty() ? ['deferredProps' => $deferredProps->toArray()] : []; } + /** + * Resolve initial props. + * + * @return array + */ + public function resolveInitialProps(Request $request): array + { + if ($request->header(Header::INERTIA)) { + return []; + } + + if ($this->initialPropsResolver === null) { + return []; + } + + $initialProps = App::call($this->initialPropsResolver, ['request' => $request]); + + return empty($initialProps) ? [] : ['initialProps' => $initialProps]; + } + /** * Determine if the request is a partial request. */ diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index d24feac9..825df0d2 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -61,6 +61,13 @@ class ResponseFactory */ protected $urlResolver; + /** + * The initial props resolver callback. + * + * @var Closure|null + */ + protected $initialPropsResolver; + /** * Set the root view template for Inertia responses. This template * serves as the HTML wrapper that contains the Inertia root element @@ -149,6 +156,14 @@ public function resolveUrlUsing(?Closure $urlResolver = null): void $this->urlResolver = $urlResolver; } + /** + * Set the initial props resolver. + */ + public function resolveInitialPropsUsing(?Closure $initialPropsResolver = null): void + { + $this->initialPropsResolver = $initialPropsResolver; + } + /** * Clear the browser history on the next visit. */ @@ -262,6 +277,7 @@ public function render(string $component, $props = []): Response $this->getVersion(), $this->encryptHistory ?? config('inertia.history.encrypt', false), $this->urlResolver, + $this->initialPropsResolver, ); } diff --git a/tests/InitialPropTest.php b/tests/InitialPropTest.php new file mode 100644 index 00000000..1dc9df25 --- /dev/null +++ b/tests/InitialPropTest.php @@ -0,0 +1,44 @@ +prepareMockEndpoint(); + + $response = $this->withoutExceptionHandling()->get('/'); + + $response->assertSuccessful(); + $this->assertSame( + '
', + $response->content(), + ); + } + + public function test_initial_props_are_not_accessible(): void + { + $this->prepareMockEndpoint(); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJsonMissingPath('initialProps'); + } + + private function prepareMockEndpoint(): \Illuminate\Routing\Route + { + return Route::middleware([StartSession::class, ExampleMiddleware::class, CustomInitialPropsResolverMiddleware::class])->get('/', function () { + return Inertia::render('User/Edit'); + }); + } +} diff --git a/tests/Stubs/CustomInitialPropsResolverMiddleware.php b/tests/Stubs/CustomInitialPropsResolverMiddleware.php new file mode 100644 index 00000000..67e0688e --- /dev/null +++ b/tests/Stubs/CustomInitialPropsResolverMiddleware.php @@ -0,0 +1,24 @@ + true, + 'appName' => 'test', + ]; + }; + } +}