-
-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Updated caching router to accept a custom cache key
- Loading branch information
1 parent
200314a
commit 358dec6
Showing
10 changed files
with
1,417 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
--- | ||
layout: post | ||
title: Cached Router (BETA) | ||
sections: | ||
Introduction: introduction | ||
BETA Notes: beta-notes | ||
To-do: to-do | ||
Usage: usage | ||
Cache Stores: cache-stores | ||
--- | ||
> **Note:** The cached router implementation is currently in BETA and is not recommended for production applications without thorough testing. | ||
## Introduction | ||
|
||
Route provides a way to improve performance on larger applications by caching a serialised, fully configured router, minimising the amount of bootstrap code that is executed on each request. | ||
|
||
## BETA Notes | ||
|
||
A cached router is essentially a fully configured router object, serialised, and stored in a simple cache store. While this works well in test scenarios, depending on the controllers you add to the router, it is actually possible that the cache will attempt to serialise your entire application and cause side effects to your code depending on any custom magic methods you may be implementing. | ||
|
||
It is recommended that when using a cached router, you lazy load your controllers. This way, they will not be instantiated/invoked until they are used. | ||
|
||
Please report any issues you via an issue on the repository. | ||
|
||
Why is this BETA feature included? To encourage higher rates of testing to make the feature as stable as possible. | ||
|
||
## To-do | ||
|
||
- ✓ Provide a way to create a router, build it and cache the resulting object. | ||
- ✓ Have cached router accept any implementation of PSR-16 simple cache interface. | ||
- ✓ Provide file based implementation. | ||
- Test test test test test. | ||
- Suggestions? Open tickets on repository. | ||
|
||
## Usage | ||
|
||
Usage of the cached router is very similar to usage of the standard route, but rather than instantiating and configuring `League\Route\Router`, you instead instantiate a cached router with a `callable` that will be used to configure the router. | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
include 'path/to/vendor/autoload.php'; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
$cacheStore = new League\Route\Cache\FileCache('/path/to/cache/file.cache', $ttl = 86400); | ||
|
||
$cachedRouter = new League\Route\Cache\Router(function (League\Route\Router $router) { | ||
// map a route | ||
$router->map('GET', '/', function (ServerRequestInterface $request): ResponseInterface { | ||
$response = new Laminas\Diactoros\Response; | ||
$response->getBody()->write('<h1>Hello, World!</h1>'); | ||
return $response; | ||
}); | ||
|
||
return $router; | ||
}, $cacheStore, cacheEnabled: true, cacheKey: 'my-router'); | ||
|
||
$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals( | ||
$_SERVER, $_GET, $_POST, $_COOKIE, $_FILES | ||
); | ||
|
||
$response = $cachedRouter->dispatch($request); | ||
|
||
// send the response to the browser | ||
(new Laminas\HttpHandlerRunner\Emitter\SapiEmitter)->emit($response); | ||
~~~ | ||
|
||
In the example above, if the file `/path/to/cache/file.cache` does not exist, the `callable` passed to the cached router will be invoked, and the returned router will be serialised and cached. | ||
|
||
On subsequent requests, the router will be resolved from the cache file instead. | ||
|
||
## Cache Stores | ||
|
||
The cached router can use any [PSR-16](https://www.php-fig.org/psr/psr-16/) simple cache implementation, serialisation happens before it is passed to be stored in the cache, so the implementation will always only use one namespaced key, with the value being the serialised router. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
--- | ||
layout: post | ||
title: Controllers | ||
sections: | ||
Introduction: introduction | ||
Defining Controllers: defining-controllers | ||
Types of Controllers: types-of-controllers | ||
Dependency Injection: dependency-injection | ||
--- | ||
## Introduction | ||
|
||
Every defined route requires a `callable` to invoke when dispatched, something that could be described as a controller in MVC. By default, Route only imposes that the callable is defined with a specific signature, it is given a request object as the first argument, an associative array of wildcard route arguments as the second argument, and expects a response object to be returned. Read more about this in [HTTP](/5.x/http). | ||
|
||
This behaviour can be changed by creating/using a different strategy, read more about strategies [here](/5.x/strategies). | ||
|
||
## Defining Controllers | ||
|
||
Defining what controller is invoked when a route is matched is as easy as padding a callable as the the third argument of the `map` method or the second argument of the proxy methods for different request verbs, `get`, `post` etc. | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/route', function (ServerRequestInterface $request): ResponseInterface { | ||
// ... | ||
}); | ||
|
||
$router->get('/another-route', function (ServerRequestInterface $request): ResponseInterface { | ||
// ... | ||
}); | ||
~~~ | ||
|
||
## Types of Controllers | ||
|
||
As mentioned above, Route will dispatch any `callable` when a route is matched. | ||
|
||
For performance reasons, Route also allows you to define controllers as a type of proxy, there are two of these proxies that will allow you to define strings and the actually callable will be built when Route dispatches it. | ||
|
||
### Closure | ||
|
||
A controller can be defined as a simple `\Closure` anonymous function. | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/', function (ServerRequestInterface $request): ResponseInterface { | ||
// ... | ||
}); | ||
~~~ | ||
|
||
### Lazy Loaded Class Method (Proxy) | ||
|
||
You can define a class method as a controller where the callable is to be lazy loaded when it is dispatched by defining a string and separating the class name and method name like so `ClassName::methodName`. | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
namespace Acme; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class SomeController | ||
{ | ||
/** | ||
* Controller. | ||
* | ||
* @param \Psr\Http\Message\ServerRequestInterface $request | ||
* | ||
* @return \Psr\Http\Message\ResponseInterface | ||
*/ | ||
public function someMethod(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
// ... | ||
} | ||
} | ||
~~~ | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/', 'Acme\SomeController::someMethod'); | ||
~~~ | ||
|
||
### Lazy Loaded Class Implementing `__invoke` (Proxy) | ||
|
||
You can define the name of a class that implements the magic `__invoke` method and the object will not be instantiated until it is dispatched. | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
namespace Acme; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class SomeController | ||
{ | ||
/** | ||
* Controller. | ||
* | ||
* @param \Psr\Http\Message\ServerRequestInterface $request | ||
* | ||
* @return \Psr\Http\Message\ResponseInterface | ||
*/ | ||
public function __invoke(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
// ... | ||
} | ||
} | ||
~~~ | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/', Acme\SomeController::class); | ||
~~~ | ||
|
||
### Lazy Loaded Array Based Callable (Proxy) | ||
|
||
A controller can be defined as an array based callable where the class element will not be instantiated until it is dispatched. | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
namespace Acme; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class SomeController | ||
{ | ||
/** | ||
* Controller. | ||
* | ||
* @param \Psr\Http\Message\ServerRequestInterface $request | ||
* | ||
* @return \Psr\Http\Message\ResponseInterface | ||
*/ | ||
public function someMethod(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
// ... | ||
} | ||
} | ||
~~~ | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/', [Acme\SomeController::class, 'someMethod']); | ||
~~~ | ||
|
||
### Object Implementing `__invoke` | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
namespace Acme; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class SomeController | ||
{ | ||
/** | ||
* Controller. | ||
* | ||
* @param \Psr\Http\Message\ServerRequestInterface $request | ||
* | ||
* @return \Psr\Http\Message\ResponseInterface | ||
*/ | ||
public function __invoke(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
// ... | ||
} | ||
} | ||
~~~ | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/', new Acme\SomeController); | ||
~~~ | ||
|
||
### Array Based Callable | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
namespace Acme; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class SomeController | ||
{ | ||
/** | ||
* Controller. | ||
* | ||
* @param \Psr\Http\Message\ServerRequestInterface $request | ||
* | ||
* @return \Psr\Http\Message\ResponseInterface | ||
*/ | ||
public function someMethod(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
// ... | ||
} | ||
} | ||
~~~ | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/', [new Acme\SomeController, 'someMethod']); | ||
~~~ | ||
|
||
### Named Function | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
namespace Acme; | ||
|
||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
/** | ||
* Controller. | ||
* | ||
* @param \Psr\Http\Message\ServerRequestInterface $request | ||
* | ||
* @return \Psr\Http\Message\ResponseInterface | ||
*/ | ||
function controller(ServerRequestInterface $request): ResponseInterface | ||
{ | ||
// ... | ||
} | ||
~~~ | ||
|
||
~~~php | ||
<?php declare(strict_types=1); | ||
|
||
$router = new League\Route\Router; | ||
|
||
$router->map('GET', '/', 'Acme\controller'); | ||
~~~ | ||
|
||
## Dependency Injection | ||
|
||
Where Route is instantiating the objects for your defined controller, a dependency injection container can be used to resolve those objects. Read more on dependency injection [here](/5.x/dependency-injection/). |
Oops, something went wrong.