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

Hello, PSR-7. (Symfony Live! London 2015)

Beau Simensen
September 18, 2015

Hello, PSR-7. (Symfony Live! London 2015)

PSR-7 HTTP Message Interface has been kicking around in one form or another since March of 2012. Get a brief history of how PSR-7 evolved from its humble beginnings as a simple HTTP client interface to the game-changing PHP-FIG proposal we have today. Learn how to work with the HTTP Message Interfaces and how they will be used by frameworks in the not to distant future. Find out what it will mean to have a healthy ecosystem of PHP software all speak HTTP the same way. Say hello to your new best friend in PHP, PSR-7.

Beau Simensen

September 18, 2015
Tweet

More Decks by Beau Simensen

Other Decks in Programming

Transcript

  1. class halo_HttpRequest { public function __construct( $method, array $queryParams =

    null, array $postParams = null, array $fileData = null, array $env = null, $body = null ) { // ... } }
  2. class halo_DispatcherUtil { public static function MAKE_HTTP_REQUEST() { return new

    halo_HttpRequest( $_SERVER['REQUEST_METHOD'], $_GET, $_POST, $_FILES, $_SERVER ); } }
  3. namespace Symfony\Component\HttpFoundation; class Request { public function __construct( array $query

    = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null ) { // ... } }
  4. namespace Symfony\Component\HttpFoundation; class Request { public static function createFromGlobals() {

    return self::createRequestFromFactory( $_GET, $_POST, array(), $_COOKIE, $_FILES, $server ); } }
  5. namespace Zend\Http\PhpEnvironment; class Request extends \Zend\Http\Request { public function __construct()

    { $this->setEnv(new Parameters($_ENV)); if ($_GET) { $this->setQuery(new Parameters($_GET)); } if ($_POST) { $this->setPost(new Parameters($_POST)); } if ($_COOKIE) { $this->setCookies(new Parameters($_COOKIE)); } if ($_FILES) { $files = $this->mapPhpFiles(); $this->setFiles(new Parameters($files)); } $this->setServer(new Parameters($_SERVER)); } }
  6. namespace Aura\Web; class WebFactory { public function newRequest() { return

    new Request( $this->newRequestClient(), $this->newRequestContent(), $this->newRequestGlobals(), $this->newRequestHeaders(), $this->newRequestMethod(), $this->newRequestParams(), $this->newRequestUrl() ); } public function newRequestGlobals() { return new Request\Globals( $this->newRequestCookies(), $this->newRequestEnv(), $this->newRequestFiles(), $this->newRequestPost(), $this->newRequestQuery(), $this->newRequestServer() ); } }
  7. What if we could share code between these applications and

    frameworks at the HTTP level? — Igor, Beau, and Christoph
  8. The Problem for both server-side and client-side: HTTP Messages abstraction

    should be a commodity <message line> Header: value Another-Header: value Message body
  9. $client = create_http_client(); // implementation specific $response = $client->request('GET', 'http://www.php.net');

    if ($response->getStatusCode() == 200) { $content = $response->getContent(); } $response = $client->request('GET', 'http://api/returning.json'); if ($response->getContentType() == 'application/json') { $json = json_decode($response->getContent()); }
  10. There was a proposal to create a HTTP client interface

    earlier this year. As was noted at the time, the core HTTP message representation part (ie the request/response objects) is probably of more importance: it's used by both HTTP clients and frameworks. — Chris Wilkinson
  11. interface MessageInterface { public function __toString(); public function getStartLine(); public

    function getProtocolVersion(); public function setProtocolVersion($protocolVersion); public function getHeader($header); public function getHeaders(); public function hasHeader($header); public function setHeader($header, $value); public function setHeaders(array $headers); public function addHeaders(array $headers); public function getBody(); public function setBody($body); }
  12. interface RequestInterface extends MessageInterface { public function __construct( $method, $url,

    $protocolVersion = '1.1' ); public function getResponse(); public function setResponse(ResponseInterface $response); public function getMethod(); public function setMethod($method); public function getUrl(); public function setUrl($url); }
  13. interface ResponseInterface extends MessageInterface { public function __construct( $statusCode, $reasonPhrase

    = null, $protocolVersion = '1.1' ); public function getRequest(); public function setRequest(RequestInterface $request); public function getStatusCode(); public function setStatusCode($statusCode); public function getReasonPhrase(); public function setReasonPhrase($reasonPhrase); }
  14. class Request implements RequestInterface { public function withMethod($method) { $new

    = clone $this; $new->method = $method; return $new; } }
  15. use Zend\Diactoros\Request; use Zend\Diactoros\Uri; $request = (new Request()) ->withUri(new Uri('http://example.com'))

    ->withMethod('GET'); // this does nothing to $request. $request->withAddedHeader( 'Content-Type', 'application/json' );
  16. use Zend\Diactoros\Request; use Zend\Diactoros\Uri; $request = (new Request()) ->withUri(new Uri('http://example.com'))

    ->withMethod('GET'); // this is probably what you want. $request = $request->withAddedHeader( 'Content-Type', 'application/json' );
  17. $message = $message ->withHeader('foo', 'bar') ->withAddedHeader('foo', 'baz'); if ($message->hasHeader('Accept')) {

    /* ... */ } // 'bar, baz' $header = $message->getHeaderLine('foo'); // ['bar', 'baz'] $header = $message->getHeader('foo'); // [ 'foo' => ['bar', 'baz'] ] foreach ($message->getHeaders() as $header => $values) { }
  18. URI // https://api.example.com $uri = $uri->withScheme('https')->withHost('api.example.com'); $usersUri = $uri->withPath('/v2/users'); $booksUri

    = $uri->withPath('/v2/books'); // https://api.example.com/v2/users?limit=10 $getUsersRequest = (new Request()) ->withMethod('GET') ->withUri($usersUri->withQuery('?limit=10')); // https://api.example.com/v2/users $postUsersRequest = (new Request()) ->withMethod('POST') ->withUri($usersUri); // https://api.example.com/v2/books $getBooksRequest = (new Request()) ->withMethod('GET') ->withUri($booksUri);
  19. $country = $this->resolveCountryForRequest($request); if ($country) { $request = $request->withAttribute( 'geocode.country',

    $country ); } // ... $country = $request->getAttribute('geocode.country'); if ($country) { // }
  20. Given file input names files[0] and files[1] What you might

    expect: [ 'files' => [ 0 => [ 'name' => 'file0.txt', 'type' => 'text/plain', /* etc. */ ], 1 => [ 'name' => 'file1.html', 'type' => 'text/html', /* etc. */ ], ], ];
  21. Given file input names files[0] and files[1] What you actually

    get: [ 'files' => [ 'name' => [ 0 => 'file0.txt', 1 => 'file1.html', ], 'type' => [ 0 => 'text/plain', 1 => 'text/html', ], /* etc. */ ], ]
  22. $file0 = $request->getUploadedFiles()['files'][0]; $file1 = $request->getUploadedFiles()['files'][1]; printf( "Received the files

    %s and %s", $file0->getClientFilename(), $file1->getClientFilename() ); // "Received the files file0.txt and file1.html"
  23. Underlying "filename" is protected // Move a file to an

    upload directory $filename = sprintf( '%s.%s', create_uuid(), pathinfo($file0->getClientFilename(), PATHINFO_EXTENSION) ); $file0->moveTo(DATA_DIR . '/' . $filename); // Stream a file to Amazon S3. // Assume $s3wrapper is a PHP stream that will write to S3, and that // Psr7StreamWrapper is a class that will decorate a StreamInterface as a PHP // StreamWrapper. $stream = new Psr7StreamWrapper($file1->getStream()); stream_copy_to_stream($stream, $s3wrapper);
  24. function ($request, $response, $next) { $contentType = $request->getHeaderLine('Content-Type'); if ('application/json'

    === $contentType) { $json = json_decode($request->getBody(), true); $request = $request->withParsedBody($json); } return $next( $request, $response ); }
  25. function ($request, $response, $next) { $response = $next( $request, $response

    ); return $response->withAddedHeader( 'X-Clacks-Overhead', 'GNU Terry Pratchett' ); }
  26. namespace Acme; class ClacksOverhead { public function __invoke($request, $response, $next)

    { $response = $next( $request, $response ); return $response->withAddedHeader( 'X-Clacks-Overhead', 'GNU Terry Pratchett' ); } }
  27. Relay use Relay\RelayBuilder; $relayBuilder = new RelayBuilder(); $relay = $relayBuilder->newInstance([

    // ... new \Acme\ClacksOverhead(), // ... ]); $response = $relay($request, $response);
  28. use Dflydev\FigCookies\FigRequestCookies; $request = $request->withHeader(Cookies::COOKIE_HEADER, 'theme=red; foo=bar'); $cookie = FigRequestCookies::get($request,

    'theme'); $cookie->getValue(); // "red" $request = FigRequestCookies::set($request, $cookie->withValue('blue')); $request = FigRequestCookies::set($request, Cookie::create('name', 'beau')); $request = FigRequestCookies::remove($request, 'foo'); // ["theme=blue; name=beau"] $value = $request->getHeader(Cookies::COOKIE_HEADER);
  29. use Dflydev\FigCookies\FigResponseCookies; $response = $response ->withAddedHeader('Set-Cookie', SetCookie::create('theme', 'red')) ->withAddedHeader('Set-Cookie', SetCookie::create('foo',

    'bar')) ; $setCookie = FigResponseCookies::get($response, 'theme'); $setCookie->getValue(); // "red" $response = FigResponseCookies::set($response, $setCookie->withValue('blue')); $response = FigResponseCookies::set($response, SetCookie::create('token') ->withValue('a9s87dfz978a9') ->withDomain('example.com') ); // ["theme=blue", "token=a9s87dfz978a9; Domain=example.com"] $value = $response->getHeader('Set-Cookie');
  30. class EncryptedFigCookiesMiddleware { public function __invoke( RequestInterface $request, ResponseInterface $response,

    callable $next ) { $request = $this->requestCookieDecryptor->decrypt( $request, $this->cookieNames ); $response = $next($request, $response); return $this->responseCookieEncryptor->encrypt( $response, $this->cookieNames ); } }
  31. Hey! I'm trying to decide if Glide should use PSR-7

    for it's next release. — Jonathan Reinink, June 2015
  32. Nimble monii/nimble-framework A light PSR-7 framework built on top of

    Laravel's Service Container, nikic/fast-route, and Relay. WIP
  33. There were at least two other existing nikic/fast-route middleware implementations

    jowy/routing-middleware & oscarotero/psr7-middlewares
  34. class NikicFastRoute { public function __invoke($request, $response, $next) { $routeInfo

    = $this->dispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); switch ($routeInfo[0]) { case FastRoute\Dispatcher::NOT_FOUND: return $response->withStatus(404); case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: return $response->withStatus(405); case FastRoute\Dispatcher::FOUND: $action = $routeInfo[1]; $parameters = $routeInfo[2]; $request = $request ->withAttribute('action', $action) ->withAttribute('parameters', $parameters) ; return $next($request, $response, $next); default: return $response->withStatus(500); } } }
  35. class ActionHandler { public function __construct(ActionHandlerResolver $actionResolver) { $this->actionResolver =

    $actionResolver; } function __invoke($request, $response, $next) { $action = $request->getAttribute('action'); if (is_null($action)) { throw new \RuntimeException('No action specified for request.'); } $handler = $this->actionResolver->resolve($action); return $handler($request, $response, $next); } }
  36. $container->bind(Relay::class, function (Container $container) { $relayBuilder = $container->make(RelayBuilder::class); return $relayBuilder->newInstance([

    $container->make(NikicFastRoute::class), // ... other middleware $container->make(ActionHandler::class), ]); }); $relay = $container->make(Relay::class); $request = $container->make(ServerRequestInterface::class); $response = $container->make(ResponseInterface::class); send_response($relay($request, $response));
  37. Bridges, Integrations, & Users Stratigility (phly/conduit), Symfony Bridge, Radar, Relay,

    Drupal 8, Laravel, Stack PSR-7 Bridge, AWS SDK, FIG Cookies, Glide, Arbiter, Expressive, Nimble, and a bunch of middleware (oscarotero/psr7-middlewares)...
  38. joind.in/14975 Questions? @beausimensen • @moniidev • @thatpodcast We are recording

    a LIVE episode of That Podcast NOW! Join us? Up the stairs, through the door, down the hall...