Loading

Writing HTTP clients in PHP

David Buchmann

This is a live streamed presentation. You will automatically follow the presenter and see the slide they're currently on.

Writing HTTP clients
 in PHP

David Buchmann

@dbu

@dbu

Márk Sági-Kazár

@sagikazarmark

@sagikazarmark

Introduction

  • HTTPlug
  • Client Library
  • Symfony Integration

HTTP

Request / Response

  • HTTP Server: Get request, reply with response.
  • HTTP Client: Send request, get response.

PSR-7

Interfaces for Request & Response

Immutable, withX methods return a copy

$request = new GuzzleHttp\Psr7\Request();
$request = $request
    ->withMethod('POST')
    ->withUri($uri)
    ->withBody($body)
;

var_dump(json_decode($response->getBody()));

?

?

Server

PSR-7 flow

create request

send request

HTTP Message Factory

Interface for message factory

 => decouple from message implementation

$request = $requestFactory->createRequest(
    $method,
    $url,
    $headers,
    $body
);
$request->withHeader(...);

?

Server

PSR-7 flow

create request

send request

Message Factory

HTTPlug

Interface for HTTP clients

 => decouple from client implementation

$response = $client
    ->sendRequest($request);

Server

PSR-7 flow

create request

send request

HTTPlug

TODO API

Simplistic API for the tutorial

Exercise: Simplistic Client

 

  • In clients/trivial-skeleton/bin/client
  • Bootstrap HTTPlug
  • Create an item (hardcoded content)
  • Then list all existing items

HTTPlug

Why?

  • Do not couple code to message/client implementation
  • Use guzzle 5 or guzzle 6
  • Or something else
  • Shared libraries: Do not force implementation on user

HTTPlug Plugins

  • Client independent plugin system
  • Only depends on PSR-7 request/response
  • Plugins provided for:
    • authentication
    • caching
    • logging
    • generic header manipulations
    • etc

HTTPlug Asynchronous

  • AsyncClient
  • Returns promise
$promise = $client->sendAsync($request);
$promise->then(
    [$this, 'onSuccess'],
    [$this, 'onFailure']
);
...
$promise->wait();

HTTPlug Discovery

  • Find implementation for HTTPlug client, message factory and stream factory implementations
  • Please allow to inject instance!
public function __construct(
    HttpClient $client = null, 
    MessageFactory $messageFactory = null
) {
    $this->client = $client ?:
        HttpClientDiscovery::find();
    $this->messageFactory = $messageFactory ?: 
        MessageFactoryDiscovery::find();
}

API Client Library

API client library

  • API over API
  • Custom or exactly the same domain model

API client library + HTTPlug

  • Use HTTP as transport layer
  • Don't force HTTP client implementation
  • If you need plugins, provide a client factory

Transport: HTTP

Serialization: JSON/XML/etc

Error handling (HTTP Status)

Domain model

Error handling (Domain)

How does it work?

Composer in a library

  • Require php-http/client-implementation
    Virtual package provided by all HTTPlug compliant clients
  • Recommended php-http/message factory-implementation
    Virtual package provided by php-http/message
  • Optionally php-http/discovery
  • require-dev e.g. php-http/guzzle6-adapter to test the library

Composer in an application

  • Require a client implementation (e.g. php-http/guzzle6-adapter)
  • Require a message factory implementation (php-http/message)

Exercise 1: Configuration

  • client/library-skeleton:
    • src/HttpClientFactory.php
    • src/TodoClient.php::__construct

Exercise 2: Get methods

  • client/library-skeleton/src/TodoClient.php:
    • getTodo
    • getTodoPage
  • Try using the command bin/get and bin/list

Exercise 3: Modify methods

  • client/library-skeleton/src/TodoClient.php:
    • createTodo
    • updateTodo
    • deleteTodo
  • Try using the commands bin/

Testing

What to test?

  • Domain operations
  • Domain exceptions
  • HTTP calls behind operations
  • Domain models

Exercise 1: Test get methods

  • client/library-skeleton/tests/TodoClientTest.php

Exercise 2: Test modify methods

  • client/library-skeleton/tests/TodoClientTest.php
Symfony is a trademark of Fabien Potencier. All rights reserved.

Bundle for Library

  • Configure services
  • Integration code (form types, validation, ...)

HttplugBundle

  • Configure clients
  • Configure plugins
  • Debug toolbar integration

Exercise 1: Symfony Bundle

  • clients/symfony-skeleton
  • src/Wsc/TodoBundle
    • Resources/config/services.xml
    • DependencyInjection/Configuration.php
    • DependencyInjection/WscTodoExtension.php

Exercise 2: Symfony Application

  • clients/symfony-skeleton
    • src/AppBundle/Controller/DefaultController
    • app/Resources/views/default/*

Outlook

PSR-15

HTTP factories

  • Upcoming FIG standard for HTTP factories
  • request, response, uri and stream body
<?php

$request = $factory->createReques('GET', '/api/user')
    ->withHeader('Authentication', 'Bearer token')
    ->WithHeader()
    //...
    ;

PSR-?

HTTP Client

  • Eventually, the client interface should be FIG standard
  • Guzzle et al could simply implement that interface

API Client Generator

OpenAPI spec to PHP code

https://github.com/jolicode/jane-openapi

Puli

  • Can be used as discovery strategy
  • Allows to have your custom client discovered
{
    "version": "1.0",
    "name": "php-http/guzzle6-adapter",
    "bindings": {
        "04b5a002-71a8-473d-a8df-75671551b84a": {
            "_class": "Puli\\Discovery\\Binding\\ClassBinding",
            "class": "Http\\Adapter\\Guzzle6\\Client",
            "type": "Http\\Client\\HttpClient"
        }
    }
}

Get help

They already use HTTPlug

Thank you!

We are glad for any feedback!

Thank you!