cult3

How to create an Active Record style PHP SDK Part 2

Jul 16, 2014

Table of contents:

  1. The importance of the HTTP connection
  2. Introducing Guzzle
  3. Setting up Guzzle
  4. Using Dependency Injection for the HTTP Client
  5. Creating the Connection class
  6. Writing Tests
  7. Conclusion

One of the most important characteristics of the Active Record pattern is how the persistence is tightly coupled to the entity. This tight coupling allows you to very easily, create, read, update and delete from the object you are working with.

When working with an ORM such as Laravel’s Eloquent, the persistence layer would be a connection to a database.

However when working with a third-party SDK, our persistence layer is the third-party’s API.

In this second part of the building an Active Record style PHP SDK I’m going to be looking at the HTTP client that allows us to query and persist our entity objects to the API.

The importance of the HTTP connection

As a quick refresher from last week, first I will cover why the HTTP connection is so important.

When using the Active Record pattern, we will typically have code that looks like the following:

$user = new User();
$user->username = "philipbrown";
$user->save();

In the code above, we are able to create a new User object, set a property and then persist it to storage.

This means two things:

  1. The User class must have a save() method defined
  2. The save() method must know how to “save” the object to storage

When using the Active Record pattern, each entity inherits the ability to persist entities to storage from the ORM. This is typically a connection to the database.

However because we are not using a database, we need a way to “persist” our entities to the third-party’s API.

In order to do that, we need a HTTP Client.

Introducing Guzzle

If you’ve never worked with third-party HTTP based web services, you’ve probably never heard of Guzzle.

Guzzle is probably the de facto HTTP Client for PHP. Using Guzzle abstracts a lot of the headaches away from consuming web services. This means we can use Guzzle as our layer between our code and the connection to the API we are working with.

Guzzle is one of those PHP packages that is just a no brainer to use. The breadth of functionality and what Guzzle makes really easy to do can be a little bit overwhelming at first. We won’t be utilising all of Guzzle’s functionality in this series, but this should provide a good introduction. Guzzle is one of those packages that I find myself using in nearly every big project I work on.

Setting up Guzzle

As with all good PHP packages, Guzzle is available through Composer (What is Composer?).

Add the following to your composer.json file:

{
    "require": {
        "guzzlehttp/guzzle": "~4.0"
    }
}

Guzzle is hosted on GitHub but I find myself consulting the documentation more than the source code.

Using Dependency Injection for the HTTP Client

The Active Record pattern by design is very coupled to the persistence layer that stores the entities. However, we do not want to write our code in such a way that actually couples our code to the HTTP Client.

When writing a PHP SDK, we only care about our code. The actual API is not our concern and so we do not need to actually make contact whilst running our test suite. Actually hitting the API with our tests would make our tests slow and brittle, and it would probably get us banned from the API.

If we were to write our code in such a way that it coupled the HTTP client to our entities, it would be extremely difficult to test our code in isolation from the HTTP client.

Instead we need to provide the connection as a dependency to our entities so that it can be very easily mocked during our tests. This will make it possible to test our code in isolation, and it will prevent us from having to hit the API every time we run the tests.

Creating the Connection class

Guzzle provides us with an excellent API for executing HTTP requests. It’s very easy to get started with a third-party API by just instantiating a new instance of the Guzzle Client.

However, we have a couple of rules around how we connect and interact with the CapsuleCRM API. Instead of using the Guzzle Client directly, we can wrap it in our own Connection class. This means we can type hint for that class within our entities and know that the business rules are in place.

So first things first, create a new class called Connection in a file called Connection.php under src:

<?php namespace PhilipBrown\CapsuleCRM;

use GuzzleHttp\Client;

class Connection
{
}

In order to interact with the CapsuleCRM API we need the subdomain of our account and an API key. We can pass these two dependencies through the constructor to ensure that they are set before we attempt to use the client:

class Connection {

/**
 * The API Key
 *
 * @var string
 */
protected $key;

/**
 * The subdomain
 *
 * @var string
 */
protected $subdomain;

/**
 * Create a new Connection instance
 *
 * @param string $key
 * @param string $subdomain
 * @return void
 */
public function __construct($key, $subdomain)
{
$this->key = $key;
$this->subdomain = $subdomain;
}

Next we can define a method to instantiate a new instance of the Guzzle Client. We also need to define some configuration settings for authentication and request headers:

/**
 * Return an HTTP client instance
 *
 * @return GuzzleHttp\Client
 */
public function client()
{
    if ($this->client) return $this->client;

    return new Client([
        'base_url' => "https://$this->subdomain.capsulecrm.com",
        'defaults' => [
            'auth' => [$this->key, 'x', 'basic'],
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json'
            ]
        ]
    ]);
}

You will notice that first I check to see if the $this->class property already exists. If we’re making multiple requests we don’t need to create a new Client each time as we can just reuse the same one.

Don’t forget to define the property at the top of the class:

/**
 * The HTTP Client
 *
 * @var GuzzleHttp\Client
 */
protected $client;

Next we need to define methods for GET, POST, PUT and DELETE. These methods will simply wrap the Guzzle Client’s methods.

The GET method looks like this:

/**
 * Send a GET request
 *
 * @param string $url
 * @param array $params
 * @return Guzzle\Http\Message\Response
 */
public function get($url, array $params = [])
{
    $request = $this->client()->createRequest('GET', $url);

    $query = $request->getQuery();

    foreach ($params as $k => $v) {
        $query->set($k, $v);
    }

    return $this->client()->send($request);
}

In this method we accept a URL endpoint to hit and an array of parameters to send with the request. This will allow us to restrict resource requests to exactly the items we are looking for.

First we create a new instance of the request and spin through each of the $params and add them to the query. Finally we can use the $this->client() to send the request.

Next we can define the POST method:

/**
 * Send a POST request
 *
 * @param string $url
 * @param string $body
 * @return Guzzle\Http\Message\Response
 */
public function post($url, $body)
{
    return $this->client()->post($url, ['body' => $body]);
}

Here we are simply using the Guzzle Client’s post() method but we are ensuring that the body of the request is nested under the body key. This is a requirement of the API we are working with.

Next we can define the PUT method:

/**
 * Send a PUT request
 *
 * @return Guzzle\Http\Message\Response
 */
public function put($url, $body)
{
    return $this->client()->put($url, ['body' => $body]);
}

Similarly to the POST method, we can simple use the Guzzle Client’s method, but once again we need to correctly key the body payload.

And finally we can define the DELETE method:

/**
 * Send a DELETE request
 *
 * @return Guzzle\Http\Message\Response
 */
public function delete($url)
{
    return $this->client()->delete($url);
}

With this method we can just pass the $url as we don’t have to specify a body for the request.

Writing Tests

The connection within our package is on the border between our responsibility and the outside world. We can safely assume that Guzzle is well tested and working correctly, and we definitely do not want to actually start hitting the API during our tests.

Therefore in my opinion we don’t need to test the HTTP methods of this Connection class. We should be aware of breaking changes from Guzzle or the CapsuleCRM API, but it is not our responsibility to write tests for them.

So the only test that I’m going to write for this class is to ensure that we are able to create a new instance of the Client correctly, there isn’t much else needed to test:

class ConnectionTest extends PHPUnit_Framework_TestCase
{
    public function testGetClient()
    {
        $c = new PhilipBrown\CapsuleCRM\Connection(", ");
        $this->assertInstanceOf("GuzzleHttp\Client", $c->client());
    }
}

Conclusion

The Active Record pattern relies heavily on the connection to the persistence layer. This coupled connection is what makes the Active Record pattern so powerful and intuitive.

When working with a traditional ORM, the Active Record pattern would require a database connection. However because we are working with a third-party API, instead we need an HTTP Client.

Just because we are using the Active Record pattern, does not mean we should write code that can’t be tested. The Active Record pattern is coupled to the persistence layer, but we can still using Dependency Injection to ensure our code can be tested in isolation.

I think a good measure of code quality would be how easy it would be to switch out a dependency. It’s fairly easy to imagine that we could switch out our HTTP connection dependency with a database connection that defined the get(), post(), put and delete() methods.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.