For middleware to use with Relay, please review Relay.Middleware and oscarotero/psr7-middlewares.

Middleware Signature

A Relay middleware callable must have the following signature:

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\RequestInterface as Request;

function (
    Request $request,   // the request
    Response $response, // the response
    callable $next      // the next middleware
) {
    // ...
}

A Relay middleware callable must return an implementation of Psr\Http\Message\ResponseInterface.

This signature makes Relay appropriate for both server-related and client-related use cases. That is, it can receive an incoming ServerRequestInterface and generate an outgoing ResponseInterface (acting as a server), or it can build an outgoing RequestInterface and return the resulting ResponseInterface (acting as a client).

N.b.: Psr\Http\Message\ServerRequestInterface extends RequestInterface, so typehinting to RequestInterface covers both use cases.

Middleware Dispatching

Create a $queue array of middleware callables:

$queue[] = function (Request $request, Response $response, callable $next) {
    // 1st middleware
};

$queue[] = function (Request $request, Response $response, callable $next) {
    // 2nd middleware
};

// ...

$queue[] = function (Request $request, Response $response, callable $next) {
    // Nth middleware
};

Use the RelayBuilder to create a Relay with the $queue, and invoke the Relay with a request and response.

/**
 * @var \Psr\Http\Message\RequestInterface $request
 * @var \Psr\Http\Message\ResponseInterface $response
 */

use Relay\RelayBuilder;

$relayBuilder = new RelayBuilder();
$relay = $relayBuilder->newInstance($queue);
$response = $relay($request, $response);

That will execute each of the middlewares in first-in-first-out order.

Middleware Logic

Your middleware logic should follow this pattern:

Here is a skeleton example; your own middleware may or may not perform the various optional processes:

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\RequestInterface as Request;

$queue[] = function (Request $request, Response $response, callable $next) {

    // optionally modify the incoming request
    $request = $request->...;

    // optionally skip the $next middleware and return early
    if (...) {
        return $response;
    }

    // optionally invoke the $next middleware and get back a new response
    $response = $next($request, $response);

    // optionally modify the Response if desired
    $response = $response->...;

    // NOT OPTIONAL: return the Response to the previous middleware
    return $response;
};

N.b.: You MUST return the response from your middleware logic.

Remember that the request and response are immutable. Implicit in that is the fact that changes to the request are always transmitted to the $next middleware but never to the previous one.

Note also that this logic chain means the request and response are subjected to two passes through each middleware:

For example, if the middleware queue looks like this:

$queue[] = function (Request $request, Response $response, callable $next) {
    // "Foo"
};

$queue[] = function (Request $request, Response $response, callable $next) {
    // "Bar"
};

$queue[] = function (Request $request, Response $response, callable $next) {
    // "Baz"
};

… the request and response path through the middlewares will look like this:

Foo is 1st on the way in
    Bar is 2nd on the way in
        Baz is 3rd on the way in, and 1st on the way out
    Bar is 2nd on the way out
Foo is 3rd on the way out

You can use this dual-pass logic in clever and perhaps unintuitive ways. For example, middleware placed at the very start may do nothing with the request and call $next right away, but it is the middleware with the “real” last opportunity to modify the response.

Resolvers

You may wish to use $queue entries other than anonymous functions or already-instantiated objects. If so, you can pass a $resolver callable to the Relay that will convert the $queue entry to a callable. Thus, using a $resolver allows you to pass in your own factory mechanism for $queue entries.

For example, this $resolver will naively convert $queue string entries to new class instances:

$resolver = function ($class) {
    return new $class();
};

You can then add $queue entries as class names, and the Relay will use the $resolver to create the objects in turn.

use Relay\RelayBuilder;

$queue[] = 'FooMiddleware';
$queue[] = 'BarMiddleware';
$queue[] = 'BazMiddleware';

$relayBuilder = new RelayBuilder($resolver);
$relay = $relayBuilder->newInstance($queue);

As long as the classes listed in the $queue implement __invoke(Request $request, Response $response, callable $next), then the Relay will work correctly.

Queue Object

Sometimes using an array for the $queue will not be suitable. You may wish to use an object to build the middleware queue instead.

In these cases, you can use the RelayBuilder to create the Relay queue from any object that extends ArrayObject or that implements Relay\GetArrayCopyInterface. The RelayBuilder will then get an array copy of that queue object for the Relay.

For example, first instantiate a RelayBuilder with an optional $resolver

use Relay\RelayBuilder;

$relayBuilder = new RelayBuilder($resolver);

… then instantiate a Relay where $queue is an array, an ArrayObject, or a Relay\GetArrayCopyInterface implementation:

/**
 * var array|ArrayObject|Relay\GetArrayCopyInterface $queue
 */
$relay = $relayBuilder->newInstance($queue);

You can then use the $relay as described above.

Traversable

In Relay 1.1, the RelayBuilder will also accept any Traversable implementation, and convert it to an array using iterator_to_array(). For backwards compatibility with Relay 1.0, iterator_to_array() is only called on a Traversable that is not an ArrayObject and that does not implement Relay\GetArrayCopyInterface.

Reusable Relays

If you wish, you can reuse the same Relay object multiple times. The same middleware queue will be used each time you invoke that Relay. For example, if you are making multiple client requests:

/**
 * @var Psr\Http\Message\ResponseInterface $response1
 * @var Psr\Http\Message\ResponseInterface $response2
 * @var Psr\Http\Message\ResponseInterface $responseN
 */
$request1 = ...;
$response1 = $relay($request1, $response1);

$request2 = ...;
$response2 = $relay($request2, $response2);

// ...

$requestN = ...;
$responseN = $relay($requestN, $responseN);

If you are certain that you will never reuse the Relay instance, you can instantiate a single-use Runner to avoid some minor overhead.

/**
 * @var array $queue The middleware queue.
 * @var callable|null $resolver An optional queue entry resolver.
 * @var Psr\Http\Message\RequestInterface $request The HTTP request.
 * @var Psr\Http\Message\ResponseInterface $response The HTTP response.
 */
use Relay\Runner;

$runner = new Runner($queue, $resolver);
$response = $runner($request, $response);