Download the PHP package karewan/knroute-inertia without Composer
On this page you can find all versions of the php package karewan/knroute-inertia. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package knroute-inertia
KnRoute Inertia
Fast Inertia.js v3 server-side adapter for the KnRoute PHP 8.3+ router.
It implements the whole Inertia v3 protocol: full page loads and Inertia visits, partial reloads with dot notation, always, optional, deferred, merge, once and infinite scroll props, shared data, flash data, validation errors and error bags, redirects (303, fragments, external locations), asset versioning, history encryption, Precognition, error pages and server-side rendering. The front end uses the official Inertia packages (@inertiajs/vue3, @inertiajs/react or @inertiajs/svelte).
Table of contents
- KnRoute Inertia
- Table of contents
- Installation
- Requirements
- Getting started
- Server setup
- Configure the adapter and the router
- The root template
- Configuration options
- Client setup
- Usage
- Render a page
- Props
- Nested props and dot notation
- Property providers
- Shared data
- Partial reloads
- Deferred props
- Merging props
- Infinite scroll
- Once props
- Redirects
- Flash data
- Forms and validation errors
- Precognition
- Error pages
- Asset versioning
- History encryption
- Server-side rendering
- Sessions
- CSRF protection
- Long-running workers
- Performance
- Sample application
- Formatting and linting
- Tests
- Changelog
- License
Installation
Requirements
- PHP 8.3+ with the JSON extension
- KnRoute 4.1+
- An Inertia.js v3 client adapter
Getting started
Server setup
Configure the adapter and the router
Configure the adapter once, register InertiaMiddleware as the first global middleware, and let ErrorHandler render the HTTP errors:
InertiaMiddleware is required:
- before the routing, it answers an Inertia visit made with outdated assets with a
409reload, so its action never runs; - after the action, it turns the
302redirects ofPUT,PATCHandDELETEInertia visits into303, turns a redirect to a URL with a fragment into a409the client follows, keeps the flash data and errors that no page sent for the next page, and forgets the request state.
The root template
A full page load renders the root template. When rootView is a file path, the template receives $inertia (a RootView), $page (the page object) and the view data as variables:
head(string $fallback = '')prints the head elements rendered by the SSR server, or the fallback when the page is rendered by the client.app(string $id = 'app')prints the page object in a<script data-page="app" type="application/json">element followed by the root<div id="app">, or the server-rendered markup. The JSON escapes<,>and/, so no prop value can close the script element.
rootView may also be a Closure receiving the RootView and returning the document, to use the template engine of the application:
Configuration options
All the options of Inertia::configure() are named arguments:
| Option | Default | Description |
|---|---|---|
rootView |
required | Path of the root template, or Closure(RootView): string |
version |
null |
Asset version, or a Closure called at most once per request, only when the version is needed |
share |
null |
Closure(): array returning the props shared by every page, called only when a page is rendered |
session |
NativeSessionStore |
Keeps the data of a redirect for the next page, false without session |
ssr |
null |
SsrGateway rendering the pages on the server, SSR disabled when null |
encryptHistory |
false |
Encrypt the history state of every page |
exposeSharedPropKeys |
true |
List the shared props in the page object (sharedProps), used by the instant visits |
jsonFlags |
0 |
Extra json_encode() flags of the page object, e.g. JSON_PRESERVE_ZERO_FRACTION |
reporter |
error_log() |
Closure(Throwable): void reporting the rescued deferred props and the SSR failures |
The configuration is process-wide. Every other method applies to the current request.
Client setup
Install the official Inertia v3 packages and the Vite plugin, for example with Vue:
The root template loads the entry with the Vite development server in development, and the files listed by public/build/.vite/manifest.json in production. The sample application contains a small helper doing both.
Usage
Render a page
render(string|UnitEnum $component, array|ProvidesInertiaProperties $props = [], array $viewData = [], int $status = 200) answers an Inertia visit with the page object as JSON (X-Inertia: true, Vary: X-Inertia) and a full page load with the root template. The component may be an enum case: the value of a backed enum, the name of a pure one. $viewData only reaches the root template, never the client. Like the HttpUtils output helpers, it returns control to the caller.
Inertia::page() builds the same page object without sending it.
Every prop is visible client-side: only send what the page needs.
Props
| Prop | Full visit | Partial reload | Evaluated |
|---|---|---|---|
'users' => $users |
Sent | When selected | Always |
'users' => fn() => UsersDao::all() |
Sent | When selected | Only when sent |
Inertia::always(fn() => ...) |
Sent | Always sent | Always |
Inertia::optional(fn() => ...) |
Never | When selected | Only when sent |
Inertia::defer(fn() => ..., 'group') |
Announced, loaded by a follow-up request | When selected | Only when sent |
Inertia::merge($items) |
Sent, replaced | Merged by the client | Like a value or a Closure |
Inertia::once(fn() => ...) |
Sent once, then remembered | When selected | Only when sent |
Inertia::scroll(fn() => ..., $metadata) |
Sent with its cursor | Merged by the client | Only when sent |
Closures are resolved without argument. Any other object, including a JsonSerializable, is left to json_encode(). A Closure may also return a prop type, which then behaves as if it had been given directly.
Nested props and dot notation
Closures and prop types are resolved at any depth, inside arrays and inside the arrays returned by Closures. Their metadata and the partial reloads use dot-notation paths:
Property providers
A ProvidesInertiaProperty value transforms itself, knowing its path, its sibling props and the component:
A ProvidesInertiaProperties listed without key is replaced by the props it returns, which lets pages reuse groups of props:
Shared data
Props shared by every page come from the share Closure of the configuration, called only when a page is rendered, and from Inertia::share() for the current request, for example in a middleware:
Page props take precedence over shared props. Inertia::getShared('team') returns a shared prop, unresolved. The top-level keys of the shared props are listed in the sharedProps member of the page object, so an instant visit carries them to the intermediate page.
Partial reloads
A partial reload of the same component (router.reload({ only: ['users'] }), except, <Link :only>) only resolves the selected props, the always props and the validation errors. Wrap the expensive props in Closures so the unselected ones never run.
Deferred props
Deferred props are announced by the full visit and loaded by one follow-up request per group, in parallel. With rescue: true, a failure is reported, the prop is omitted and listed in rescuedProps, so the <Deferred> component shows its rescue slot instead of failing the response.
Merging props
Merging only happens on partial reloads: a full visit always replaces the prop. A prop listed in the reset option of the client is replaced.
Infinite scroll
Inertia::scroll() sends a page of items and its cursor for the <InfiniteScroll> component. The items of the wrapper key (data by default) are appended, or prepended when the client scrolls backwards:
ScrollMetadata also accepts cursors: new ScrollMetadata($currentCursor, $previousCursor, $nextCursor, 'cursor'). The metadata may be a Closure receiving the resolved data. ->defer() loads the first page after the page is displayed.
Once props
The client remembers a once prop and sends its key in X-Inertia-Except-Once-Props: the adapter skips it on the following visits until it expires. A partial reload selecting it always resolves it.
Redirects
An Inertia visit redirected to a URL with a fragment gets a 409 with X-Inertia-Redirect, so the client keeps the fragment. back() only follows a Referer of the current host and redirects to its path: it is never an open redirect.
A redirect written with HttpUtils::location() works too: InertiaMiddleware applies the same rules to it after the action.
Flash data
Flash data is sent once with the next page, in page.flash, and never kept in the browser history: the page of this request when it is rendered, otherwise the page the redirect leads to (the data is kept in the session until then).
Forms and validation errors
Inertia does not use 422 responses for forms: the action redirects back to the form with the errors, which reach the page in the errors prop, always sent, {} when empty.
Inertia::input()returns the JSON body Inertia sends for a form without files, or$_POSTfor a multipart form.Inertia::validate(array $errors, string $bag = 'default')does nothing when$errorsis empty. Otherwise it throws aValidationException(a422HttpExceptioncarrying the errors in itserrorsextension).ErrorHandlersends them back: a redirect to the previous page for Inertia visits and page loads, a422JSON error with itserrorsmember foruseHttp()and the other JSON clients.Inertia::withErrors($errors, $bag)followed byInertia::back()does the same without exception.
A field maps to one message or to a list of messages. With the errorBag option of the client (X-Inertia-Error-Bag), the errors are sent under the bag name. Errors given with a named bag are sent under that name.
When the application renders its errors with its own handler, ErrorHandler::handleValidation(HttpError $error) handles the validation errors and returns whether it did.
Precognition
The Precognition validation of useForm().withPrecognition() and <Form> is answered by the action itself: Inertia::validate() detects the Precognition request, answers 204 or 422 with the errors of the validated fields only, and stops the request before the action stores anything. Precognition exposes the underlying helpers (isRequest(), validateOnly(), filter(), pass(), fail()).
Error pages
ErrorHandler renders the KnRoute HTTP errors (404, 405, HttpException...) as a page component for Inertia visits and page loads, and as a JSON error (HttpUtils::outputError()) for the other clients:
The page receives status, title and detail by default, with the shared props, and keeps the error status. The client renders it instead of its error modal.
Exceptions that are not HttpException still leave Router::run(): catch them in the front controller and render the 500 page there if needed, with (new ErrorHandler('Error'))->render(new HttpError(500, 'Server Error', '...')).
Asset versioning
When the version changes, the next Inertia visit gets a 409 reload of the same URL, so the browser loads the new assets. The check runs before the routing. A version derived from a file (the Vite manifest...) should be given as a Closure: it is only computed for the requests that need it.
History encryption
The route middleware #[EncryptHistory] (from Karewan\KnRouteInertia\Middlewares) encrypts the pages of a controller or an action, #[EncryptHistory(false)] opts out. Encryption needs a secure context (HTTPS or localhost).
Server-side rendering
- Production: build the SSR bundle (
vite build --ssr, the@inertiajs/viteplugin wraps the entry with the SSR server) and run it with Node (node ssr/app.js), under a process manager. - Development: point the gateway to the endpoint of the Vite plugin,
http://localhost:5173/__inertia_ssr, no separate server needed.
Only full page loads are rendered on the server, with one request for head() and app(). A failed rendering (server down, timeout, component error) is reported as an SsrException carrying the classification of the SSR server (type, component, hint, sourceLocation...), and the client renders the page: a broken SSR server never takes the site down. A reporter that rethrows it makes the failures visible in the end-to-end tests.
Inertia::disableSsr() or the route middleware #[WithoutSsr] render a request on the client only. HttpSsrGateway::isHealthy() checks the SSR server. Another renderer can implement SsrGateway.
Sessions
The flash data, the errors and the history flags of a redirect are kept in the session until the next page reads them, under the _inertia key. The default NativeSessionStore uses $_SESSION with any save handler. A read never creates a session: it only resumes the session of a client that has its cookie. A write starts the session with session_start(), or with the starter of the application:
Implement SessionStore (pull() and put()) for another storage, or pass session: false when the application never redirects with data.
CSRF protection
The Inertia client sends the XSRF-TOKEN cookie back in an X-XSRF-TOKEN header. A middleware of the application sets the cookie and checks the header on the state-changing methods:
The cookie and header names can be changed with the http option of createInertiaApp().
Long-running workers
The adapter keeps no request data in static properties beyond the request: InertiaMiddleware forgets the state at the end of every request, including the requests it stops for a version change. State set before Router::run() (a share in the front controller) belongs to the request being handled. Inertia::resetRequest() clears it explicitly.
Performance
The adapter adds a few microseconds to a request and never parses what it does not send:
- Closures and prop types that a response does not send are never called: optional and deferred props on full visits, unselected props on partial reloads, once props the client already holds.
- Plain data is traversed once, arrays are never copied when nothing changes in them, dot paths are only built where a prop type needs one, and the page object is encoded once, by
json_encode(). - The version check runs before the routing, the shared props are only resolved for pages, the SSR server is only called for full page loads, and the session is only started to write.
- No dependency beyond KnRoute, no reflection, no container.
composer bench on PHP 8.5 with OPcache, compared with json_encode() of the same plain data:
| Case | Adapter | json_encode() alone |
|---|---|---|
| Typical page, 50 rows, lazy and deferred props | 34 µs | 17 µs |
| Partial reload of 1 prop next to 10 000 lazy rows | 5 µs | 0.1 µs |
| Infinite scroll page, 20 rows | 17 µs | 7 µs |
| 10 000 rows of nested arrays | 6.1 ms | 3.5 ms |
The difference on very large data comes from the traversal looking for nested prop types. Data wrapped in a JsonSerializable object, or given as objects, is left to json_encode() without traversal.
Sample application
The samples folder contains a small application with Vue 3: shared props, deferred, optional and once props, partial reloads, forms with Precognition and validation errors, flash data, DELETE redirects, infinite scroll, history encryption, error pages, external redirects and SSR.
For SSR, run pnpm ssr and start PHP with INERTIA_SSR=1. For development with hot reload, run pnpm dev and start PHP with VITE_DEV_SERVER=http://localhost:5173 (and INERTIA_SSR=1 to render with the Vite plugin).
Formatting and linting
The JavaScript, Vue, CSS and JSON files are formatted and linted with Biome, configured by biome.json. PHP is not supported by Biome. From the root of the repository:
Tests
The test suite only requires PHP and Composer: no PHPUnit, no external server.
Every test file runs in its own PHP process. The suite covers the props resolution (full visits, partial reloads, nested props, metadata), the HTML and JSON responses, the root template escaping, SSR through a real HTTP server and its failures, the session flow of flash data and errors, redirects, Precognition, error pages and complete requests through the KnRoute router with a reused router, as in a worker.
Changelog
See CHANGELOG.md.
License
See LICENSE.txt.