1/40


PSR-18


Abstracting HTTP Clients in PHP


PHP Benelux, Antwerp - January 26th, 2019

© David Buchmann

What is the difference between

guzzle/guzzle

and

guzzlehttp/guzzle?

Depending on an implementation


Once upon a time

FOSHttpCache needs to send HTTP requests to varnish for cache invalidation

HTTPlug

=> Márk Sági-Kazár

PHP Framework Interoperability Group (PHP-FIG)

HTTP Request and Response: PSR-7

How to create an object without knowing the class?

1interface RequestFactoryInterface
2{
3    function createRequest(string $method, $uri)
4               : RequestInterface;
5}

HTTP Message Factories: PSR-17

=> Woody Gilk

How to send that request?

1interface ClientInterface
2{
3  function sendRequest(RequestInterface $request)
4           : ResponseInterface;
5}

What is PSR-18?

HTTP Client: PSR-18

=> Tobias Nyholm

Interchangeable: Defined behaviour

What about configuration?

But...

1sendRequest(RequestInterface $request, $config)

What about the cute elephant?

Httplug is still useful

Httplug PluginClient

1public function handleRequest(
2    RequestInterface $request,
3    callable $next,
4    callable $first
5): Promise;

Cache Plugin

1CachePlugin::clientCache - max-age, no-cache
2CachePlugin::serverCache - additionally: private

Httplug Authentication

Other decorators

BatchClient

1/**
2 * @param RequestInterface[] The requests to send
3 *
4 * @throws BatchException If any request threw
5 */
6public function sendRequests(
7    array $requests
8): BatchResult

HttpMethodsClient

1public function get($uri, array $headers = []): Re
2 
3public function post($uri, array $headers = [], $b
4 
5public function put($uri, array $headers = [], $bo
6 
7public function send(string $method, $uri, array $

Httplug discovery

Symfony HttplugBundle

HttplugBundle configuration

01httplug:
02  clients:
03    app:
04      http_methods_client: true
05      plugins:
06        - header_defaults:
07            headers:
08              "X-Conference": "Benelux"
09        - header_set:
10            headers:
11              "User-Agent": "PHP/Symfony"

HTTP request in Symfony

01/** @var HttpMethodsClient */
02private $httpClient;
03 
04public function status(): Response {
05try {
06  $r = $this->httpClient->get('http://php.net/');
07} catch (Exception $e) {
08  return new Response('Failed', 502);
09}
10return new Response(200 === $r->getStatusCode()
11    ? 'Success'
12    : 'Error');

Outlook

Who can promise a Promise?

1sendAsync(RequestInterface $request): Promise;
2Promise::wait(): ResponseInterface;

Meanwhile: Use Httplug

01interface Promise
02{
03    const PENDING = 'pending';
04    const FULFILLED = 'fulfilled';
05    const REJECTED = 'rejected';
06 
07    public function then(?callable $onFulfilled,
08    public function getState();
09    public function wait($unwrap = true);
10}

Fire off requests

01try {
02  $promises = [];
03  foreach ($uris as $u) {
04    $promises[$u] = $httpClient->sendAsyncRequest(
05      $requestFactory->createRequest('GET', $u)
06    );
07  }
08} catch (\Exception $e) {
09  return new Response('Configuration error', 500);
10}

Wait for it

01foreach ($promises as $uri => $promise) {
02   $s .= $uri.':';
03   try {
04      $r = $promise->wait();
05      if (200 === $r->getStatusCode()) {
06         $s .= 'Up and running';
07      } else {
08         $s .= 'Error: '.$r->getStatusCode();
09      }
10   } catch (\Exception $e) {
11      $s .= 'Network Error: '.$e->getMessage();
12   }
13}

then()

01$results = [];
02$promises = [];
03foreach ($uris as $u) {
04  $promise = $httpClient->sendAsyncRequest(
05    $requestFactory->createRequest('GET', $u));
06  $onFulfilled = function (ResponseInterface $r)
07    use ($u, $results) {
08    if (200 === $r->getStatusCode()) {
09     $results[$u] = 'Up and running';
10    } else {
11     $results[$u] = 'Error: '.$r->getStatusCode();
12    }
13  };
14}

then() callbacks

01...
02    $onRejected = function (Exception $r)
03      use ($u, $results) {
04      $results[$u] = 'Error: '.$e->getMessage();
05    };
06    $promise->then($onFulfilled, $onRejected);
07    $promises[] = $promise;
08  }
09} catch (Exception $e) {
10  return new Response('Configuration error', 500);
11}

Wait and build result page

1foreach ($promises as $promise) {
2   $promise->wait(false);
3}
4foreach ($results as $u => $text) {
5   $s .= $uri.':'.text;
6}

Thank you!

@dbu


https://github.com/dbu/httplug-demo

https://joind.in/talk/8b940