Building a PHP SDK with HTTPlug

@nathanjdunn

How would you go about building an SDK?

  • Which abstraction would you use?
  • How would you test it?
  • What happens if you want to switch from Guzzle to cURL?

How does HTTPlug help?

  • HTTPlug decouples packages from the implementation
  • Comes with a 'mock client' which allows you to send fake HTTP requests
  • Plugins system to handle custom logic
  • Authentication support for common auth types
  • PSR-7 Compatible (🤷‍♀️)

HTTPlug makes certain decisions so you don't have to

The Chargebee API

  • Subscription Billing Platform
  • REST API
  • Official, but old-school PHP SDK
  • https://github.com/nathanjdunn/chargebee-php-sdk
  • Modelled from https://github.com/m4tthumphrey/php-gitlab-api

Chargebee SDK Usage

use NathanDunn\Chargebee\Client;

$client = new Client('nathandunn-test', 'test_bv6UzHREYaCNJQLqzdNzILQ0450o2dyS');

$sub = $client->subscription()->list();


// Response
array(1) {
  'list' =>
  array(1) {
    [0] =>
    array(2) {
      'subscription' =>
      array(22) {
        ...
      }
      'customer' =>
      array(21) {
        ...
      }
    }
  }
}

Swapping Out Instances

use NathanDunn\Chargebee\HttpClient\Builder;
use Http\Mock\Client as MockClient;
use NathanDunn\Chargebee\Client;

$builder = new Builder(
    'test_bv6UzHREYaCNJQLqzdNzILQ0450o2dyS', new MockClient
);

$client = new Client(
    'nathandunn-test', 'test_bv6UzHREYaCNJQLqzdNzILQ0450o2dyS', $builder
);

$sub = $client->subscription()->list();

Plugins

  • Allows you to execute logic before/after a request
  • You can also manipulate a request before it gets sent
  • HTTPlug have 'official' plugins and can be installed via Composer
  • If you want to do something really custom, you can create your own. Just create a class and implement the Http\Client\Common\Plugin interface.

Plugins

  • Allows you to execute logic before/after a request
  • You can also manipulate a request before it gets sent
  • HTTPlug have 'official' plugins and can be installed via Composer
  • If you want to do something really custom, you can create your own. Just create a class and implement the Http\Client\Common\Plugin interface.

Plugins - ErrorPlugin

  • The ErrorPlugin transforms responses with HTTP error status codes into exceptions:
    • 400-499 status code are transformed into Http\Client\Common\Exception\ClientErrorException;
    •  500-599 status code are transformed into Http\Client\Common\Exception\ServerErrorException

Plugins - Usage

// Builder.php

/**
 * @return HttpMethodsClient
 */
public function getHttpClient(): HttpMethodsClient
{
    $this->httpMethodsClient = new HttpMethodsClient(
        new PluginClient($this->httpClient, [new ErrorPlugin]),
        $this->requestFactory
    );

    return $this->httpMethodsClient;
}

Authentication

  • Authentication works in exactly the same way as a plugin
  • HTTPlug provides several out-of-box auth types
    • Basic Auth
    • Bearer
    • WSSE

Authentication - Usage

// Builder.php

/**
 * @return AuthenticationPlugin
 */
protected function getAuthenticationPlugin(): AuthenticationPlugin
{
    $authentication = new BasicAuth($this->key, null);

    return new AuthenticationPlugin($authentication);
}

Authentication - Usage

// Builder.php

/**
 * @return HttpMethodsClient
 */
public function getHttpClient(): HttpMethodsClient
{
    $authenticator = $this->getAuthenticationPlugin();

    $this->httpMethodsClient = new HttpMethodsClient(
        new PluginClient($this->httpClient, [$authenticator]),
        $this->requestFactory
    );

    return $this->httpMethodsClient;
}

Testing

// tests/Unit/Api/Addons/AddonTest.php


/** @test */
public function should_list_addons()
{
    $expected = $this->getContent(
        sprintf('%s/data/responses/addon_list.json', __DIR__)
    );

    $addon = $this->getApiMock();
    $addon->expects($this->once())
        ->method('get')
        ->with('https://123456789.chargebee.com/api/v2/addons', [])
        ->will($this->returnValue($expected));

    $this->assertEquals($expected, $addon->list());
}

Testing

// Unit/Api/TestCase.php

protected function getApiMock(array $methods = [])
{
    $httpClient = $this->getMockBuilder(HttpClient::class)
        ->setMethods(['sendRequest'])
        ->getMock();

    $httpClient
        ->expects($this->any())
        ->method('sendRequest');

    $builder = new Builder(self::$key, $httpClient);
    $client = new Client(self::$site, self::$key, $builder);

    return $this->getMockBuilder(Addon::class)
        ->setMethods(['get', 'post'])
        ->setConstructorArgs([$client])
        ->getMock();
}

Any questions?

Building a PHP SDK with HTTPlug

By Nathan Dunn

Building a PHP SDK with HTTPlug

  • 88