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

Hello, PSR-7. (phpDay 2015)

Hello, PSR-7. (phpDay 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

May 16, 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 Phly\Http\Request; use Phly\Http\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 Phly\Http\Request; use Phly\Http\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. // Returns an empty array if not found: $header =

    $message->getHeader('Accept'); // Returns an empty string if not found: $header = $message->getHeaderLine('Accept'); // Test for a header: if (! $message->hasHeader('Accept')) { } // If the header has multiple values, fetch them // as an array: $values = $message->getHeader('X-Foo'); // Or as a comma-separated string: $values = $message->getHeaderLine('X-Foo');
  18. $message = $message ->withHeader('foo', 'bar') ->withAddedHeader('foo', 'baz'); // 'bar, baz'

    $header = $message->getHeaderLine('foo'); // ['bar', 'baz'] $header = $message->getHeader('foo'); // [ 'foo' => ['bar', 'baz'] ] foreach ($message->getHeaders() as $header => $values) { }
  19. URI

  20. // 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);
  21. $country = $this->resolveCountryForRequest($request); if ($country) { $request = $request->withAttribute( 'geocode.country',

    $country ); } // ... $country = $request->getAttribute('geocode.country'); if ($country) { // }
  22. 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. */ ], ], ];
  23. 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. */ ], ]
  24. $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"
  25. 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);
  26. 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 ); }
  27. function ($request, $response, $next) { $response = $next( $request, $response

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

    { $response = $next( $request, $response ); return $response->withAddedHeader( 'X-Clacks-Overhead', 'GNU Terry Pratchett' ); } }
  29. Laravel: on root request // app/Http/Kernel.php class Kernel { protected

    $routeMiddleware = [ 'clacks' => 'Acme\ClacksOverhead', ]; } // routes.php Route::get('/',['middleware'=>'clacks', function() { return view('home'); }]);