Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Stack (PHP Benelux 2015)

Stack (PHP Benelux 2015)

Stack is a convention for composing HttpKernelInterface middlewares. By following Stack's conventions you can add behavior and functionality to any application based on Symfony's HttpKernelInterface. This means Stack middlewares can be applied to Silex, Laravel 4, and Drupal 8 applications in addition to any other HttpKernelInterface based application. Learn the conventions, see community middlewares, and find out how to get started with Stack.

Beau Simensen

January 23, 2015
Tweet

More Decks by Beau Simensen

Other Decks in Programming

Transcript

  1. February 1st, 2013 <simensen> igorw: what is the closest analogy

    to middlewares is there in silex/symfony? anything? <igorw> simensen: what kind of middlewares? <simensen> like ruby rack middlewares or python middlewares. <igorw> httpkernel decorators ... <igorw> I guess you can think of it like a stack of httpkernels?
  2. • A Stack middleware MUST implement the HttpKernelInterface. • A

    Stack middleware MAY delegate to a decorated HttpKernelInterface instance. • A Stack middleware MUST take an HttpKernelInterface as its first constructor argument. Good old RFC 2119...
  3. :(

  4. :)

  5. class Passthru implements HttpKernelInterface { private $app; public function __construct(HttpKernelInterface

    $app) { $this->app = $app; } public function handle( Request $request, $type = self::MASTER_REQUEST, $catch = true ) { return $this->app->handle($request, $type, $catch) } }
  6. class Passthru implements HKI { private $app; public function __construct(HKI

    $app) { $this->app = $app; } public function handle($request) { return $this->app->handle($request) } }
  7. class Passthru implements HKI { private $app; public function __construct(HKI

    $app) { $this->app = $app; } public function handle($request) { // Request / before ! // Delegate $response = $this->app->handle($request) ! // Response /after return $response; } }
  8. class Passthru implements HKI { private $app; public function __construct(HKI

    $app) { $this->app = $app; } public function handle($request) { // Request / before ! // Delegate $response = $this->app->handle($request) ! // Response /after return $response; } }
  9. class Application implements HttpKernelInterface { public function run(SymfonyRequest $request =

    null) { $request = $request ?: $this['request']; ! $response = with($stack = $this->getStackedClient())->handle($request); ! $response->send(); ! $stack->terminate($request, $response); } ! protected function getStackedClient() { $sessionReject = $this->bound('session.reject') ? $this['session.reject'] : null; ! $client = (new \Stack\Builder) ->push('Illuminate\Cookie\Guard', $this['encrypter']) ->push('Illuminate\Cookie\Queue', $this['cookie']) ->push('Illuminate\Session\Middleware', $this['session'], $sessionReject); ! $this->mergeCustomMiddlewares($client); ! return $client->resolve($this); } }
  10. class StackedKernelPass implements CompilerPassInterface { public function process(ContainerBuilder $container) {

    if (!$container->hasDefinition('http_kernel_factory')) { return; } ! $http_kernel_factory = $container->getDefinition('http_kernel_factory'); $middleware_priorities = array(); $middleware_arguments = array(); foreach ($container->findTaggedServiceIds('http_middleware') as $id => $attributes) { $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; $middleware_priorities[$id] = $priority; $definition = $container->getDefinition($id); $middleware_arguments[$id] = $definition->getArguments(); array_unshift($middleware_arguments[$id], $definition->getClass()); } array_multisort($middleware_priorities, SORT_DESC, $middleware_arguments, SORT_DESC); ! foreach ($middleware_arguments as $id => $push_arguments) { $http_kernel_factory->addMethodCall('push', $push_arguments); } } }
  11. class Inline implements HKI { public function __construct(HKI $app, $callable)

    { $this->app = $app; $this->callable = $callable; } public function handle($request) { return call_user_func($this->callable, $this->app); } }
  12. $app = new Silex\Application(); ! $app->get('/', function (Request $request) {

    if ('success' === $request->attributes->get('callable_middleware')) { return new Response('SUCCESS'); } ! return new Response('FAILED', 500); }); ! $inlineMiddleware = function(HKI $app) { $request->attributes->set('callable_middleware', 'success'); $response = $app->handle($request, $type, $catch); $response->setContent('['.$response->getContent().']'); return $response; }; ! $stack = (new Stack\Builder()) ->push('Stack\Inline', $inlineMiddleware); ! $app = $stack->resolve($app);
  13. class Session implements HKI { public function __construct(HKI $app, $session)

    { $this->app = $app; $this->session = $session; } public function handle($request) { $request->setSession($this->session); $this->session->setId($request->cookies->get($this->session->getName())); ! $response = $this->app->handle($request); ! if ($this->session && $this->session->isStarted()) { $response->headers->setCookie(new Cookie( $session->getName(), $session->getId(), /* ... */ )); } ! return $response; } }
  14. class Session implements HKI { public function __construct(HKI $app, $session)

    { $this->app = $app; $this->session = $session; } public function handle($request) { $request->setSession($this->session); $this->session->setId($request->cookies->get($this->session->getName())); ! $response = $this->app->handle($request); ! if ($this->session && $this->session->isStarted()) { $response->headers->setCookie(new Cookie( $session->getName(), $session->getId(), /* ... */ )); } ! return $response; } }
  15. class Session implements HKI { public function __construct(HKI $app, $session)

    { $this->app = $app; $this->session = $session; } public function handle($request) { $request->setSession($this->session); $this->session->setId($request->cookies->get($this->session->getName())); ! $response = $this->app->handle($request); ! if ($this->session && $this->session->isStarted()) { $response->headers->setCookie(new Cookie( $session->getName(), $session->getId(), /* ... */ )); } ! return $response; } }
  16. class Session implements HKI { public function __construct(HKI $app, $session)

    { $this->app = $app; $this->session = $session; } public function handle($request) { $request->setSession($this->session); $this->session->setId($request->cookies->get($this->session->getName())); ! $response = $this->app->handle($request); ! if ($this->session && $this->session->isStarted()) { $response->headers->setCookie(new Cookie( $session->getName(), $session->getId(), /* ... */ )); } ! return $response; } }
  17. class Session implements HKI { public function __construct(HKI $app, $session)

    { $this->app = $app; $this->session = $session; } public function handle($request) { $request->setSession($this->session); $this->session->setId($request->cookies->get($this->session->getName())); ! $response = $this->app->handle($request); ! if ($this->session && $this->session->isStarted()) { $response->headers->setCookie(new Cookie( $session->getName(), $session->getId(), /* ... */ )); } ! return $response; } }
  18. $app = new Silex\Application(); ! $app->get('/login', function (Request $request) {

    $session = $request->getSession(); $username = $request->server->get('PHP_AUTH_USER'); $password = $request->server->get('PHP_AUTH_PW'); if ('igor' === $username && 'password' === $password) { $session->set('user', array('username' => $username)); return new RedirectResponse('/account'); } return new Response('Please sign in.', 401, [ 'WWW-Authenticate' => sprintf('Basic realm="%s"', 'site_login'), ]); }); ! $app->get('/account', function (Request $request) { $session = $request->getSession(); if (null === $user = $session->get('user')) { return new RedirectResponse('/login'); } return sprintf('Welcome %s!', $user['username']); }); ! $stack = (new Stack\Builder()) ->push('Stack\Session'); ! $app = $stack->resolve($app);
  19. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  20. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  21. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  22. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  23. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  24. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  25. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  26. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  27. class UrlMap implements HKI { public function __construct(HKI $app, $map)

    { $this->app = $app; $this->map = $map; } public function handle($request) { $pathInfo = rawurldecode($request->getPathInfo()); foreach ($this->map as $path => $app) { if (0 === strpos($pathInfo, $path)) { $server = $request->server->all(); $server['SCRIPT_FILENAME'] = $server['SCRIPT_NAME'] = $server['PHP_SELF'] = $request->getBaseUrl().$path; $attributes = $request->attributes->all(); $attributes['stack.url_map.prefix'] = $request->getBaseUrl().$path; $newRequest = $request->duplicate(null, null, $attributes, null, null, $server); return $app->handle($newRequest, $type, $catch); } } return $this->app->handle($request); } }
  28. $app = new Application(); $app->get('/', function () { return "Main

    Application!"; }); ! $blog = new Application(); $blog->get('/', function () { return "This is the blog!"; }); ! $forum = new Application(); $forum->get('/', function () { return "This is the forum!"; }); ! $map = [ "/blog" => $blog, "/forum" => $forum, ]; ! $stack = (new Stack\Builder()) ->push('Stack\UrlMap', $map); ! $app = $stack->resolve($app);
  29. class HttpCache implements HKI { public function __construct(HKI $app, StoreInterface

    $store) { $this->app = $app; $this->store = $store; } public function handle($request) { if ($this->store->isStillCached($request)) { $response = $this->store->getFromCache($request); } else { $response = $this->app->handle($request); } ! $response = $this->app->handle($request); ! if ($this->store->shouldCache($request, $response)) { $this->store->cache($request, $response); } ! return $response; } }
  30. class GeoIp implements HKI { public function __construct(HKI $app, $geocoder,

    $header = 'X-Country') { $this->app = $app; $this->geocoder = $geocoder; $this->header = $header; } public function handle($request) { $results = $this->geocoder->geocode($request->getClientIp()); if ($country = $results->getCountryCode()) { $request->headers->set($this->header, $country, true); } return $this->app->handle($request); } }
  31. $app = new Silex\Application(); ! $app->get('/', function(Request $request) { $ip

    = $request->getClientIp(); $country = $request->headers->get('X-Country', 'UNKNOWN'); ! return new Response($ip . ' => '. $country, 200); }); ! $stack = (new Stack\Builder()) ->push('Geocoder\Stack\GeoIp'); ! $app = $stack->resolve($app);
  32. $app = new Silex\Application(); ! $app->get('/', function(Request $request) { $ip

    = $request->getClientIp(); $country = $request->headers->get('X-Country', 'UNKNOWN'); ! return new Response($ip . ' => '. $country, 200); }); ! $blockAllButUs = function (HKI $app, $request) { if ($request->headers->get('X-Country') != 'US') { return new RedirectResponse('http://google.com); } return $app->handle($request); }; ! $stack = (new Stack\Builder()) ->push('Geocoder\Stack\GeoIp') ->push('Stack\Inline', $blockAllButUs); ! $app = $stack->resolve($app);
  33. class Backstage implements HKI { public function __construct(HKI $app, $path)

    { $this->app = $app; $this->path = $path; } ! public function handle($request) { $path = realpath($this->path); ! if (false !== $path) { return new Response(file_get_contents($path), 503); } ! return $this->app->handle($request); } }
  34. $app = new Silex\Application(); ! $app->get('/', function () { return

    'my app is working'; }); ! $stack = (new Stack\Builder()) ->push('Atst\StackBackstage', __DIR__.'/maintenance.html'); ! $app = $stack->resolve($app);
  35. $ echo "<h1>Down for Maintenance</h1>" > /path/to/your/app/maintenance.html ! # do

    your thang ! $ rm /path/to/your/app/maintenance.html
  36. • HTTP Cache • CookieGuard • GeoIp • IpRestrict •

    Backstage • OAuth • Basic Authentication • Hawk • CORS • Robots • Negotiation • Honeypot • Logger • ... and more
  37. • PHP-FIG – http://www.php-fig.org/ • PSR-7 HTTP Message Interface –

    https://github.com/php-fig/fig- standards/blob/master/proposed/http-message.md • phly/http – https://github.com/phly/http • ply/conduit – https://github.com/phly/conduit • stacker – https://github.com/Crell/stacker (an experimental PSR-7 implementation of Stack) • Laravel 5 middlewares – http://mattstauffer.co/blog/laravel-5.0- middleware-filter-style
  38. Stack-1 Core • Middlewares MUST implement the HttpKernelInterface. • Middlewares

    MAY delegate to the decorated HttpKernelInterface. • Middlewares MUST take an HttpKernelInterface as its first constructor argument. Good old RFC 2119...
  39. WIP Stack-1 Core • Middlewares MUST implement the HttpKernelInterface. •

    Middlewares MAY delegate to the decorated HttpKernelInterface. • Middlewares MUST take an HttpKernelInterface as its first constructor argument. Good old RFC 2119...
  40. dflydev/stack-authentication A collection of Stack middlewares designed to help authentication

    middleware implementors adhere to the STACK-2 Authentication conventions.