Build middleware applications with Zend Framework 3

by Enrico Zimuel / @ezimuel

Senior Software Engineer
Zend Technologies, a Rogue Wave Company

About me

Table of contents

  • ZF3: latest news
  • Middleware applications in PHP
  • PSR-7 standard
  • Middleware with zend-expressive
  • A skeleton application

Zend Framework 3

  • 50+ separate components on github
  • Support PSR-7 standard
  • Middleware and MVC patterns
  • Performance improvement: 4x faster than ZF2!
  • Optimized for PHP 7, but supporting PHP 5.5 onwards
  • Stats: 10,269 stars, 8,167 forks, 124 repos, 699 contributors

ZF3 roadmap

  • Performance improvement
  • PSR-7 support
  • New middleware components: Stratigility, Expressive, MiddlewareListener (MVC)
  • Enable forward compatibility with version 3
  • Updated documentation and website
  • Code refactor of MVC

Middleware

A function that gets a request and generates a response


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

Build a middleware workflow

1) Passing middleware to other middleware:


class Middleware
{
    protected $middleware;

    public function __construct(callable $middleware) {
        $this->middleware = $middleware;
    }

    public function __invoke($request, $response) {
        // do something before
        call_user_func($this->middleware, $request, $response);
        // do something after
    }
}

Build a middleware workflow

2) Use an additional callable during the invoke ($next)


class Middleware
{
    public function __invoke($request, $response, callable $next = null) {
        // do something before
        if ($next) {
            $next($request, $response);
        }
        // do something after
    }
}

The Middleware onion

HTTP is the foundation of the web

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

HTTP messages

Request


GET /path HTTP/1.1
Host: example.com
Accept: application/json
				            

Response


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

{"foo":"bar"}
				            

Frameworks model messages

But every framework does it differently.


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

PSR-7

Shared HTTP Message Interfaces

Request


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

Response


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

Zend-expressive

  • 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;

chdir(dirname(__DIR__));
require 'vendor/autoload.php';

$app = AppFactory::create();

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

$app->pipeRoutingMiddleware();
$app->pipeDispatchMiddleware();
$app->run();

Routing


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($apiMiddleware);
$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 ($req, $res, $next) {
        $res->write('Hello, world!');
        return $res;
    };
});

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

We support container-interop

Templating

  • 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;

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

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

Using Whoops

Skeleton application

Getting started


$ composer create-project zendframework/zend-expressive-skeleton <path>

Run the skeleton app


$ composer serve

You can browse to http://localhost:8080

Skeleton source code

A brief look at the source code...

Thanks!

Rate this talk at joind.in/talk/f6938

More info: zendframework.github.io/zend-expressive


Contact me: enrico [at] zend.com

Follow me: @ezimuel



Creative Commons License
This work is licensed under a
Creative Commons Attribution-ShareAlike 3.0 Unported License.
I used reveal.js to make this presentation.