Route Cache
Without a cache, every boot registers the routes and compiles their patterns into the router — under PHP-FPM, on every request. Set routesCachePath to keep the built router in a PHP file instead:
<?php
use Solo\Application\Application;
use Solo\Application\Config;
$app = new Application(new Config(
basePath: $basePath,
routesPath: $basePath . '/app/routes.php',
providers: require $basePath . '/app/providers.php',
middleware: require $basePath . '/app/middleware.php',
routesCachePath: $basePath . '/var/cache/routes.php',
));The first boot runs routes.php, builds the router and writes the file, creating its directory if needed. Later boots load the router from the file — from OPcache when it is enabled — without running routes.php or even looking for it. Solo Router measures loading a cached router at under 2 µs, against 1.2–2.8 ms to build 1,000 routes; see its performance page.
Rules
- The file is not compared with
routes.php: delete it whenever the routes change, for example in your deploy script, and leaveroutesCachePathunset during development. - Handlers, middleware and attributes have to be plain values: strings, numbers, booleans,
null, arrays and enums, such as[UserController::class, 'show']orAuthMiddleware::class. A closure or an object makes the constructor throwLogicExceptionnaming the route, and no file is written. - Keep the file outside the web root, in a directory only the application can write: it is executed as PHP.
- Use an absolute path: a relative one is resolved against the working directory, which differs between PHP-FPM and the CLI, so each would keep a file of its own.
- Keep
routes.phpto route definitions: it runs only when the file is written, so anything else it does is skipped on later boots.
<?php
use Solo\Router\RouteCollector;
return function (RouteCollector $r): void {
$r->get('/users/{id}', [UserController::class, 'show']); // Cacheable
$r->get('/health', HealthCheck::class)->middleware('auth'); // Cacheable
$r->get('/ping', fn($request, $response) => $response); // LogicException
$r->get('/admin', AdminController::class)
->attribute('policy', new AdminPolicy()); // LogicException
};If the file cannot be written, the constructor throws RuntimeException.
Clearing the cache
Delete the file when you deploy new routes:
rm -f var/cache/routes.phpThe next request builds the router and writes the file again. Deleting is enough even when OPcache runs with opcache.validate_timestamps=0: the request that writes the new file also invalidates it in OPcache.
From PHP, Solo\Router\RouteCache deletes the file and invalidates it in the OPcache of the current process:
<?php
use Solo\Router\RouteCache;
(new RouteCache('/var/www/app/var/cache/routes.php'))->clear();Writing the cache on deploy
The first request after a deploy pays for building the router. To write the file ahead of time, construct the application once from a deploy script, with the same configuration as the front controller:
<?php
// bin/warm-route-cache.php
use Solo\Application\Application;
use Solo\Router\RouteCache;
require __DIR__ . '/../vendor/autoload.php';
$config = require __DIR__ . '/../app/config.php'; // returns the Config the front controller uses
(new RouteCache($config->routesCachePath))->clear();
new Application($config); // runs routes.php and writes the cache file- The CLI cannot invalidate PHP-FPM's OPcache. With
opcache.validate_timestamps=0, reload PHP-FPM after writing the file, as you do after a code change, or PHP-FPM keeps serving the old routes. - The file must be readable by the PHP-FPM user.
- OPcache does not store a file modified less than
opcache.file_update_protectionseconds ago, 2 by default: see concurrency and OPcache in the Solo Router documentation.
OPcache
Enable OPcache in production. Without it, PHP parses the cache file on every request, which is still faster than building the router but much slower than loading it from OPcache.
See the Solo Router cache guide for the file format, atomic writes and storing the router elsewhere.