Skip to content

Commit

Permalink
Updated caching router to accept a custom cache key
Browse files Browse the repository at this point in the history
  • Loading branch information
philipobenito committed Nov 9, 2024
1 parent 200314a commit 358dec6
Show file tree
Hide file tree
Showing 10 changed files with 1,417 additions and 6 deletions.
76 changes: 76 additions & 0 deletions docs/6.x/cached-router.md
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.
269 changes: 269 additions & 0 deletions docs/6.x/controllers.md
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/).
Loading

0 comments on commit 358dec6

Please sign in to comment.