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?

interface RequestFactoryInterface
{
    function createRequest(string $method, $uri)
               : RequestInterface;
}

HTTP Message Factories: PSR-17

=> Woody Gilk

How to send that request?

interface ClientInterface
{
  function sendRequest(RequestInterface $request)
           : ResponseInterface;
}

What is PSR-18?

HTTP Client: PSR-18

=> Tobias Nyholm

Interchangeable: Defined behaviour

What about configuration?

But...

sendRequest(RequestInterface $request, $config)

What about the cute elephant?

Httplug is still useful

Httplug PluginClient

public function handleRequest(
    RequestInterface $request,
    callable $next,
    callable $first
): Promise;
            

Cache Plugin

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

Httplug Authentication

Other decorators

BatchClient

/**
 * @param RequestInterface[] The requests to send
 *
 * @throws BatchException If any request threw
 */
public function sendRequests(
    array $requests
): BatchResult

HttpMethodsClient

public function get($uri, array $headers = []): Re

public function post($uri, array $headers = [], $b

public function put($uri, array $headers = [], $bo

public function send(string $method, $uri, array $
            

Httplug discovery

Symfony HttplugBundle

HttplugBundle configuration

httplug:
  clients:
    app:
      http_methods_client: true
      plugins:
        - header_defaults:
            headers:
              "X-Conference": "Benelux"
        - header_set:
            headers:
              "User-Agent": "PHP/Symfony"
            

HTTP request in Symfony

/** @var HttpMethodsClient */
private $httpClient;

public function status(): Response {
try {
  $r = $this->httpClient->get('http://php.net/');
} catch (Exception $e) {
  return new Response('Failed', 502);
}
return new Response(200 === $r->getStatusCode()
    ? 'Success'
    : 'Error');
        

Outlook

Who can promise a Promise?

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

Meanwhile: Use Httplug

interface Promise
{
    const PENDING = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';

    public function then(?callable $onFulfilled,
    public function getState();
    public function wait($unwrap = true);
}
            

Fire off requests

try {
  $promises = [];
  foreach ($uris as $u) {
    $promises[$u] = $httpClient->sendAsyncRequest(
      $requestFactory->createRequest('GET', $u)
    );
  }
} catch (\Exception $e) {
  return new Response('Configuration error', 500);
}

Wait for it

foreach ($promises as $uri => $promise) {
   $s .= $uri.':';
   try {
      $r = $promise->wait();
      if (200 === $r->getStatusCode()) {
         $s .= 'Up and running';
      } else {
         $s .= 'Error: '.$r->getStatusCode();
      }
   } catch (\Exception $e) {
      $s .= 'Network Error: '.$e->getMessage();
   }
}

then()

$results = [];
$promises = [];
foreach ($uris as $u) {
  $promise = $httpClient->sendAsyncRequest(
    $requestFactory->createRequest('GET', $u));
  $onFulfilled = function (ResponseInterface $r)
    use ($u, $results) {
    if (200 === $r->getStatusCode()) {
     $results[$u] = 'Up and running';
    } else {
     $results[$u] = 'Error: '.$r->getStatusCode();
    }
  };
}
            

then() callbacks

...
    $onRejected = function (Exception $r)
      use ($u, $results) {
      $results[$u] = 'Error: '.$e->getMessage();
    };
    $promise->then($onFulfilled, $onRejected);
    $promises[] = $promise;
  }
} catch (Exception $e) {
  return new Response('Configuration error', 500);
}

Wait and build result page

foreach ($promises as $promise) {
   $promise->wait(false);
}
foreach ($results as $u => $text) {
   $s .= $uri.':'.text;
}

Thank you!

@dbu


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

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