Beyond PSR-7

The magical middleware tour

Marco

Explorer

Avid learner

Chocolate Lover

Steve

Enjoys Travelling

Software Architectures

In a few years...

Software Engineers

Start of the Journey...

Everybody on its own

A Total Mess


$filename = 'log.txt';
$handle = fopen($filename, 'a'));
fwrite($handle, $errorMessage);
fclose($handle);

$filename = 'log.txt';
$handle = fopen($filename, 'a'));
fwrite($handle, $errorMessage);
fclose($handle);

Libraries

Include Hell


include 'config.php';
include_once 'dbcon.php';
include_once 'logger.php';
include 'utils.php';
include 'forms.php';
include 'calculations.php';
include 'graphs.php';
include 'auth.php';

MVC Frameworks

IoC First Wave

Same things, different ways...

Zend Framework


$logger = new Zend_Log();
$writer = new Zend_Log_Writer_Stream('php://output');
$logger->addWriter($writer);
$logger->log('Hello PHPDay People!', Zend_Log::INFO);
                    

Symfony


// YAML Configuration
// [...]
sfContext::getInstance()->getLogger()->info('Hello PHPDay People!');
                    

Reinventing the Wheel

With a little help of...

composer.json

    
                {
    "name": "zendframework/skeleton-application",
    "description": "Skeleton Application for ZF2",
    "license": "BSD-3-Clause",
    "keywords": [
        "framework",
        "zf2"
    ],
    "homepage": "http://framework.zend.com/",
    "require": {
        "php": ">=5.5",
        "zendframework/zendframework": "~2.5"
    }
}

IoC New Wave

Microframeworks

One issue to solve...


namespace Symfony\Component\HttpFoundation;

class Request {
    public static function createFromGlobals(): Request {
        return self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);
    }
}
                    

namespace Zend\Http\PhpEnvironment;

class Request extends \Zend\Http\Request {
    public function __construct() {
        $this->setEnv(new Parameters($_ENV));
        $this->setQuery(new Parameters($_GET));
        $this->setPost(new Parameters($_POST));
        $this->setCookies(new Parameters($_COOKIE));
        $this->setFiles(new Parameters($this->mapPhpFiles()));
        $this->setServer(new Parameters($_SERVER));
    }
}
                    

Need for

a good HTTP abstraction

SERVER API

  • $_SERVER
  • $_POST
  • $_GET
  • header()
  • setCookie()
  • echo

CLIENT ADAPTERS

PSR-7

PSR-7 GOALS

Interfaces

Practical applications and usability

No limits

Server and client

PSR-7 NON-GOALS

Conformation

Impose details

A Value object is forever

Pipes and filters

Pizza Example

Decorator


interface Pizza

class Margherita implements Pizza

class CheeseDecoratedPizza implements Pizza
{
    function __construct(Pizza $pizza)
}

class VegetablesDecoratedPizza implements Pizza
{
    function __construct(Pizza $pizza)
}
                    

$myFavouritePizza =

    new VegatablesDecoratedPizza(

        new CheeseDecoratedPizza(

            new Margherita()

        )

    );
                    

Middleware

Middleware


function (Request request): Response
{
    ...

    response = next(request);

    ...

    return response;
}
                    

Middleware


function (Request): Response
                    

Middleware


function (Request, Response): Response
                    

Middleware


function (

    Request request,

    Response response,

    callable next

): Response
                    

Hot Week

Middleware in Action


class Middleware
{
    function __invoke($request, $response, $next)
    {
        if (!$this->preconditionsExist($request, $response)) {
            throw new RuntimeException();
        }

        $request = $this->doSomethingOnRequest($request);

        $response = $next($request, $response);

        return $this->doSomethingOnResponse($response);
    }
}
                    

class BasicAuthentication
{
    function __invoke($request, $response, $next)
    {
        $authorization = $request->getHeaderLine('Authorization');

        if ($this->checkUserPassword($authorization)) {
            $request = self::setAttribute(
                $request,
                'USERNAME',
                $authorization['username']
            );

            return $next($request, $response);
        }

        return $this->unauthorizedUserResponse($response);
    }
}
                    

class AccessLog
{
    function __invoke($request, $response, $next)
    {
        if (!self::hasAttribute($request, 'CLIENT_IPS')) {
            throw new RuntimeException();
        }

        $response = $next($request, $response);

        $message = $this->createMessage($request, $response);

        $this->logger->log($message);

        return $response;
    }
}
                    

Slim

Slim


// src/middleware.php

$app->add(new AccessLog($logger));

$app->add(new Geolocate());

$app->add(new ClientIp());

$app->add(new BasicAuthentication($users));
                    

Radar & Relay

Radar & Relay


// web/index.php

$adr->middle(new BasicAuthentication($users));

$adr->middle(new ClientIp());

$adr->middle(new Geolocate());

$adr->middle(new AccessLog($logger));

Expressive

Expressive


// config/autoload/middleware-pipeline.global.php
return [
    'middleware-pipeline' => [
        'basic_authentication' => [
            'middleware' => new BasicAuthentication($users),
            'priority' => 4000
        ],
        'clientip' => [
            'middleware' => ClientIp::class,
            'priority' => 3000
        ],
        'geolocate' => [
            'middleware' => Geolocate::class,
            'priority' => 2000
        ],
        'access-log' => [
            'middleware' => new AccessLog($logger),
            'priority' => 1000
        ]
    ]
];
                    

The magical Expressive tour


// config/autoload/middleware-pipeline.global.php
return [
    'middleware-pipeline' => [
        'always' => [
            'middleware' => [
                Helper\ServerUrlMiddleware::class
            ], 'priority' => 10000
        ],
        'routing' => [
            'middleware' => [
                ApplicationFactory::ROUTING_MIDDLEWARE,
                Helper\UrlHelperMiddleware::class,
                ApplicationFactory::DISPATCH_MIDDLEWARE
            ], 'priority' => 1
        ],
        'error' => [
            'middleware' => [], 'error' => true, 'priority' => -10000
        ]
    ]
];
                    

ReactPhp

Expressive/ReactPhp


// config/autoload/middleware-pipeline.global.php
return [
    'dependencies' => [
        'factories' => [
            React2Psr7\StaticFiles::class => React2Psr7\StaticFilesFactory::class,
        ]
    ],
    'middleware_pipeline' => [
        'static' => [
            'middleware' => React2Psr7\StaticFiles::class,
            'priority' => 100000, // Execute earliest!
        ],
        ...
    ]
];
                    

Use cases

Debug bar


class DebugBar
{
    public function __invoke($request, $response, $next)
    {
        if (!self::hasAttribute($request, FormatNegotiator::KEY)) {
            throw new RuntimeException('Need FormatNegotiator executed before');
        }

        if ($this->isAsset($request)) {
            return $this->responsewithAssetBody($request, $response);
        }
        $response = $next($request, $response);

        if (Utils\Helpers::isRedirect($response)) {
            $this->debugBar->stackData();
        } else if (FormatNegotiator::getFormat($request) === 'html') {
            $response = $this->createHtmlResponse($response);
        } else if (Utils\Helpers::isAjax($request)) {
            $response = $this->createAjaxResponse($response);
        }
        return $response;
    }
}
                    

More Available Middleware

  • Storage-Less Sessions
  • Device Detection
  • Analytics Support
  • Robot-Blocking
  • Request Rate Limiting
  • And More...

Roundup

PSR-7: A good HTTP abstraction

Abstractions VS Implementations

Demolishing the Silos

Beware of Runtime Dangers

Middleware is a Hot Topic

Thank you very much

Resources

Speakers love feedback

Leave your feedback at https://joind.in/talk/1ccba

All you need is middleware

Credits

Plane view by Victor Costan

Chocolate by John Loo

Orioles Fan by Keith Allison

Mosque by Fasihjee

Fans by Mirage Kale

Hippies by Roland Godefroy

Hippie Van by Joe Mabel

Students by Shimer College

Rave by EDM Playlist

Weird Bicicle by Thomas Guest

Suitcase found at publicdomainpictures.net

A320 model found at wesharepics.info

Beatles picture by Nationaal Archief

Beatles picture by United Press Intl.

Board by ericfleming8

Abstract painting by Earle M. Pilgrim

Figs by Mburnat

Kungsleden by Shyguy24x7

Danger zone by cvander

PSR-7 diagram by ninjagrl

Diamond by EWAR

Pizza by ElfQrin

Cheese and vegetables by StockSnap

Pizza by Scott bauer

Onion by Colin

Onion by Amada44

Cutting onion by Lali Masriera

Onion by darwinbell

Onion by costanzimarco

Onion by sarangib

Locked door by LEEROY.ca

Log by Greenpeace Finland