Easy PSR-7 with Zend Diactoros

Aaron Weatherall
5 min readJun 6, 2017

--

It’s no mystery that I’m a fan (most-of-the-time) of the Zend Framework — heck I’m a Zend Framework 2 Certified System Architect (Phew! Big title). But as a crusader for decoupled libraries I was beginning to be disillusioned by the gentle giant and it’s enormous spiderweb of dependancies.

Then came ZF3.

The new version of the framework was really a game changer — the team has put incredible work into decoupling ALL of the packages into light, standalone components.

Each components is now cleaner, leaner and less reliant on other Zend libraries which means they’re 200% more awesome.

The once heavily-coupled monolithic library is now, like it’s counterpart Symfony, a huge collection of lean, fully-decoupled components with exceptional test coverage and documentation.

Though Zend have some great production-ready standalone offerings such as Apigility and Expressive, I still love the thrill of building my own lean applications and learning how these components work with others.

Under the hood, Expressive uses two core Zend components.

  • Zend Stratigility — according to the description, Stratigility is a port of Sencha Connect to PHP. It allows you to create and dispatch middleware pipelines. Although it’s a well put-together component, I’m much more of a fan of the leaner League Pipeline library (which does the same thing with a smaller footprint).
  • Zend Diactoros — is a PHP package containing implementations of the accepted PSR-7 HTTP message interfaces, as well as a "server" implementation similar to node's http.Server — and the focus of todays article!

Diactor-what?

According to the documentation
https://zendframework.github.io/zend-diactoros/overview/

Diactoros (pronunciation: /dɪʌktɒrɒs/) is an epithet for Hermes, meaning literally, "the messenger."

Diactoros exists to:

  • to provide a proof-of-concept of the accepted PSR HTTP message interfaces with relation to server-side applications.
  • to provide a node-like paradigm for PHP front controllers.
  • to provide a common methodology for marshalling a request from the server environment

How do we get started?

Before we get started playing with Diactoros, we really want to require it into our code using Composer.

$ composer require zendframework/zend-diactoros

Let’s write some code

To get started with Diactoros, we need to pass all of the super-globals to a factory to generate a ServerRequest object.

This request implements the PSR-fig ServerRequestInterface which creates an immutable object for handling our request.

// Using the createServer factory, providing it with super-globals:
$server = Zend\Diactoros\Server::createServer(
new RequestHandler(),
$_SERVER,
$_GET,
$_POST,
$_COOKIE,
$_FILES
);

In this example, the first argument is a callable which means either an object with an __invoke() method or a closure.

The rest of the arguments are super-globals that should be self-explanatory.

Shh… Listen

Now that we have a Server, we need to get the server to listen.

The listener takes a single optional argument which is a callable (explained above) — this becomes the FinalHandler and will be called after the RequestHandler has finished.

This is useful for catching other errors that weren’t caught in the handler or modifying the content before it’s sent.

As you can see in the example below, if there is no error passed to the callback, the response is returned as normal.

$server->listen(function ($request, $response, $error = null) {
// Final Handler
if (! $error) {
return $response;
}

// Handle extra errors etc here
});

Behind the scenes, listen will emit the response at the end of the chain.

Response Objects

Diactoros ships with an array of standard response objects that should fit almost all of your needs.. OR roll your own using the ResponseInterface.

Check the documentation for more information on available response.
https://zendframework.github.io/zend-diactoros/custom-responses/

  • TextResponse
    $response = new TextResponse('Hello world!');
    The default ContentType is text/plain.
  • HtmlResponse
    $response = new HtmlResponse($htmlContent);
    The default ContentType is text/html.
  • JsonResponse
    $response = new JsonResponse($data);
    The default ContentType is application/json.
  • EmptyResponse
    $response = new EmptyResponse(204);
    Note: These are SUPER useful for created (201), deleted(204) and accepted(202) which don’t require a response body as per the RFC.
  • Redirect
    $response = new RedirectResponse('/user/login', 301);

How would I implement routing?

For ultra-lean routing, you probably can’t beat FastRoute. https://github.com/nikic/FastRoute

Using the request handler that we defined above, we can easily feed FastRoute with our request object to hit the right page/content.

Using a simple config file with routes, we can generate a route handler using the RouteCollector or to make it even leaner, you can declare them in a more concrete way in the RequestHandler.

$dispatcher = FastRoute\simpleDispatcher(
function(FastRoute\RouteCollector $r) use ($config){
foreach ($config['routes'] as $route) {
$r->addRoute(
$route['method'],
$route['pattern'],
new $route['class']
);
}
}
);

The RequestHandler then uses the PSR7 Request object to get the method and path and pass them to the dispatcher. As you can see below, we either return a valid callback when a route is matched OR we return a JsonResponse object.

/**
*
@param ServerRequest $request
*
@param Response $response
*
@param $next
*
@return Response
*/
public function __invoke(
ServerRequest $request,
Response $response,
callable $next
){
$routeInfo = $this->dispatcher->dispatch(
$request->getMethod(),
(string) $request->getUri()->getPath()
);
switch ($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
return new JsonResponse(
["message" => "Oh no it wasn't found"],
404
);
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
return new JsonResponse(
["message" => "You can't do that!"],
405
);
break;
case FastRoute\Dispatcher::FOUND:
/** @var Response $response */
return
call_user_func_array(
$routeInfo[1],
[$request, $response, $next]
);
break;
}
}

Obviously you’d clean this up and dry it out before dropping it in production, but you get the idea!

Where’s the Middleware?

Obviously Diactoros screams out to be integrated with a Middleware/Pipeline style framework.

What could you do?

  • You could add your own pipeline handler.
  • You could add your own routing.
  • You could add your own method handling.
  • You could add your own dependency injection etc.
  • You could then add your own templating engine.
Don’t reinvent the wheel! Smarter people than me probably built it!

But, to be honest, that wouldn’t be effective engineering.

Good engineers wouldn’t spend the time reinventing something that’s already been built into frameworks like Zend Expressive or the incredible and LEAN https://www.slimframework.com/.

Both of these take PSR7 standard request and responses and wrap it in awesome and extensible middleware and request/response handlers out of the box.

Wrap up

The more features you add, the less useful these standalone components become — Hopefully you can see how awesome this could be for simpler projects or for adding PSR7 functionality to a legacy system.

If you’re like me and you love to ‘roll-your-own’ framework for small projects and libraries, components like Zend Diactoros can help you build quality and scalable code in a very short amount of time.

If you’re looking for something more fully featured, don’t even hesitate in checking out Zend Expressive or my personal favourite, Slim Framework.

--

--

Aaron Weatherall

Passionate Product Owner, security fanatic, Senior Software Engineer, boat restorer, blogger, Melbournite and keen motorcyclist!