Building middleware applications with Zend Framework 3

by Enrico Zimuel / @ezimuel
Senior Software Engineer
Zend Technologies, a Rogue Wave Company

Oct 18, ZendCon 2016, Las Vegas

About me

HTTP is the foundation
of the web

  • A client sends a request
  • A server returns a response

HTTP messages


GET /path HTTP/1.1
Accept: application/json


HTTP/1.1 200 OK
Content-Type: application/json


Frameworks model messages

But every framework does it differently.

$method = $request->getMethod();
$method = $request->getRequestMethod();
$method = $request->method;


Shared HTTP Message Interfaces


$method     = $request->getMethod();
$accept     = $request->getHeader('Accept');
$path       = $request->getUri()->getPath();
$controller = $request->getAttribute('controller');


$response->getBody()->write('Hello world!');
$response = $response->withStatus(200, 'OK')
            ->withHeader('Content-Type', 'text/plain');


A function that gets a request and generates a response

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

function (Request $request, Response $response) {
  // manipulate $request to generate a $response
  return $response;

Web application

Middleware app

Middleware in zend-expressive

Use an additional callable during the invoke ($next)

class Middleware
  public function __invoke(
    \Psr\Http\Message\ServerRequestInterface $request,
    \Psr\Http\Message\ResponseInterface $response,
    callable $next = null
  ) {
    // do something before
    if ($next) {
        $next($request, $response);
    // do something after
    return $response;


  • PSR-7 support (using zend-diactoros)
  • Middleware using a callable ($next):
    function ($request, $response, $next)
  • Piping workflow (using zend-stratigility)
  • Features: routing, container-interop, templating, error handling
  • Stable version 1.0 (28 Jan 2016)

Components layer

Flow overview

Basic example

use Zend\Expressive\AppFactory;

require 'vendor/autoload.php';

$app = AppFactory::create();
$app->get('/', function ($request, $response, $next) {
  $response->getBody()->write('Hello, world!');
  return $response;



namespace Zend\Expressive\Router;
use Psr\Http\Message\ServerRequestInterface as Request;

interface RouterInterface
  public function addRoute(Route $route);
  public function match(Request $request);
  public function generateUri($name, array $substitutions = []);

Routing example

// $app is an instance of Zend\Expressive\AppFactory

$app->get('/', function ($request, $response, $next) {
  $response->getBody()->write('Hello, world!');
  return $response;

Piping Middleware

// $app is an instance of Zend\Expressive\AppFactory

// Executed in all the requests
$app->pipe('middleware service name');

// Pipe to a specific URL
$app->pipe('/api', $apiMiddleware);
$app->pipe('/api', 'middleware service name');

// Error handler
$app->pipeErrorHandler('error handler service name');
$app->pipeErrorHandler('/api', 'error handler service name');

Using a Service Container

use Zend\Expressive\AppFactory;
use Zend\ServiceManager\ServiceManager;

$container = new ServiceManager();
$container->setFactory('HelloWorld', function ($container) {
  return function ($request, $response, $next) {
    $response->write('Hello, world!');
    return $response;

$app = AppFactory::create($container);
$app->get('/', 'HelloWorld');

We support container-interop


  • While Expressive does not assume templating is being used, it provides a templating abstraction.
  • Default adapters: Plates, Twig, Zend-View

namespace Zend\Expressive\Template;

interface TemplateRendererInterface
  public function render($name, $params = []);
  public function addPath($path, $namespace = null);
  public function getPaths();
  public function addDefaultParam($templateName, $param, $value);

Error Handling

  • Expressive provides error handling out of the box, via zend-stratigility's FinalHandler
  • This pseudo-middleware is executed in the following conditions:
    • If the middleware stack is exhausted, and no middleware has returned a response
    • If an error has been passed via $next(), but not handled by any error middleware

Templated Errors

use Zend\Expressive\Application;
use Zend\Expressive\Plates\PlatesRenderer;
use Zend\Expressive\TemplatedErrorHandler as TErrHandler;

$plates = new PlatesRenderer();
$plates->addPath(__DIR__ . '/templates/error', 'error');
$final = new TErrHandler($plates, 'error::404', 'error::500');

$app = new Application($router, $container, $final);

Using Whoops



Use Vagrant to set up the environment.

Clone the repository ezimuel/zend-expressive-workshop

git clone

Run composer:

cd zend-expressive-workshop
composer install

Turn on Vagrant:

vagrant up

The PHP server runs at localhost:8080

Application tree

├── config
│   └── autoload
├── data
│   └── cache
├── public
├── src
│   └── App
│       └── Action
├── templates
│   ├── app
│   ├── error
│   └── layout
└── test


The application configuration files, including:

  • the registered services (middleware, db, etc);
  • the template file paths;
  • the error handler;

Front Controller

public/index.php (programmatic version)

$container = require 'config/container.php';
$app = $container->get(Application::class);

// Pipeline
// here add custom middleware ...

// Routes
$app->get('/', Action\HomePageAction::class, 'home');
$app->get('/api/ping', Action\PingAction::class, '');

Middleware Factory


class HomePageFactory
  public function __invoke(ContainerInterface $container)
    $router   = $container->get(RouterInterface::class);
    $template = ($container->has(TemplateRendererInterface::class))
      ? $container->get(TemplateRendererInterface::class)
      : null;

    return new HomePageAction($router, $template);

Middleware Action


class HomePageAction
  private $router;
  private $template;

  public function __construct(
    Router\RouterInterface $router,
    Template\TemplateRendererInterface $template = null
    $this->router   = $router;
    $this->template = $template;

  public function __invoke(
    ServerRequestInterface $request,
    ResponseInterface $response,
    callable $next = null
    $data = [];
    // insert value into $data
    return new HtmlResponse($this->template->render(

Build a conference website

  • Publish 3 web pages: speakers, talks, and schedule
  • The data are stored in data/db.sqlite3
  • We want to have the following URLs:
    • /speaker[/{id:}]
    • /talk[/{id:}]
    • /schedule

Exercise 1

  • Add routes for speakers, talks, and schedule
  • Add links to navbar in templates\layout\default.phtml

Solution 1

You can check out a solution in exercise1 branch:

git checkout exercise1

Model Layer

We use a Model layer to interact with the database

We built a Speaker and Talk classes in src/App/Model

Exercise 2

  • Build the Talk Action and Factory
  • Build the pages to resolve /talk and talk[/{id:\d+}] URLs
  • Note: have a look at the SpeakerAction and templates

Solution 2

You can check out a solution in exercise2 branch:

git checkout exercise2

Exercise 3

  • Build the Schedule page

Solution 3

You can check out a solution in exercise3 branch:

	git checkout exercise3


Please rate this workshop at: