diff --git a/src/FirstLoad.php b/src/FirstLoad.php new file mode 100644 index 00000000..a1a1eac9 --- /dev/null +++ b/src/FirstLoad.php @@ -0,0 +1,8 @@ +callback = $callback; + } + + public function __invoke() + { + return App::call($this->callback); + } +} diff --git a/src/Response.php b/src/Response.php index b5f8ec82..3228043f 100644 --- a/src/Response.php +++ b/src/Response.php @@ -47,7 +47,7 @@ public function __construct( string $rootView = 'app', string $version = '', bool $encryptHistory = false, - ?Closure $urlResolver = null + ?Closure $urlResolver = null, ) { $this->component = $component; $this->props = $props instanceof Arrayable ? $props->toArray() : $props; @@ -126,6 +126,7 @@ public function toResponse($request) $this->resolveMergeProps($request), $this->resolveDeferredProps($request), $this->resolveCacheDirections($request), + $this->resolveInitialProps($request), ); if ($request->header(Header::INERTIA)) { @@ -141,6 +142,7 @@ public function toResponse($request) public function resolveProperties(Request $request, array $props): array { $props = $this->resolvePartialProperties($props, $request); + $props = $this->filterInitialProps($props); $props = $this->resolveArrayableProperties($props, $request); $props = $this->resolveAlways($props); $props = $this->resolvePropertyInstances($props, $request); @@ -296,6 +298,14 @@ public function resolvePropertyInstances(array $props, Request $request): array return $props; } + /** + * Filter initial properties from the props. + */ + public function filterInitialProps(array $props): array + { + return array_filter($props, static fn ($prop) => ! $prop instanceof InitialProp); + } + /** * Resolve the cache directions for the response. */ @@ -376,6 +386,19 @@ public function resolveDeferredProps(Request $request): array return $deferredProps->isNotEmpty() ? ['deferredProps' => $deferredProps->toArray()] : []; } + public function resolveInitialProps(Request $request): array + { + if ($request->header(Header::INERTIA)) { + return []; + } + + $initialProps = collect($this->props) + ->filter(fn ($prop) => $prop instanceof InitialProp) + ->mapWithKeys(fn ($value, $key) => [$key => App::call($value)]); + + return $initialProps->isNotEmpty() ? ['initialProps' => $initialProps->toArray()] : []; + } + /** * Determine if the request is a partial request. */ diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index d6ed86b2..3d609f35 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -35,9 +35,8 @@ class ResponseFactory /** @var Closure|null */ protected $urlResolver; - /*** - * @param string $name The name of the root view - * @return void + /** + * @param string $name The name of the root view */ public function setRootView(string $name): void { @@ -128,6 +127,11 @@ public function optional(callable $callback): OptionalProp return new OptionalProp($callback); } + public function initial(callable $callback): InitialProp + { + return new InitialProp($callback); + } + public function defer(callable $callback, string $group = 'default'): DeferProp { return new DeferProp($callback, $group); diff --git a/tests/InitialPropTest.php b/tests/InitialPropTest.php new file mode 100644 index 00000000..2dbdabfd --- /dev/null +++ b/tests/InitialPropTest.php @@ -0,0 +1,73 @@ + Inertia::initial(fn () => true), + 'appName' => Inertia::initial(fn () => 'test'), + ]); + + $this->prepareMockEndpoint(); + + $response = $this->withoutExceptionHandling()->get('/'); + + $response->assertSuccessful(); + $this->assertSame( + '
', + $response->content(), + ); + } + + public function test_initial_props_are_not_accessible() + { + Inertia::share([ + 'initial' => Inertia::initial(fn () => true), + 'appName' => Inertia::initial(fn () => 'test'), + ]); + + $this->prepareMockEndpoint(); + + $response = $this->withoutExceptionHandling()->get('/', [ + 'X-Inertia' => 'true', + ]); + + $response->assertSuccessful(); + $response->assertJsonMissingPath('initialProps'); + } + + public function test_can_invoke(): void + { + $initialProp = new InitialProp(function () { + return 'A initial value'; + }); + + $this->assertSame('A initial value', $initialProp()); + } + + public function test_can_resolve_bindings_when_invoked(): void + { + $initialProp = new InitialProp(function (Request $request) { + return $request; + }); + + $this->assertInstanceOf(Request::class, $initialProp()); + } + + private function prepareMockEndpoint(): \Illuminate\Routing\Route + { + return Route::middleware([StartSession::class, ExampleMiddleware::class])->get('/', function () { + return Inertia::render('User/Edit'); + }); + } +} diff --git a/tests/ResponseFactoryTest.php b/tests/ResponseFactoryTest.php index 52970279..1048bbe6 100644 --- a/tests/ResponseFactoryTest.php +++ b/tests/ResponseFactoryTest.php @@ -15,6 +15,7 @@ use Inertia\ComponentNotFoundException; use Inertia\DeferProp; use Inertia\Inertia; +use Inertia\InitialProp; use Inertia\LazyProp; use Inertia\MergeProp; use Inertia\OptionalProp; @@ -342,6 +343,16 @@ public function test_can_create_optional_prop(): void $this->assertInstanceOf(OptionalProp::class, $optionalProp); } + public function test_can_create_initial_prop(): void + { + $factory = new ResponseFactory; + $initialProp = $factory->initial(function () { + return 'An initial value'; + }); + + $this->assertInstanceOf(InitialProp::class, $initialProp); + } + public function test_can_create_always_prop(): void { $factory = new ResponseFactory;