Skip to content

Commit 48bd3ea

Browse files
committed
Add upgrade guide and document breaking changes for version 7.0; include new RouterInterface and cached router redesign
1 parent 92dc30c commit 48bd3ea

10 files changed

Lines changed: 220 additions & 98 deletions

File tree

docs/_data/releases.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Middleware: '/unstable/middleware/'
1818
Cached Router: '/unstable/cached-router/'
1919
Dependency Injection: '/unstable/dependency-injection/'
20+
Upgrade Guide: '/unstable/upgrade/'
2021
-
2122
default: true
2223
version: 6.x

docs/unstable/cached-router.md

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ This design ensures:
3131

3232
- Fast startup times after the first request
3333
- Reliable cache invalidation when routes change
34-
- No serialisation of your entire application or controller instances
35-
- Support for closures and container-resolved handlers
34+
- Only plain PHP arrays (the compiled FastRoute route data) are serialised, not the entire Router object or any controller instances
35+
- Support for closures and container-resolved handlers, since closure-based controllers are never serialised
3636
- Automatic recovery from corruption
3737

3838
## Usage
@@ -45,7 +45,7 @@ Using the cached router is very similar to the standard router, but you pass a b
4545
use Psr\Http\Message\ResponseInterface;
4646
use Psr\Http\Message\ServerRequestInterface;
4747

48-
$cacheStore = new League\Route\Cache\FileCache('/path/to/cache/file.cache', $ttl = 86400);
48+
$cacheStore = new League\Route\Cache\FileCache('/path/to/cache/file.cache', 86400);
4949

5050
$cachedRouter = new League\Route\Cache\Router(
5151
function (League\Route\Router $router): League\Route\Router {
@@ -87,7 +87,7 @@ $cachedRouter = new League\Route\Cache\Router(
8787
~~~
8888

8989
- `cacheEnabled: bool` (default: true) - Set to false to disable caching temporarily
90-
- `cacheKey: string` (default: 'route') - Custom cache key for multiple router instances
90+
- `cacheKey: string` (default: 'league/route/cache') - Custom cache key for multiple router instances
9191

9292
## Cache Stores
9393

@@ -102,31 +102,17 @@ Route includes a `League\Route\Cache\FileCache` implementation that stores the c
102102

103103
$cache = new League\Route\Cache\FileCache(
104104
'/path/to/cache/file.cache',
105-
$ttl = 86400 // Time-to-live in seconds (optional, default: 86400)
105+
86400
106106
);
107107

108108
$cachedRouter = new League\Route\Cache\Router($builder, $cache);
109109
~~~
110110

111-
The FileCache requires a writable directory and will automatically create the cache file.
111+
The FileCache requires a writable directory and will automatically create the cache file. The second argument is the TTL in seconds.
112112

113113
### PSR-16 Compatible Stores
114114

115-
You can use any PSR-16 simple cache implementation, such as:
116-
117-
- Redis (via redis-adapter/cache)
118-
- Memcached
119-
- APCu
120-
- Any custom implementation
121-
122-
~~~php
123-
<?php declare(strict_types=1);
124-
125-
// Example with league/container's PSR-16 adapter
126-
$cache = new SomeRedisCache();
127-
128-
$cachedRouter = new League\Route\Cache\Router($builder, $cache);
129-
~~~
115+
Any PSR-16 compatible cache implementation will work. Browse available implementations at [Packagist](https://packagist.org/providers/psr/simple-cache-implementation).
130116

131117
## Cache Invalidation
132118

@@ -141,13 +127,21 @@ This means you don't need to manually clear the cache when routes change. It hap
141127

142128
### Manual Cache Clearing
143129

144-
If you need to manually clear the cache (for example, during deployment or testing), you can delete the cache file or use your cache store's `clear()` method:
130+
If you need to manually clear the cache (for example, during deployment or testing), you can use your cache store's `delete()` or `clear()` method:
145131

146132
~~~php
147-
<?php declare(straight_types=1);
133+
<?php declare(strict_types=1);
148134

149-
$cache->delete('route'); // Clear the default cache key
150-
$cache->delete('my-custom-key'); // Clear a custom cache key
135+
$cache->clear();
136+
~~~
137+
138+
If you are using a PSR-16 store with named keys, use `delete()` with the matching key:
139+
140+
~~~php
141+
<?php declare(strict_types=1);
142+
143+
$cache->delete('league/route/cache');
144+
$cache->delete('my-custom-key');
151145
~~~
152146

153147
### Handling Corruption
@@ -161,28 +155,25 @@ Both the standard `Router` and the `Cache\Router` implement the new `RouterInter
161155
Type-hint against `RouterInterface` in your dependency injection container:
162156

163157
~~~php
164-
<?php declare(straight_types=1);
158+
<?php declare(strict_types=1);
165159

166160
use League\Route\RouterInterface;
167161

168162
$container = new League\Container\Container;
169163

170-
// Use the standard router
171164
$container->add(
172165
RouterInterface::class,
173166
League\Route\Router::class
174167
);
175168

176-
// Or use the cached router instead
177169
$container->add(
178170
RouterInterface::class,
179171
function (): RouterInterface {
180172
return new League\Route\Cache\Router(
181173
function (League\Route\Router $router): League\Route\Router {
182-
// Register your routes here
183174
return $router;
184175
},
185-
new League\Route\Cache\FileCache('/tmp/route.cache')
176+
new League\Route\Cache\FileCache('/tmp/route.cache', 86400)
186177
);
187178
}
188179
);
@@ -191,7 +182,7 @@ $container->add(
191182
This enables you to switch between routers based on environment or configuration:
192183

193184
~~~php
194-
<?php declare(straight_types=1);
185+
<?php declare(strict_types=1);
195186

196187
$cacheEnabled = $_ENV['ROUTE_CACHE'] ?? true;
197188

docs/unstable/dependency-injection.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ sections:
55
Introduction: introduction
66
Recommended Reading: recommended-reading
77
Using a Container: using-a-container
8+
Binding RouterInterface: binding-routerinterface
89
---
910
## Introduction
1011

@@ -61,3 +62,46 @@ $router = (new League\Route\Router)->setStrategy($strategy);
6162

6263
$router->map('GET', '/', Acme\SomeController::class);
6364
~~~
65+
66+
## Binding RouterInterface
67+
68+
Both `League\Route\Router` and `League\Route\Cache\Router` implement `League\Route\RouterInterface`. You can bind this interface in your container so that any service type-hinting against `RouterInterface` will receive the correct implementation:
69+
70+
~~~php
71+
<?php declare(strict_types=1);
72+
73+
use League\Route\RouterInterface;
74+
75+
$container = new League\Container\Container;
76+
77+
$container->add(RouterInterface::class, function () use ($container): RouterInterface {
78+
$strategy = (new League\Route\Strategy\ApplicationStrategy)->setContainer($container);
79+
$router = (new League\Route\Router)->setStrategy($strategy);
80+
81+
$router->map('GET', '/', Acme\SomeController::class);
82+
83+
return $router;
84+
});
85+
~~~
86+
87+
This allows you to swap in the cached router for production without changing any code that depends on `RouterInterface`:
88+
89+
~~~php
90+
<?php declare(strict_types=1);
91+
92+
use League\Route\RouterInterface;
93+
94+
$container->add(RouterInterface::class, function () use ($container): RouterInterface {
95+
$builder = function (League\Route\Router $router) use ($container): League\Route\Router {
96+
$strategy = (new League\Route\Strategy\ApplicationStrategy)->setContainer($container);
97+
$router->setStrategy($strategy);
98+
$router->map('GET', '/', Acme\SomeController::class);
99+
return $router;
100+
};
101+
102+
return new League\Route\Cache\Router(
103+
$builder,
104+
new League\Route\Cache\FileCache('/tmp/route.cache', 86400)
105+
);
106+
});
107+
~~~

docs/unstable/http.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,25 @@ See more about controllers [here](/unstable/controllers).
6666

6767
Route does not provide any functionality for dealing with globals such as `$_GET`, `$_POST` etc, this is all handled by your [PSR-7](https://www.php-fig.org/psr/psr-7/) implementation, please refer to that documentation for details on how to interact with input on the request object.
6868

69+
### Route Attributes
70+
71+
When a route is matched, Route sets the route variables (wildcard segments and defaults from `setVars()`) as PSR-7 request attributes. This means you can retrieve them either from the `$args` array passed to your controller or directly from the request:
72+
73+
~~~php
74+
<?php declare(strict_types=1);
75+
76+
use Psr\Http\Message\ResponseInterface;
77+
use Psr\Http\Message\ServerRequestInterface;
78+
79+
$router = new League\Route\Router;
80+
81+
$router->map('GET', '/user/{id}', function (ServerRequestInterface $request, array $args): ResponseInterface {
82+
$idFromArgs = $args['id'];
83+
$idFromRequest = $request->getAttribute('id');
84+
// ...
85+
});
86+
~~~
87+
6988
## The Response
7089

7190
Because Route is built around PSR-15, this means that middleware and controllers are handled in a [single pass](https://www.php-fig.org/psr/psr-15/meta/#52-single-pass-lambda) approach. What this means in practice is that all middleware is passed a request object but is expected to build and return its own response or pass off to the next middleware in the stack for that to create one. Any controller that is dispatched via Route is wrapped in a middleware that adheres to this.

docs/unstable/index.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ sections:
1010
[![Author](https://img.shields.io/badge/author-@philipobenito-blue.svg?style=flat-square)](https://twitter.com/philipobenito)
1111
[![Latest Version](https://img.shields.io/github/release/thephpleague/route.svg?style=flat-square)](https://github.com/thephpleague/route/releases)
1212
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://github.com/thephpleague/route/blob/master/LICENSE.md)
13-
[![Build Status](https://img.shields.io/travis/thephpleague/route/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/route)
1413
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/route.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/route/code-structure)
1514
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/route.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/route)
1615
[![Total Downloads](https://img.shields.io/packagist/dt/league/route.svg?style=flat-square)](https://packagist.org/packages/league/route)
@@ -61,3 +60,7 @@ Most modern frameworks will include Composer out of the box, but ensure the foll
6160

6261
require 'vendor/autoload.php';
6362
~~~
63+
64+
## Upgrading
65+
66+
If you are upgrading from a previous version of Route, see the [upgrade guide](/unstable/upgrade).

docs/unstable/middleware.md

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ sections:
55
Introduction: introduction
66
Example Middleware: example-middleware
77
Defining Middleware: defining-middleware
8+
Lazy Middleware: lazy-middleware
89
Middleware Order: middleware-order
910
Route as a Middleware: route-as-a-middleware
1011
---
@@ -35,18 +36,10 @@ class AuthMiddleware implements MiddlewareInterface
3536
{
3637
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
3738
{
38-
// determine authentication and/or authorisation
39-
// ...
40-
41-
// if user has auth, use the request handler to continue to the next
42-
// middleware and ultimately reach your route callable
4339
if ($auth === true) {
4440
return $handler->handle($request);
4541
}
4642

47-
// if user does not have auth, possibly return a redirect response,
48-
// this will not continue to any further middleware and will never
49-
// reach your route callable
5043
return new RedirectResponse(/* .. */);
5144
}
5245
}
@@ -99,6 +92,34 @@ $router
9992
;
10093
~~~
10194

95+
## Lazy Middleware
96+
97+
If you are using a PSR-11 dependency injection container, you can register middleware by class name using `lazyMiddleware()`. The middleware will be resolved from the container (or instantiated directly) at dispatch time, rather than upfront:
98+
99+
~~~php
100+
<?php declare(strict_types=1);
101+
102+
$router = new League\Route\Router;
103+
104+
$router->lazyMiddleware(Acme\AuthMiddleware::class);
105+
106+
// Or add multiple at once
107+
$router->lazyMiddlewares([
108+
Acme\AuthMiddleware::class,
109+
Acme\LoggingMiddleware::class,
110+
]);
111+
~~~
112+
113+
You can also prepend a lazy middleware to the front of the stack:
114+
115+
~~~php
116+
<?php declare(strict_types=1);
117+
118+
$router->lazyPrependMiddleware(Acme\AuthMiddleware::class);
119+
~~~
120+
121+
These lazy variants are available on the router, route groups, and individual routes, mirroring the eager `middleware()` methods.
122+
102123
## Middleware Order
103124

104125
Middleware is invoked in a specific order but depending on the logic contained in a middleware, you can control whether your code is run before or after your controller is invoked.
@@ -126,17 +147,14 @@ class SomeMiddleware implements MiddlewareInterface
126147
{
127148
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
128149
{
129-
// invoke the rest of the middleware stack and your controller resulting
130-
// in a returned response object
131150
$response = $handler->handle($request);
132151

133152
// ...
134-
// do something with the response
135153
return $response;
136154
}
137155
}
138156
~~~
139157

140158
## Route as a Middleware
141159

142-
League\Route is itself a Request Handler, so an instance of `League\Route\Router` can be added to any existing middleware stack.
160+
`League\Route\Router` implements `League\Route\RouterInterface`, which extends PSR-15's `RequestHandlerInterface`. This means an instance of `League\Route\Router` can be added to any existing middleware stack as a request handler.

0 commit comments

Comments
 (0)