class: main-title

Conclusion :

Un middleware est quelque chose qui prend une requête et retourne une réponse.


class: profile

.profile-picture[ ]

Matthieu Napoli github.com/mnapoli

.company-logo[ ]



middle-what ?


class: main-title

Un middleware est quelque chose qui prend une requête et retourne une réponse.


Singleton


class: main-title

Un singleton est une classe qui a une seule instance.


Accéder à d’autre composants (dépendances)

  • singleton
  • variables globales
  • service locator/registry
  • proxy statique (“facades” Laravel)
  • injection de dépendances

class: main-title

Un middleware est quelque chose qui prend une requête et retourne une réponse.


class: main-title

Architecture d’applications HTTP

« quand, comment et quoi est appelé ? »


  • routing

– - authentification/firewall - logging - cache - headers de cache HTTP - session - page de maintenance - assets/medias - rate limiting - forcer HTTPS - restriction par IP - content negotiation - language negotiation - …


class: title

Symfony


try {
    $event = new Event(...);
    $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
    if ($event->hasResponse()) {
        return $event->getResponse();
    }

    $controller = $request->attributes->get('_controller');
    $controllerArguments = /* resolve arguments */;

    $response = call_user_func_array($controller, $controllerArguments);
} catch (\Exception $e) {
    $response = /* generate error response (error page) */;
}

class: title

Events


class: title

Stack

2013


$kernel = new AppKernel();

$request = Request::createFromGlobals();

$response = $kernel->handle($request);

$response->send();

class: main-title

Un middleware est quelque chose qui prend une requête et retourne une réponse.


interface HttpKernelInterface
{
    /**
     * @return Response
     */
    public function handle(Request $request, ...);
}

Stack stackphp.com


class Middleware implements HttpKernelInterface
{
    public function __construct(HttpKernelInterface $next)
    {
        $this->next = $next;
    }

    public function handle(Request $request, …)
    {
        // do something before
        
        if (/* I want to */) {
            $response = $this->next->handle($request, …);
        } else {
            $response = new Response('Youpida');
        }
        
        // do something after
        
        return $response;
    }
}

class LoggerMiddleware implements HttpKernelInterface
{
    public function __construct(HttpKernelInterface $next)
    {
        $this->next = $next;
    }

    public function handle(Request $request, …)
    {
        $response = $this->next->handle($request, …);
        
        // write to log
        
        return $response;
    }
}

$kernel = new LoggerMiddleware(
    new AppKernel()
);

$request = Request::createFromGlobals();

$response = $kernel->handle($request);

$response->send();

class: center-image

Onion style

.small[ stackphp.com ]


$kernel = new HttpCache(
    new CorsMiddleware(
        new LoggerMiddleware(
            new AppKernel()
        ),
    ),
    new Storage(...)
);

  • utilisation complexe
  • hors de l’application
  • spécifique Symfony

class: title

PSR-7

Mai 2015


PSR-7

composer require psr/http-message
  • RequestInterface
  • ServerRequestInterface
  • ResponseInterface

class: title

Callable


PHP callables

.left-block[ ```php function foo() { … }

function () { … }

class Foo { public function bar() { … } }

class Foo { public function __invoke() { … } } ] .right-block[ php $callable = ‘foo’;

$callable = function () { … }

$callable = [new Foo(), ‘bar’];

$callable = new Foo();

$callable(); ``` ]


use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

class LoggerMiddleware
{
    public function __construct(callable $next)
    {
        $this->next = $next;
    }

    public function __invoke(ServerRequestInterface $request)
    {
        $next = $this->next;
        $response = $next($request);
        
        // write to log
        
        return $response;
    }
}

class LoggerMiddleware
{
    public function __invoke(ServerRequestInterface $request, callable $next)
    {
        $response = $next($request);
        
        // write to log
        
        return $response;
    }
}

$middleware = function (ServerRequestInterface $request, callable $next) {
    $response = $next($request);
    
    // write to log
    
    return $response;
}

“Middleware PSR-7”

$middleware = function ($request, $response, callable $next) {
    $response = $next($request, $response);
    
    // write to log
    
    return $response;
}

$middleware = function (ServerRequestInterface $request, callable $next) {
    $response = $next($request);
    
    // write to log
    
    return $response;
}

$logger = function (ServerRequestInterface $request, callable $next) {
    $response = $next($request);
    
    // write to log
    
    return $response;
}

$errorHandler = function (ServerRequestInterface $request, callable $next) {
    try {
        return $next($request);
    } catch (\Throwable $e) {
        return new TextResponse('Oops!', 500);
    }
}

// ?

class: title

Pipe


$ cat access.log | grep 404 | awk '{ print $7 }' | sort | uniq -c | sort

class: center-image


.left-block[ php $pipe = new Pipe([ function ($request, $next) { ... }, function ($request, $next) { ... }, function ($request, $next) { ... }, ]);

.small[ Pipe.php ] ]

.right-block[ ```php $pipe = new Pipe();

$pipe->pipe(function ($request, $next) { … }); $pipe->pipe(function ($request, $next) { … }); $pipe->pipe(function ($request, $next) { … }); ``` ]


class: main-title

Un middleware est quelque chose qui prend une requête (et $next) et retourne une réponse.


class: main-title

Un middleware est quelque chose qui prend une requête et retourne une réponse.


$pipe = new Pipe([

    function ($request, $next) { // error handler
        try {
            return $next($request);
        } catch (\Throwable $e) {
            return new TextResponse('Oops!', 500);
        }
    },
    
    function ($request, $next) { // logger
        $response = $next($request);
        
        // write to log
        
        return $response;
    },
    
]);

$pipe = new Pipe([
    new ErrorHandler(),
    new Logger(),
]);

$router = new Router([
    '/' => /* controller */,
    '/about' => /* controller */,
]);

$response = $router->route($request);

class: main-title

Un middleware est quelque chose qui prend une requête et retourne une réponse.


class: center-image


$pipe = new Pipe([
    new ErrorHandler(),
    new Logger(),
    new Router([
        '/' => /* controller */,
        '/about' => /* controller */,
    ]),
]);

class: title

Frameworks


Zend Expressive/ZF3

$app = Zend\Expressive\AppFactory::create();

$app->pipe(function (...) {
    // middleware
});
$app->pipe(new MyMiddleware());
$app->pipe('nom-de-service');

$app->get('/', function () {
    // controller
});

// ...
$app->run();

Slim

$app = new Slim\App();

$app->add(function (...) {
	// middleware
});

$app->get('/', function () {
	// controller
})->add(function (...) {
    // route middleware
});

class: center-image


Laravel

class MyMiddleware
{
    public function handle(Request $request, $next)
    {
        // ...
    }
}

class Kernel extends HttpKernel
{
    protected $middleware = [
        MyMiddleware::class,
    ];
    
    ...
}

class: title

Middlewares


class: center-image

github.com/oscarotero/psr7-middlewares


class Authentication
{
    public function __invoke($request, $next)
    {
        $auth = /* get token from headers */;
        
        $user = /* find user by token */;
        
        if (!$user) {
            return new Response('YOU SHALL NOT PASS!', 403);
        }
            
        $request = $request->withAttribute('user', $user);

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

PSR-15

class MyMiddleware implements MiddlewareInterface
{
    public function process(RequestInterface $request, DelegateInterface $delegate)
    {
        return $delegate->next($request);
    }
}

class: title

Architecture


$application = new Pipe([
    new ErrorHandler(),
    new ForceHttps(),
    new MaintenanceMode(),
    new SessionMiddleware(),
    new DebugBar(),
    new Authentication(),
    
    new Router([
        '/' => /* controller */,
        '/article/{id}' => /* controller */,
        '/api/articles' => /* controller */,
        '/api/articles/{id}' => /* controller */,
    ]),
]);

$website = new Pipe([
    new ErrorHandler(),
    new ForceHttps(),
    new MaintenanceMode(),
    new SessionMiddleware(),
    new DebugBar(),
    new Router([
        '/' => /* controller */,
        '/article/{id}' => /* controller */,
    ]),
]);
$api = new Pipe([
    new ErrorHandler(),
    new ForceHttps(),
    new Authentication(),
    new Router([
        '/api/articles' => /* controller */,
        '/api/articles/{id}' => /* controller */,
    ]),
]);

$application = new Router([
    '/api/{.*}' => new Pipe([
        new ErrorHandler(),
        new ForceHttps(),
        new Authentication(),
        new Router([
            '/api/articles' => /* controller */,
            '/api/articles/{id}' => /* controller */,
        ]),
    ]),
    '/{.*}' => new Pipe([
        new ErrorHandler(),
        new ForceHttps(),
        new MaintenanceMode(),
        new SessionMiddleware(),
        new DebugBar(),
        new Router([
            '/' => /* controller */,
            '/article/{id}' => /* controller */,
        ]),
    ]),
]);

$application = new PrefixRouter([
    '/api/' => new Pipe([
        new ErrorHandler(),
        new ForceHttps(),
        new Authentication(),
        new Router([
            '/api/articles' => /* controller */,
            '/api/articles/{id}' => /* controller */,
        ]),
    ]),
    '/' => new Pipe([
        new ErrorHandler(),
        new ForceHttps(),
        new MaintenanceMode(),
        new SessionMiddleware(),
        new DebugBar(),
        new Router([
            '/' => /* controller */,
            '/article/{id}' => /* controller */,
        ]),
    ]),
]);

$application = new Pipe([
    new ErrorHandler(),
    new ForceHttps(),
    new PrefixRouter([
        '/api/' => new Pipe([
            new Authentication(),
            new Router([
                '/api/articles' => /* controller */,
                '/api/articles/{id}' => /* controller */,
            ]),
        ]),
        '/' => new Pipe([
            new MaintenanceMode(),
            new SessionMiddleware(),
            new DebugBar(),
            new Router([
                '/' => /* controller */,
                '/article/{id}' => /* controller */,
            ]),
        ]),
    ]),
]);

$expressive = Zend\Expressive\AppFactory::create();
$expressive->...

$slim = new Slim\App();
$slim->...

$application = new PrefixRouter([

    '/dashboard/' => $slim,
    
    '/api/' => $expressive, // ou API Platform ? ou Zend Apigility ?
    
    '/admin/' => function ($request) {
        set_global_variables($request);
        ob_start();
        $run_legacy_application();
        $html = ob_get_clean();
        return new HtmlResponse($html);
    },
]);

class: main-title

Votre application

Votre architecture


class: main-title

Conclusion :

Un middleware est quelque chose qui prend une requête et retourne une réponse.