wellrested/docs/source/extending.rst

220 lines
9.2 KiB
ReStructuredText

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.
Custom 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.
Here's the interface for the third-party middleware:
.. code-block:: php
interface OtherMiddlewareInterface
{
/**
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param \Psr\Http\Message\ResponseInterface $response
* @return \Psr\Http\Message\ResponseInterface
*/
public function run(
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response
);
}
Wrapping
^^^^^^^^
One solution is to wrap an instance of this middleware inside of a ``WellRESTed\MiddlewareInterface`` instance.
.. code-block:: php
/**
* Wraps an instance of OtherMiddlewareInterface
*/
class OtherWrapper implements \WellRESTed\MiddlewareInterface
{
private $middleware;
public function __construct(OtherMiddlewareInterface $middleware)
{
$this->middleware = $middleware;
}
public function __invoke(
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response,
$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
^^^^^^^^^^^^^^^^^
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.
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.)
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``:
.. code-block:: php
namespace MyApi;
/**
* Dispatcher with support for OtherMiddlewareInterface
*/
class CustomDispatcher extends \WellRESTed\Dispatching\Dispatcher
{
public function dispatch(
$middleware,
\Psr\Http\Message\ServerRequestInterface $request,
\Psr\Http\Message\ResponseInterface $response,
$next
) {
try {
// Use the dispatch method in the parent class first.
$response = parent::dispatch($middleware, $request, $response, $next);
} catch (\WellRESTed\Dispatching\DispatchException $e) {
// If there's a problem, check if the middleware implements
// OtherMiddlewareInterface. Dispatch it if it does.
if ($middleware instanceof OtherMiddlewareInterface) {
$response = $middleware->run($request, $response);
$response = $next($request, $response);
} else {
// Otherwise, re-throw the exception.
throw $e;
}
}
return $response;
}
}
To use this dispatcher, pass it to the constructor of ``WellRESTed\Server`` as the second argument. (The first argument is a hash array to use as `request attributes`_.)
.. code-block:: php
// Create an instance of your custom dispatcher.
$dispatcher = new MyApi\CustomDispatcher;
// Pass this dispatcher to the server.
$server = new WellRESTed\Server(null, $dispatcher);
// Now, you can add any middleware implementing OtherMiddlewareInterface
$other = new OtherMiddleware();
$server->add($other);
// Registering OtherMiddlewareInterface middleware by FQCN will work, too.
Message Customization
---------------------
In the example above, we passed a custom dispatcher to the server. You can also customize your server in other ways. For example, if you have a different implementation of PSR-7_ messages that you prefer, you can pass them into the ``Server::respond`` method:
.. code-block:: php
// Represents the request submitted by the client.
$request = new ThirdParty\Request();
// A "blank" response.
$response = new ThirdParty\Response();
$server = new WellRESTed\Server();
// ...add middleware...
// Pass your request and response to Server::respond
$server->response($request, $response);
Even if you don't want to use a different implementation, you may still find a reason to provide you're own messages. For example, the default response status code for a ``WellRESTed\Message\Response`` is 500. If you wanted to make the default 200 instead, you could do something like this:
.. code-block:: php
// The first argument is the status code.
$response = new \WellRESTed\Message\Response(200);
$server = new \WellRESTed\Server();
// ...add middleware...
// Pass the response to respond()
$server->respond(null, $response);
Server Customization
--------------------
As an alternative to passing you preferred request and response instances into ``Server::respond``, you can extend ``Server`` to obtain default values from a different source.
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:
.. 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/
.. _Using Middleware: middleware.html#using-middleware
.. _Request Attributes: messages.html#attributes