Composer & Symfony & Buzz, Oh, My!

These days everything has an API. This is a fantastic development in the eyes of most developers, but comes with its own pain points. One library that has been a great help is Kris Wallsmith's Buzz curl wrapper. However, this still leaves us with creating our own wrapper for each API we'd like to use it for, with all the error handling and other tasks.

Let's solve that pain by using some Symfony components to build a easily extendable wrapper.

A few notes about this tutorial:

The original code this was based on comes from  Alex Joyce's PIN Payment API library, 
which has a lot more use cases you can study. I recently used his library for PIN 
& created a second version for a different API - and was so impressed by how easy it was, 
I was inspired to write this. 

It is assumed you have a basic understanding of Composer - if you run into trouble, let 
me recommend Juan Treminio's excellent tutorial

Now, on with the code...

We're going to pretend a lot of things for the sake of brevity - namely, we'll make a fake api to post to & pretend that we are really pulling our BuzzWrapper package from Packagist. First, let's make a directory for everything called /buzz_wrapper, and create the api file inside it:


// Create a file called api.php. I set up a virtual host so I could call
// http://buzzwrapper.api for this post, but feel free to do as you like

if (empty($_POST))
{
	echo 'No data posted';
}	
else
{
	echo post_data($_POST);
}	

function post_data($post)
{
	$return = 'Posted:' . $post['field1'];
	if (!empty($post['field2']))
	{
		$return .= ' & ' . $post['field2'];
	}	
	return $return;
}

You can see it's not much of an api, but it will serve. Next, let's grab our composer packages.


// your composer.json file will look like this - we need Buzz for the curl, 
// and Symfony's standalone Options Resolver component as the base of our wrapper

{
    "require": {
        "php": ">=5.3.3",
        "kriswallsmith/buzz": ">=0.6",
        "symfony/options-resolver": ">=2.1"
    }
}

Great! You should have the usual composer /vendor directory with "kriswallsmith" & "symfony" sub-directories. Now, let's setup the last of the "fake" bits - our own composer package:


// We are going to pretend we pulled all this via Composer, so set up the following:

/vendor
       /buzz_wrapper
                    /src
                        /BuzzWrapper

// in file /vendor/composer/autoload_namespaces.php, add the BuzzWrapper namespace to your array

/'BuzzWrapper' => $vendorDir . '/buzz_wrapper/src/',

Finally, let's check everything is all set before we tackle our code. In buzz_wrapper directory, create index.php

// check your api is reachable

$homepage = file_get_contents('http://buzzwrapper.api/api.php');
echo $homepage;

// check Buzz is set up correctly

include_once './vendor/autoload.php';

$url = 'http://buzzwrapper.api/api.php';

$browser = new Buzz\Browser();
$fields = array('field1'=>'test post 1', 'field2'=>'test post 2');

$post_request = $browser->submit($url, $fields);
var_dump($post_request);

// Now, for real this time, on with the code...

We'd like to break our code out into two main sections - a Handler, which is in charge of making the connection & passing authentication - and the Request, which of course is the data we are passing back and forth. So first let's make a file in the BuzzWrapper directory called Handler.php

namespace BuzzWrapper;

use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Options;
use Buzz\Browser;
use Buzz\Client\Curl;

class Handler
{
    protected $options = array();
    protected $browser;

    /**
     * Constructor
     * 
     * @param array $options
     */
    public function __construct(array $options = array())
    {
        $resolver = new OptionsResolver();
        $this->setDefaultOptions($resolver);
        $this->options = $resolver->resolve($options);

        $this->init();
    }

    /**
     * Set our default options.
     * 
     * @param OptionsResolverInterface $resolver
     */
    protected function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver
            ->setDefaults(array(
                'url' => function (Options $options) {
                    return 'http://buzzwrapper.api/api.php';
                }
            ))
            ->setRequired(array(
                'key',
                'appid'
            ))
            ->setAllowedTypes(array(
                'url' => 'string',
                'key'  => 'string',
                'appid'=>'string' 
            ));
    }

    /**
     * Initialise
     */
    protected function init()
    {
        $client = new Curl;
        $client->setVerifyPeer(false);

        $this->client = $client;

        $this->browser = new Browser($client);
    }

    /**
     * Submit a request to the API.
     * 
     * @param RequestInterface $request
     * @return object
     */
    public function submit(RequestInterface $request)
    {
        $response = $this->browser->submit(
            $this->options['url'] . $request->getPath(),
            $request->getData(),
            $request->getMethod());

        return $response->getContent();
    }

}	

Don't panic - it's not as bad as it looks. Let's break it down:

The first thing we do is declare our namespace and include the Symfony & Buzz components we'll need. Browser is the most public facing component of the Buzz library - we used that above in our test. The Symfony components are typically part of the Form library, and we are using them in much the same way - to validate all our options & set some defaults. (You can read more about OptionResolver here)

Our __construct() creates a new OptionResolver which we use to check if our options are all valid, and to set a default url if one is missing. Finally, we apply the options to the class variable ($this->options) for later and initialize the class.

The class init() is where we create an instance of Buzz. In this case, we will create a new Curl instance so we can control a few settings, and pass that into the Browser's construct. Submit() is a wrapper for the Buzz browser submit function, which we will use later to make our call.

Let's stop for a moment and check out how this works. Back in your index.php, replace all the testing code we made with this:

include_once './vendor/autoload.php';

$url 		= 'http://buzzwrapper.api/api.php';
$app_key 	= 'qwerty123';
$app_id		= '12345';

$buzz_wrapper = new BuzzWrapper\Handler(array('url'=>$url, 'key' => $app_key, 'appid' => $app_id ));
var_dump($buzz_wrapper);

So you can see we now have what is essentially a connection object to our api. At this point we've confirmed that our url endpoint is correct, along with our api keys or whatever credentials we might need. So now we're ready to build a request and send it over.

If you looked closely at the Symfony parts of the code, you'll notice we used OptionResolver and also something called OptionResolverInterface - a blueprint or "contract" for what an OptionResolver must look like. We're going to do the same thing for our own request objects - create a RequestInterface and then implement it.

// file BuzzWrapper/RequestInterface.php

namespace BuzzWrapper;

interface RequestInterface
{
    const METHOD_OPTIONS = 'OPTIONS';
    const METHOD_GET     = 'GET';
    const METHOD_HEAD    = 'HEAD';
    const METHOD_POST    = 'POST';
    const METHOD_PUT     = 'PUT';
    const METHOD_DELETE  = 'DELETE';
    const METHOD_PATCH   = 'PATCH';
    
    /**
     * Get our end-point's expected method.
     * 
     * @return string
     */
    public function getMethod();
    
    /**
     * Get our end-point path.
     * 
     * @return string
     */
    public function getPath();

    /**
     * Get our sendable data.
     * 
     * @return array
     */
    public function getData();
}

This sets up some constants we need for later and defines our required methods. If you are unfamiliar with interfaces, have a read here

Having defined our abstract request, let's make a specific one. This next class is the work horse, so to speak - it is specific to a particular action on an api object. For example, if we had a User object and our api allowed us to Add, Update, Delete we would have an individual class fo reach of those three. That may sound like a lot of work, but when you see the code and the few things you have to change for each, you'll see the power behind this technique.

// file BuzzWrapper/User/Add.php

namespace BuzzWrapper\User;

use BuzzWrapper\RequestInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;

class Add implements RequestInterface
{
    protected $options;


    public function __construct(array $options = array())
    {
        // options
        $resolver = new OptionsResolver();
        $this->setDefaultOptions($resolver);

        $this->options = $resolver->resolve($options);
    }

    protected function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver
            ->setRequired(array(
                'field1'
            ))
            ->setOptional(array(
                'field2'
            ))
            ->setAllowedTypes(array(
                'field1' => 'string',
                'field2' => 'string'
            ));
    }

    /**
     * {@inheritdoc}
     */
    public function getMethod()
    {
        return self::METHOD_POST; //chose appropriately from the RequestInterface
    }

    /**
     * {@inheritdoc}
     */
    public function getPath()
    {
        return ''; //this depends on your api - if the endpoint has segments like "/v2/user/add", put it here
    }

    /**
     * {@inheritdoc}
     */
    public function getData()
    {
        return $this->options; //this is the POST_FIELDS - set as empty array() if not posting
    }
}

It only takes a few moments to see how thi sall works, since it is built on the same idea as our handler. Now our options are the specific fields required to add a new user, so those will change with each class, as will the getMethod() and possibly the getPath() & getData() - but nothing else. So setting up 10 different classes that safely validate and build our request is a matter of about 10 minutes! Not only that, but to rework the Handler & Request classes to work with a completely different api - is virtually the same type of change.

To use it let's go back to our index.php:

include_once './vendor/autoload.php';

$url 		= 'http://buzzwrapper.api/api.php';
$app_key 	= 'qwerty123';
$app_id		= '12345';

$buzz_wrapper = new BuzzWrapper\Handler(array('url'=>$url, 'key' => $app_key, 'appid' => $app_id ));
var_dump($buzz_wrapper);


$request_data = array('field1'=>'test post 1', 'field2'=>'test post 2');
$request = new BuzzWrapper\User\Add($request_data);

// send it
$response = $buzz_wrapper->submit($request);
var_dump($response);

Hope that helped!

Contact me