Update documentation pages to use PSR-15 examples

This commit is contained in:
PJ Dietz 2018-03-13 12:21:05 -04:00
parent ac9f40be5f
commit cd5f25ba5e
8 changed files with 344 additions and 541 deletions

View File

@ -3,49 +3,13 @@ Dependency Injection
WellRESTed strives to play nicely with other code and not force developers into using any specific libraries or frameworks. As such, WellRESTed does not provide a dependency injection container, nor does it require you to use a specific container (or any). WellRESTed strives to play nicely with other code and not force developers into using any specific libraries or frameworks. As such, WellRESTed does not provide a dependency injection container, nor does it require you to use a specific container (or any).
This section describes a handful of ways of making dependencies available to middleware. This section describes the recommended way of using WellRESTed with Pimple_, a common dependency injection container for PHP.
Request Attribute Imaging we have a ``FooHandler`` that depends on a ``BarInterface``, and ``BazInterface``. Our handler looks something like this:
^^^^^^^^^^^^^^^^^
``Psr\Http\Message\ServerRequestInterface`` provides "attributes" that allow you attach arbitrary data to a request. You can use this to make your dependency container available to any dispatched middleware.
When you instantiate a ``WellRESTed\Server``, you can provide an array of attributes that the server will add to the request.
.. code-block:: php .. code-block:: php
$container = new MySuperCoolDependencyContainer(); class FooHandler implements RequestHandlerInterface
$server = new WellRESTed\Server(["container" => $container]);
// ... Add middleware, routes, etc. ...
When the server dispatches middleware, the middleware will be able to read the container as the "container" attribute.
.. code-block:: php
function ($request, $response, $next) {
$container = $request->getAttribute("container");
// It's a super cool dependency container!
}
.. note::
This approach is technically more of a `service locator`_ pattern. It's easy to implement, and it allows you the most flexibility in how you assign middleware.
It has some drawbacks as well, though. For example, your middleware is now dependent on your container, and describing which items needs to be **in** the container provides its own challenge.
If your interested in a truer dependency injection approach, read on to the next section where we look at registering middleware factories.
Middleware Factories
^^^^^^^^^^^^^^^^^^^^
Another approach is to use a factory function that returns middleware, usually in the form of a ``MiddlewareInterface`` instance. This approach provides the opportunity to pass dependencies to your middleware's constructor, while still delaying instantiation until the middleware is used.
Imagine a middleware ``FooHandler`` that depends on a ``BarInterface``, and ``BazInterface``.
.. code-block:: php
Class FooHandler implements WellRESTed\MiddlewareInterface
{ {
private $bar; private $bar;
private $baz; private $baz;
@ -56,47 +20,38 @@ Imagine a middleware ``FooHandler`` that depends on a ``BarInterface``, and ``Ba
$this->baz = $baz; $this->baz = $baz;
} }
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next); public function handle(ServerRequestInterface $request): ResponseInterface
{ {
// Do something with the bar and baz and update the response. // Do something with the bar and baz and return a response...
// ... // ...
return $response;
} }
} }
When you add the middleware to the server or register it with a router, you can use a callable that passes appropriate instances into the constructor. We can register the handler and these dependencies in a Pimple_ service provider.
.. code-block:: php .. code-block:: php
// Assume $bar and $baz exist in this scope. class MyServiceProvider implements ServiceProviderInterface
$fooHandlerFactory = function () use ($bar, $bar) { {
return new FooHandler($bar, $baz); public function register(Container $c)
{
// Register the Bar and Baz as services.
$c['bar'] = function ($c) {
return new Bar();
};
$c['baz'] = function ($c) {
return new Baz();
};
// Register the Handler as a protected function. When you use
// protect, Pimple returns the function itself instead of the return
// value of the function.
$c['fooHandler'] = $c->protect(function () use ($c) {
return new FooHandler($c['bar'], $c['baz']);
});
}
} }
$server = new Server(); By "protected" the ``fooHandler`` service, we are delaying the instantiation of the ``FooHandler``, the ``Bar``, and the ``Baz`` until the handler needs to be dispatched. This works because we're not passing instance of ``FooHandler`` when we register this with a router, we're passing a function to it that does the instantiation on demand.
$server->add(
$server->createRoute()
->register("GET", "/foo/{id}", $fooHandlerFactory)
);
$server->respond();
You can combine this approach with a dependency container. Here's an example using Pimple_). .. _Pimple: https://pimple.symfony.com/
.. code-block:: php
$c = new Pimple\Container();
$c["bar"] = /* Return a BarInterface */
$c["baz"] = /* Return a BazInterface */
$c["fooHandler"] = $c->protect(function () use ($c) {
return new FooHandler($c["bar"], $c["baz"]);
});
$server = new Server();
$server->add(
$server->createRoute()
->register("GET", "/foo/{id}", $c["fooHandler"])
);
$server->respond();
.. _Pimple: http://pimple.sensiolabs.org
.. _service locator: https://en.wikipedia.org/wiki/Service_locator_pattern

View File

@ -1,112 +1,82 @@
Extending and Customizing Extending and Customizing
========================= =========================
WellRESTed is designed with customization in mind. This section describes some common scenarios for customization, starting with using middleware that implements a different interface. WellRESTed is designed with customization in mind. This section describes some common scenarios for customization, starting with using a handler that implements a different interface.
Custom Middleware Custom Handlers and Middleware
----------------- ------------------------------
Imagine you found a middleware class from a third party that does exactly what you need. The only problem is that it implements a different middleware interface. Imagine you found a handler class from a third party that does exactly what you need. The only problem is that it implements a different interface.
Here's the interface for the third-party middleware: Here's the interface for the third-party handler:
.. code-block:: php .. code-block:: php
interface OtherMiddlewareInterface interface OtherHandlerInterface
{ {
/** /**
* @param \Psr\Http\Message\ServerRequestInterface $request * @param ServerRequestInterface $request
* @param \Psr\Http\Message\ResponseInterface $response * @return ResponseInterface
* @return \Psr\Http\Message\ResponseInterface
*/ */
public function run( public function run(ResponseInterface $response);
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response
);
} }
Wrapping Wrapping
^^^^^^^^ ^^^^^^^^
One solution is to wrap an instance of this middleware inside of a ``WellRESTed\MiddlewareInterface`` instance. One solution is to wrap an instance of this handler inside of a ``Psr\Http\Server\RequestHandlerInterface`` instance.
.. code-block:: php .. code-block:: php
/** /**
* Wraps an instance of OtherMiddlewareInterface * Wraps an instance of OtherHandlerInterface
*/ */
class OtherWrapper implements \WellRESTed\MiddlewareInterface class OtherHandlerWrapper implements RequestHandlerInterface
{ {
private $middleware; private $handler;
public function __construct(OtherMiddlewareInterface $middleware) public function __construct(OtherHandlerInterface $handler)
{ {
$this->middleware = $middleware; $this->handler = $handler;
} }
public function __invoke( public function handle(ServerRequestInterface $request): ResponseInterface
\Psr\Http\Message\ServerRequestInterface $request, {
\Psr\Http\Message\ResponseInterface $response, return $this->handler->run($request);
$next
) {
// Run the wrapped middleware.
$response = $this->middleware->run($request, $response);
// Pass the middleware's response to $next and return the result.
return $next($request, $myResponse);
} }
} }
.. note::
``OtherMiddlewareInterface`` doesn't provide any information about how to propagate the request and response through a chain of middleware, so I chose to call ``$next`` every time. If there's a sensible way to tell that you should stop propagating, your wrapper class could return a response without calling ``$next`` under those circumstances. It's up to you and the middleware you're wrapping.
To use this wrapped middleware, you can do something like this:
.. code-block:: php
// The class we need to wrap; implements OtherMiddlewareInterface
$other = new OtherMiddleware();
// The wrapper class; implements WellRESTed\MiddlewareInterface
$otherWrapper = new OtherWrapper($other)
$server = new WellRESTed\Server();
$server->add($otherWrapper);
Custom Dispatcher Custom Dispatcher
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
Wrapping works well when you have one or two middleware implementing a third-party interface. If you want to integrate a lot of middleware classes that implement a given third-party interface, you're better off customizing the dispatcher. Wrapping works well when you have one or two handlers implementing a third-party interface. If you want to integrate a lot of classes that implement a given third-party interface, you're might consider customizing the dispatcher.
The dispatcher is an instance that unpacks your middleware and sends the request and response through it. A default dispatcher is created for you when you instantiate your ``WellRESTed\Server`` (without passing the second argument). The server instantiates a ``WellRESTed\Dispatching\Dispatcher`` which is capable of running middleware provided as a callable, a string containing the fully qualified class name of a middleware, or an array of middleware. (See `Using Middleware`_ for a description of what a default dispatcher can dispatch.) The dispatcher is an instance that unpacks your handlers and middleware and sends the request and response through it. A default dispatcher is created for you when you instantiate your ``WellRESTed\Server`` (without passing the second argument). The server instantiates a ``WellRESTed\Dispatching\Dispatcher`` which is capable of running handlers and middleware as described in the `Handlers and Middleware`_.
If you need the ability to dispatch other types of middleware, you can create your own by implementing ``WellRESTed\Dispatching\DispatcherInterface``. The easiest way to do this is to subclass ``WellRESTed\Dispatching\Dispatcher``. Here's an example that extends ``Dispatcher`` and adds support for ``OtherMiddlewareInterface``: If you need the ability to dispatch other types of middleware, you can create your own by implementing ``WellRESTed\Dispatching\DispatcherInterface``. The easiest way to do this is to subclass ``WellRESTed\Dispatching\Dispatcher``. Here's an example that extends ``Dispatcher`` and adds support for ``OtherHandlerInterface``:
.. code-block:: php .. code-block:: php
namespace MyApi;
/** /**
* Dispatcher with support for OtherMiddlewareInterface * Dispatcher with support for OtherHandlerInterface
*/ */
class CustomDispatcher extends \WellRESTed\Dispatching\Dispatcher class CustomDispatcher extends \WellRESTed\Dispatching\Dispatcher
{ {
public function dispatch( public function dispatch(
$middleware, $dispatchable,
\Psr\Http\Message\ServerRequestInterface $request, ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response, ResponseInterface $response,
$next $next
) { ) {
try { try {
// Use the dispatch method in the parent class first. // Use the dispatch method in the parent class first.
$response = parent::dispatch($middleware, $request, $response, $next); $response = parent::dispatch($dispatchable, $request, $response, $next);
} catch (\WellRESTed\Dispatching\DispatchException $e) { } catch (\WellRESTed\Dispatching\DispatchException $e) {
// If there's a problem, check if the middleware implements // If there's a problem, check if the handler or middleware
// OtherMiddlewareInterface. Dispatch it if it does. // (the "dispatchable") implements OtherHandlerInterface.
if ($middleware instanceof OtherMiddlewareInterface) { // Dispatch it if it does.
$response = $middleware->run($request, $response); if ($dispatchable instanceof OtherHandlerInterface) {
$response = $next($request, $response); $response = $dispatchable->run($request);
} else { } else {
// Otherwise, re-throw the exception. // Otherwise, re-throw the exception.
throw $e; throw $e;
@ -126,12 +96,10 @@ To use this dispatcher, pass it to the constructor of ``WellRESTed\Server`` as t
// Pass this dispatcher to the server. // Pass this dispatcher to the server.
$server = new WellRESTed\Server(null, $dispatcher); $server = new WellRESTed\Server(null, $dispatcher);
// Now, you can add any middleware implementing OtherMiddlewareInterface // Now, you can add any handlers implementing OtherHandlerInterface
$other = new OtherMiddleware(); $other = new OtherHandler();
$server->add($other); $server->add($other);
// Registering OtherMiddlewareInterface middleware by FQCN will work, too.
Message Customization Message Customization
--------------------- ---------------------
@ -145,7 +113,7 @@ In the example above, we passed a custom dispatcher to the server. You can also
$response = new ThirdParty\Response(); $response = new ThirdParty\Response();
$server = new WellRESTed\Server(); $server = new WellRESTed\Server();
// ...add middleware... // ...add middleware and handlers...
// Pass your request and response to Server::respond // Pass your request and response to Server::respond
$server->response($request, $response); $server->response($request, $response);
@ -170,50 +138,8 @@ As an alternative to passing you preferred request and response instances into `
Classes such as ``Server`` that create dependencies as defaults keep the instantiation isolated in easy-to-override methods. For example, ``Server`` has a protected method ``getResponse`` that instantiates and returns a new response. You can easily replace this method with your own that returns the default response of your choice. Classes such as ``Server`` that create dependencies as defaults keep the instantiation isolated in easy-to-override methods. For example, ``Server`` has a protected method ``getResponse`` that instantiates and returns a new response. You can easily replace this method with your own that returns the default response of your choice.
For example, imagine you have a dependency container that provides the starting messages for you. You can subclass ``Server`` to obtain and use these messages as defaults like this: In addition to the messages, you can do similar customization for other ``Server`` dependencies such as the dispatcher (see above), the transmitter (which writes the response out to the client), and the routers that are created with ``Server::createRouter``. These dependencies are instantiated in isolated methods as with the request and response to make this sort of customization easy, and other classes such as ``Router`` use this pattern as well.
.. code-block:: php
class CustomerServer extends WellRESTed\Server
{
/** @var A dependency container */
private $container;
public function __construct(
$container,
array $attributes = null,
DispatcherInterface $dispatcher = null,
$pathVariablesAttributeName = null
) {
// Call the parent constructor with the expected parameters.
parent::__construct($attributes, $dispatcher, $pathVariablesAttributeName);
// Store the container.
$this->container = $container;
}
/**
* Redefine this method, which is called in Server::respond when
* the caller does not provide a request.
*/
protected function getRequest()
{
// Return a request obtained from the container.
return $this->container["request"];
}
/**
* Redefine this method, which is called in Server::respond when
* the caller does not provide a response.
*/
protected function getResponse()
{
// Return a response obtained from the container.
return $this->container["response"];
}
}
In addition to the messages, you can do similar customization for other ``Server`` dependencies such as the dispatcher (see above), the transmitter (which writes the response out to the client), and the routers that are created with ``Server::createRouter``. These dependencies are instantiated in isolated methods as with the request and response to make this sort of customization easy, and other classes such as ``Router`` use this pattern as well. See the source code, and don't hesitated to subclass.
.. _PSR-7: http://www.php-fig.org/psr/psr-7/ .. _PSR-7: http://www.php-fig.org/psr/psr-7/
.. _Using Middleware: middleware.html#using-middleware .. _Handlers and Middleware: handlers-and-middleware.html
.. _Request Attributes: messages.html#attributes .. _Request Attributes: messages.html#attributes

View File

@ -0,0 +1,236 @@
Handlers and Middleware
=======================
WellRESTed allows you to define and use your handlers and middleware in a number of ways.
Defining Handlers and Middleware
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PSR-15 Interfaces
-----------------
The prefered method is to use the interfaces standardized by PSR-15_. This standard includes two interfaces, ``Psr\Http\Server\RequestHandlerInterface`` and ``Psr\Http\Server\MiddlewareInterface``.
Use ``RequestHandlerInterface`` for individual components that generate and return responses.
.. code-block:: php
class HelloHandler implements RequestHandlerInterface
{
public function handle(ServerRequestInterface $request): ResponseInterface
{
// Check for a "name" attribute which may have been provided as a
// path variable. Use "world" as a default.
$name = $request->getAttribute("name", "world");
// Set the response body to the greeting and the status code to 200 OK.
$response = (new Response(200))
->withHeader("Content-type", "text/plain")
->withBody(new Stream("Hello, $name!"));
// Return the response.
return $response;
}
}
Use ``MiddlewareInterface`` for classes that interact with other middleware and handlers. For example, you may have middleware that attempts to retrieve a cached response and delegates to other handlers on a cache miss.
.. code-block:: php
class CacheMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
// Inspect the request to see if there is a representation on hand.
$representation = $this->getCachedRepresentation($request);
if ($representation !== null) {
// There is already a cached representation.
// Return it without delegating to the next handler.
return (new Response())
->withStatus(200)
->withBody($representation);
}
// No representation exists. Delegate to the next handler.
$response = $handler->handle($request);
// Attempt to store the response to the cache.
$this->storeRepresentationToCache($response);
return $response
}
private function getCachedRepresentation(ServerRequestInterface $request)
{
// Look for a cached representation. Return null if not found.
// ...
}
private function storeRepresentationToCache(ResponseInterface $response)
{
// Ensure the response contains a success code, a valid body,
// headers that allow caching, etc. and store the representation.
// ...
}
}
Legacy Middleware Interface
---------------------------
Prior to PSR-15, WellRESTed's recommended handler interface was ``WellRESTed\MiddlewareInterface``. This interface is still supported for backwards compatibility.
This interface serves for both handlers and middleware. It differs from the ``Psr\Http\Server\MiddlewareInterface`` in that is expects an incoming ``$response`` parameter which you may use to generate the returned response. It also expected a ``$next`` parameter which is a ``callable`` with this signature:
.. code-block:: php
function next($request, $resposnse): ResponseInterface
Call ``$next`` and pass ``$request`` and ``$response`` to forward the request to the next handler. ``$next`` will return the response from the handler. Here's the cache example above as a ``WellRESTed\MiddlewareInterface``.
.. code-block:: php
class CacheMiddleware implements WellRESTed\MiddlewareInterface
{
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
$next
) {
// Inspect the request to see if there is a representation on hand.
$representation = $this->getCachedRepresentation($request);
if ($representation !== null) {
// There is already a cached representation.
// Return it without delegating to the next handler.
return $response
->withStatus(200)
->withBody($representation);
}
// No representation exists. Delegate to the next handler.
$response = $next($request, $response);
// Attempt to store the response to the cache.
$this->storeRepresentationToCache($response);
return $response
}
private function getCachedRepresentation(ServerRequestInterface $request)
{
// Look for a cached representation. Return null if not found.
// ...
}
private function storeRepresentationToCache(ResponseInterface $response)
{
// Ensure the response contains a success code, a valid body,
// headers that allow caching, etc. and store the representation.
// ...
}
}
Callables
---------
You may also use a ``callable`` similar to the legacy ``WellRESTed\MiddlewareInterface``. The signature of the callable matches the signature of ``WellRESTed\MiddlewareInterface::__invoke``.
.. code-block:: php
$handler = function ($request, $response, $next) {
// Delegate to the next handler.
$response = $next($request, $response);
return $response
->withHeader("Content-type", "text/plain")
->withBody(new Stream("Hello, $name!"));
}
Using Handlers and Middleware
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Methods that accept handlers and middleware (e.g., ``Server::add``, ``Router::register``) allow you to provide them in a number of ways. For example, you can provide an instance, a ``callable`` that provides an instance, or an ``array`` of handlers to use in sequence. The following examples will demonstrate all of the ways you can register handlers and middleware.
Factory Callable
----------------
The best method is to use a ``callable`` that returns an instance of your handler. The main benefit of this approach is that no handlers are instantiated until they are needed.
.. code-block:: php
$router->register("GET,PUT,DELETE", "/widgets/{id}",
function () { return new App\WidgetHandler() }
);
If you're using ``Pimple``, a popular `dependency injection`_ container for PHP, you may have code that looks like this:
.. code-block:: php
// Create a DI container.
$c = new Container();
// Store a function to the container that will create and return the handler.
$c['widgetHandler'] = $c->protect(function () use ($c) {
return new App\WidgetHandler();
});
$router->register("GET,PUT,DELETE", "/widgets/{id}", $c['widgetHandler']);
Instance
--------
WellRESTed also allows you to pass an instance of a handler directly. This may be useful for smaller handlers that don't require many dependencies, although it is generally better to use the factory callable approach.
.. code-block:: php
$widgetHandler = new App\WidgetHandler();
$router->register("GET,PUT,DELETE", "/widgets/{id}", $widgetHandler);
.. warning::
This is simple, but has a significant disadvantage over the other options because each middleware used this way will be loaded and instantiated, even if it's not needed for a given request-response cycle. You may find this approach useful for testing, but avoid if for production code.
Fully Qualified Class Name (FQCN)
---------------------------------
For handlers that do not require any arguments passed to the constructor, you may pass the fully qualified class name of your handler as a ``string``. You can do that like this:
.. code-block:: php
$router->register("GET,PUT,DELETE", "/widgets/{id}", \App\WidgetHandler::class);
// ... or ...
$router->register("GET,PUT,DELETE", "/widgets/{id}", 'App\\WidgetHandler');
The class is not loaded, and no instances are created, until the route is matched and dispatched. However, the drawback to this approach is the there is no way to pass any arguments to the contructor.
Array
-----
The final approach is to provide a sequence of middleware and a handler as an ``array``.
For example, imagine if we had a Pimple_ container with these services:
.. code-block:: php
$c['authMiddleware'] // Ensures the user is logged in
$c['cacheMiddlware'] // Provides a cached response if able
$c['widgetHandler'] // Provides a widget representation
We could provide these as a sequence by using an ``array``.
.. code-block:: php
$router->register('GET', '/widgets/{id}', [
$c['authMiddleware'],
$c['cacheMiddlware'],
$c['widgetHandler']
]);
.. _Dependency Injection: dependency-injection.html
.. _Pimple: https://pimple.symfony.com/
.. _PSR-15: https://www.php-fig.org/psr/psr-15/

View File

@ -131,7 +131,7 @@ Contents
overview overview
getting-started getting-started
messages messages
middleware handlers-and-middleware
router router
uri-templates uri-templates
uri-templates-advanced uri-templates-advanced
@ -142,6 +142,5 @@ Contents
.. _PSR-7: http://www.php-fig.org/psr/psr-7/ .. _PSR-7: http://www.php-fig.org/psr/psr-7/
.. _PSR-15: http://www.php-fig.org/psr/psr-15/ .. _PSR-15: http://www.php-fig.org/psr/psr-15/
.. _middleware: middleware.html
.. _router: router.html .. _router: router.html
.. _URI Templates: uri-templates.html .. _URI Templates: uri-templates.html

View File

@ -1,295 +0,0 @@
Middleware
==========
Okay, so what exactly **is** middleware? It's a nebulous term, and it's a bit reminiscent of the Underpants gnomes.
- Phase 1: Request
- Phase 2: ???
- Phase 3: Response
Middleware is indeed Phase 2. It's something (a callable or object) that takes a request and a response as inputs, does something with the response, and sends the altered response back out.
A Web service can built from many, many pieces of middleware, with each piece managing a specific task such as authentication or parsing representations. When each middleware runs, it is responsible for propagating the request through to the next middleware in the sequence—or deciding not to.
So what's it look like? In essence, a single piece of middleware looks something like this:
.. code-block:: php
function ($request, $response, $next) {
// Update the response.
/* $response = ... */
// Determine if any other middleware should be called after this.
if (/* Stop now without calling more middleware? */) {
// Return the response without calling any other middleware.
return $response;
}
// Let the next middleware work on the response. This propagates "up"
// the chain of middleware, and will eventually return a response.
$response = $next($request, $response);
// Possibly update the response some more.
/* $response = ... */
// Return the response.
return $response;
}
Defining Middleware
^^^^^^^^^^^^^^^^^^^
Middleware can be a callable (as in the `Getting Started`_) or an implementation of the ``WellRESTed\MiddlewareInterface`` (which implements ``__invoke`` so is technically a callable, too).
.. rubric:: Callable
.. code-block:: php
/**
* @param Psr\Http\Message\ServerRequestInterface $request
* @param Psr\Http\Message\ResponseInterface $response
* @param callable $next
* @return Psr\Http\Message\ResponseInterface
*/
function ($request, $response, $next) { }
.. rubric:: MiddlewareInterface
.. code-block:: php
<?php
namespace WellRESTed;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
interface MiddlewareInterface
{
/**
* @param ServerRequestInterface $request
* @param ResponseInterface $response
* @param callable $next
* @return ResponseInterface
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next);
}
Using Middleware
^^^^^^^^^^^^^^^^
Methods that accept middleware (e.g., ``Server::add``, ``Router::register``) allow you to provide middleware in a number of ways. For example, you can provide a string containing a class name, a middleware callable, a factory callable, or even an array containing a sequence of middleware.
Fully Qualified Class Name (FQCN)
---------------------------------
Assume your Web service has an autoloadable class named ``Webservice\Widgets\WidgetHandler``. You can register it with a router by passing a string containing the fully qualified class name (FQCN):
.. code-block:: php
$router->register("GET,PUT,DELETE", "/widgets/{id}", 'Webservice\Widgets\WidgetHandler');
The class is not loaded, and no instances are created, until the route is matched and dispatched. Even for a router with 100 routes, no middleware registered by string name is loaded, except for the one that matches the request.
Factory Callable
----------------
You can also use a callable to instantiate and return a ``MiddlewareInterface`` instance or middleware callable.
.. code-block:: php
$router->add("GET,PUT,DELETE", "/widgets/{id}", function () {
return new \Webservice\Widgets\WidgetHandler();
});
This still delays instantiation, but gives you some added flexibility. For example, you could define middleware that receives some dependencies upon construction.
.. code-block:: php
$container = new MySuperCoolDependencyContainer();
$router->add("GET,PUT,DELETE", "/widgets/{id}", function () use ($container) {
return new \Webservice\Widgets\WidgetHandler($container["foo"], $container["baz"]);
});
This is one approach to `dependency injection`_.
Middleware Callable
-------------------
Use a middleware callable directly.
.. code-block:: php
$router->add("GET,PUT,DELETE", "/widgets/{id}", function ($request, $response, $next) {
$response = $response->withStatus(200)
->withHeader("Content-type", "text/plain")
->withBody(new \WellRESTed\Message\Stream("It's a bunch of widgets!"));
return $next($request, $response);
});
Because ``WellRESTed\MiddlewareInterface`` has an ``__invoke`` method, implementing instances are also middleware callables. Assuming ``WidgetHandler`` implements ``MiddelewareInterface``, you can do this:
.. code-block:: php
$router->add("GET,PUT,DELETE", "/widgets/{id}", new \Webservice\Widgets\WidgetHandler());
.. warning::
This is simple, but has a significant disadvantage over the other options because each middleware used this way will be loaded and instantiated, even if it's not needed for a given request-response cycle. You may find this approach useful for testing, but avoid if for production code.
Array
-----
Why use one middleware when you can use more?
Provide a sequence of middleware as an array. Each component of the array can be any of the varieties listed in this section.
When dispatched, the middleware in the array will run in order, with each calling the one following via the ``$next`` parameter.
.. code-block:: php
$router->add("GET", "/widgets/{id}", ['Webservice\Auth', $jsonParser, $widgetHandler]);
Chaining Middleware
^^^^^^^^^^^^^^^^^^^
Chaining middleware together allows you to build your Web service in a discrete, modular pieces. Each middleware in the chain makes the decision to either move the request up the chain by calling ``$next``, or stop propagation by returning a response without calling ``$next``.
Propagating Up the Chain
------------------------
Imagine we want to add authorization to the ``/widgets/{id}`` endpoint. We can do this without altering the existing middleware that deals with the widget itself.
What we do is create an additional middleware that performs just the authorization task. This middleware will inspect the incoming request for authorization headers, and either move the request on up the chain to the next middleware if all looks good, or send a request back out with an appropriate status code.
Here's an example authorization middleware using pseudocode.
.. code-block:: php
namespace Webservice;
class Authorization implements \WellRESTed\MiddlewareInterface
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
{
// Validate the headers in the request.
try {
$validateUser($request);
} catch (InvalidHeaderException $e) {
// User did not supply the right headers.
// Respond with a 401 Unauthorized status.
return $response->withStatus(401);
} catch (BadUserException $e) {
// User is not permitted to access this resource.
// Respond with a 403 Forbidden status.
return $response->withStatus(403);
}
// No exception was thrown, so propagate to the next middleware.
return $next($request, $response);
}
}
We can add authorization for just the ``/widgets/{id}`` endpoint like this:
.. code-block:: php
$server = new \WellRESTed\Server();
$server->add($server->createRouter()
->register("GET,PUT,DELETE", "/widgets/{id}", [
'Webservice\Authorization',
'Webservice\Widgets\WidgetHandler'
])
->respond();
Or, if you wanted to use the authorization for the entire service, you can add it to the ``Server`` in front of the ``Router``.
.. code-block:: php
$server = new \WellRESTed\Server();
$server
->add('Webservice\Authorization')
->add($server->createRouter()
->register("GET,PUT,DELETE", "/widgets/{id}", 'Webservice\Widgets\WidgetHandler')
)
->respond();
Moving Back Down the Chain
--------------------------
The authorization example returned ``$next($request, $response)`` immediately, but you can do some interesting things by working with the response that comes back from ``$next``. Think of the request as taking a round trip on the subway with each middleware being a stop along the way. Each of the stops you go through going up the chain, you also go through on the way back down.
We could add a caching middleware in front of ``GET`` requests for a specific widget. This middleware will check if a cached representation exists for the resource the client requested. If it exists, it will send it out to the client without ever bothering the ``WidgetHandler``. If there's no representation cached, it will call ``$next`` to propagate the request up the chain. On the return trip (when the call to ``$next`` finishes), the caching middleware will inspect the response and store the body to the cache for next time.
Here's a pseudocode example:
.. code-block:: php
namespace Webservice;
class Cache implements \WellRESTed\MiddlewareInterface
{
public function dispatch(ServerRequestInterface $request, ResponseInterface $response, $next)
{
// Inspect the request path to see if there is a representation on
// hand for this resource.
$representation = $this->getCachedRepresentation($request);
if ($representation !== null) {
// There is already a cached representation. Send it out
// without propagating.
return $response
->withStatus(200)
->withBody($representation);
}
// No representation exists. Propagate to the next middleware.
$response = $next($request, $response);
// Attempt to store the response to the cache.
$this->storeRepresentationToCache($response);
return $response;
}
private function getCachedRepresentation(ServerRequestInterface $request)
{
// Look for a cached representation. Return null if not found.
// ...
}
private function storeRepresentationToCache(ResponseInterface $response)
{
// Ensure the response contains a success code, a valid body,
// headers that allow caching, etc. and store the representation.
// ...
}
}
We can add this caching middleware in the chain between the authorization middleware and the Widget.
.. code-block:: php
$router->register("GET,PUT,DELETE", "/widgets/{id}", [
'Webservice\Authorization',
'Webservice\Cache',
'Webservice\Widgets\WidgetHandler'
]);
Or, if you wanted to use the authorization and caching middleware for the entire service, you can add them to the ``Server`` in front of the ``Router``.
.. code-block:: php
$server = new \WellRESTed\Server();
$server
->add('Webservice\Authorization')
->add('Webservice\Cache')
->add($server->createRouter()
->register("GET,PUT,DELETE", "/widgets/{id}", 'Webservice\Widgets\WidgetHandler')
)
->respond();
.. _Dependency Injection: dependency-injection.html
.. _Getting Started: getting-started.html

View File

@ -1,8 +1,7 @@
Router Router
====== ======
A router is a type of middleware_ that organizes the components of a site by associating URI paths with other middleware_. When the router receives a request, it examines the path components of the request's URI, determines which "route" matches, and dispatches the associated middleware_. The dispatched middleware_ is then responsible for reacting to the request and providing a response. A router is a type of handler that organizes the components of a site by associating HTTP methods and paths with other handler and middleware. When the router receives a request, it examines the path components of the request's URI, determines which "route" matches, and dispatches the associated handler. The dispatched handler is then responsible for reacting to the request and providing a response.
Basic Usage Basic Usage
^^^^^^^^^^^ ^^^^^^^^^^^
@ -42,12 +41,12 @@ The ``register`` method is fluent, so you can add multiple routes in either of t
Paths Paths
^^^^^ ^^^^^
A router can map middleware to an exact path, or to a pattern of paths. A router can map a handler to an exact path, or to a pattern of paths.
Static Routes Static Routes
------------- -------------
The simplest type of route is called a "static route". It maps middleware to an exact path. The simplest type of route is called a "static route". It maps a handler to an exact path.
.. code-block:: php .. code-block:: php
@ -77,17 +76,14 @@ For example, this template will match requests to ``/cats/12``, ``/cats/molly``,
$router->register("GET", "/cats/{cat}", $catHandler); $router->register("GET", "/cats/{cat}", $catHandler);
When the router dispatches a route matched by a template route, it provides the extracted variables as an associative array. To access a variable, call the request object's ``getAttribute`` method method and pass the variable's name. When the router dispatches a route matched by a template route, it provides the extracted variables as request attributes. To access a variable, call the request object's ``getAttribute`` method and pass the variable's name.
For a request to ``/cats/molly``: For a request to ``/cats/molly``:
.. code-block:: php .. code-block:: php
$catHandler = function ($request, $response, $next) { $name = $request->getAttribute("cat");
$name = $request->getAttribute("cat"); // molly
// molly
...
}
Template routes are very powerful, and this only scratches the surface. See `URI Templates`_ for a full explanation of the syntax supported. Template routes are very powerful, and this only scratches the surface. See `URI Templates`_ for a full explanation of the syntax supported.
@ -106,31 +102,28 @@ For a request to ``/cats/molly-90``:
.. code-block:: php .. code-block:: php
$catHandler = function ($request, $response, $next) { $vars = $request->getAttributes();
$vars = $request->getAttributes(); /*
/* Array
Array (
( [0] => cats/molly-12
[0] => cats/molly-12 [name] => molly
[name] => molly [1] => molly
[1] => molly [number] => 12
[number] => 12 [2] => 12
[2] => 12 ... Plus any other attributes that were set ...
... Plus any other attributes that were set ... )
) */
*/
...
}
Route Priority Route Priority
-------------- --------------
A router will often contain many routes, and sometimes more than one route will match for a given request. When the router looks for a matching route, it performs these checks: A router will often contain many routes, and sometimes more than one route will match for a given request. When the router looks for a matching route, it performs these checks in order.
#. If there is a static route with exact match to path, dispatch it. #. If there is a static route with exact match to path, dispatch it.
#. If one prefix route matches the beginning of the path, dispatch it. #. If one prefix route matches the beginning of the path, dispatch it.
#. If multiple prefix routes match, dispatch the longest matching prefix route. #. If multiple prefix routes match, dispatch the longest matching prefix route.
#. Inspect each pattern route (template and regular expression) in the order added. Dispatch the first route that matches. #. Inspect each pattern route (template and regular expression) in the order in which they were added to the router. Dispatch the first route that matches.
#. If no pattern routes match, return a response with a ``404 Not Found`` status. #. If no pattern routes match, return a response with a ``404 Not Found`` status.
Static vs. Prefix Static vs. Prefix
@ -179,7 +172,7 @@ Given these routes:
Pattern vs. Pattern Pattern vs. Pattern
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
When multiple pattern routes match a path, the first one that was added to the router will be the one dispatched. Be careful to add the specific routes before the general routes. For example, say you want to send traffic to two similar looking URIs to different middleware based whether the variables were supplied as numbers or letters—``/dogs/102/132`` should be dispatched to ``$numbers``, while ``/dogs/herding/australian-shepherd`` should be dispatched to ``$letters``. When multiple pattern routes match a path, the first one that was added to the router will be the one dispatched. **Be careful to add the specific routes before the general routes.** For example, say you want to send traffic to two similar looking URIs to different handlers based whether the variables were supplied as numbers or letters—``/dogs/102/132`` should be dispatched to ``$numbers``, while ``/dogs/herding/australian-shepherd`` should be dispatched to ``$letters``.
This will work: This will work:
@ -209,7 +202,7 @@ When you register a route, you can provide a specific method, a list of methods,
Registering by Method Registering by Method
--------------------- ---------------------
Specify a specific middleware for a path and method by including the method as the first parameter. Specify a specific handler for a path and method by including the method as the first parameter.
.. code-block:: php .. code-block:: php
@ -222,7 +215,7 @@ Specify a specific middleware for a path and method by including the method as t
Registering by Method List Registering by Method List
-------------------------- --------------------------
Specify the same middleware for multiple methods for a given path by proving a comma-separated list of methods as the first parameter. Specify the same handler for multiple methods for a given path by proving a comma-separated list of methods as the first parameter.
.. code-block:: php .. code-block:: php
@ -238,7 +231,7 @@ Specify the same middleware for multiple methods for a given path by proving a c
Registering by Wildcard Registering by Wildcard
----------------------- -----------------------
Specify middleware for all methods for a given path by proving a ``*`` wildcard. Specify a handler for all methods for a given path by proving a ``*`` wildcard.
.. code-block:: php .. code-block:: php
@ -319,17 +312,11 @@ GET /hamsters/ 404 Not Found
PUT /cats/ 405 Method Not Allowed PUT /cats/ 405 Method Not Allowed
====== ========== ======== ====== ========== ========
.. note::
When the router fails to dispatch a route, or when it responds to an ``OPTIONS`` request, is will stop propagation, and any middleware that comes after the router will not be dispatched.
Nested Routers Nested Routers
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
For large Web services with large numbers of endpoints, a single, monolithic router may not to optimal. To avoid having each request test every pattern-based route, you can break up a router into sub-routers. For large Web services with large numbers of endpoints, a single, monolithic router may not to optimal. To avoid having each request test every pattern-based route, you can break up a router into sub-routers.
This works because a ``Router`` is type of middleware, and can be used wherever middleware can be used.
Here's an example where all of the traffic beginning with ``/cats/`` is sent to one router, and all the traffic for endpoints beginning with ``/dogs/`` is sent to another. Here's an example where all of the traffic beginning with ``/cats/`` is sent to one router, and all the traffic for endpoints beginning with ``/dogs/`` is sent to another.
.. code-block:: php .. code-block:: php
@ -357,4 +344,3 @@ Here's an example where all of the traffic beginning with ``/cats/`` is sent to
.. _preg_match: http://php.net/manual/en/function.preg-match.php .. _preg_match: http://php.net/manual/en/function.preg-match.php
.. _URI Template: `URI Templates`_s .. _URI Template: `URI Templates`_s
.. _URI Templates: uri-templates.html .. _URI Templates: uri-templates.html
.. _middleware: middleware.html

View File

@ -1,7 +1,7 @@
URI Templates (Advanced) URI Templates (Advanced)
======================== ========================
In `URI Templates`_, we looked at the most common ways to use URI Templates. In this chapter, we'll look at some of the extended syntaxes that URI Templates provide. In `URI Templates`_, we looked at the most common ways to use URI Templates. Here, we'll look at some of the extended syntaxes that URI Templates provide.
Path Components Path Components
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^

View File

@ -1,7 +1,7 @@
URI Templates URI Templates
============= =============
WellRESTed allows you to register middleware with a router using URI Templates, based on the URI Templates defined in `RFC 6570`_. These templates include variables (enclosed in curly braces) which are extracted and made available to the dispatched middleware. WellRESTed allows you to register handlers with a router using URI Templates, based on the URI Templates defined in `RFC 6570`_. These templates include variables (enclosed in curly braces) which are extracted and made available to the dispatched middleware.
Reading Variables Reading Variables
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
@ -9,7 +9,7 @@ Reading Variables
Basic Usage Basic Usage
----------- -----------
Register middleware with a URI Template by providing a path that include at least one section enclosed in curly braces. The curly braces define variables for the template. Register a handler with a URI Template by providing a path that include at least one section enclosed in curly braces. The curly braces define variables for the template.
.. code-block:: php .. code-block:: php
@ -17,16 +17,17 @@ Register middleware with a URI Template by providing a path that include at leas
The router will match requests for paths like ``/widgets/12`` and ``/widgets/mega-widget`` and dispatch ``$widgetHandler`` with the extracted variables made available as request attributes. The router will match requests for paths like ``/widgets/12`` and ``/widgets/mega-widget`` and dispatch ``$widgetHandler`` with the extracted variables made available as request attributes.
To read a path variable, the ``$widgetHandler`` middleware inspects the request attribute named ``"id"``, since ``id`` is what appears inside curly braces in the URI template. To read a path variable, router inspects the request attribute named ``"id"``, since ``id`` is what appears inside curly braces in the URI template.
.. code-block:: php .. code-block:: php
$widgetHandler = function ($request, $response, $next) { // For a request to /widgets/12
// Read the variable extracted form the path. $id = $request->getAttribute("id");
$id = $request->getAttribute("id"); // 12
};
When the request path is ``/widgets/12``, the value returned by ``$request->getAttribute("id")`` is ``"12"``. For ``/widgets/mega-widget``, the value is ``"mega-widget"``. // For a request to /widgets/mega-widget
$id = $request->getAttribute("id");
// mega-widget
.. note:: .. note::
@ -47,15 +48,13 @@ A request for ``GET /avatars/zoidberg-100x150.jpg`` will provide these request a
.. code-block:: php .. code-block:: php
$avatarHandlers = function ($request, $response, $next) { // Read the variables extracted form the path.
// Read the variables extracted form the path. $username = $request->getAttribute("username");
$username = $request->getAttribute("username"); // "zoidberg"
// "zoidberg" $width = $request->getAttribute("width");
$width = $request->getAttribute("width"); // "100"
// "100" $height = $request->getAttribute("height");
$height = $request->getAttribute("height"); // "150"
// "150"
};
Arrays Arrays
------ ------
@ -108,7 +107,7 @@ Given the template ``/users/{user}``, the following paths provide these values f
* - /users/zoidberg%40planetexpress.com * - /users/zoidberg%40planetexpress.com
- "zoidberg@planetexpress.com" - "zoidberg@planetexpress.com"
A request for ``GET /uses/zoidberg@planetexpress.com`` will **not** match this template, because ``@`` is not an unreserved character and is not percent encoded. A request for ``GET /uses/zoidberg@planetexpress.com`` will **not** match this template, because ``@`` is a reserved character and is not percent encoded.
Reserved Characters Reserved Characters
------------------- -------------------
@ -127,11 +126,8 @@ The router will dispatch ``$pathHandler`` with for a request to ``GET /my-favori
.. code-block:: php .. code-block:: php
$pathHandler = function ($request, $response, $next) { $path = $request->getAttribute("path");
// Read the variable extracted form the path. // "/has/a/few/slashes.jpg"
$path = $request->getAttribute("path");
// "/has/a/few/slashes.jpg"
};
.. note:: .. note::