Guzzle – PHP HTTP Client

Miguel Ibarra Romero
Tweet

As you probably know, website development can be broken up into 2 main areas:

  • Front end (what the end user sees)
  • Back end (what the server has to do in order to provide the requested data)

While front end development frequently uses several data sources to display a page, simple dynamic sites would only depend on data coming from a database hosted on the same server. As back end developers today, we are in need of retrieving data from a database server on a different host, or consuming an API from a third-party provider, while making it look like everything happened in our server.

PHP comes with the native cURL client (if enabled), that can be frightening to the newcomer or hard to use to consume SOAP services. Other frameworks implement their versions of HTTP, REST, and SOAP clients to some degree. However, if you do not wish to use cURL, you do not have a framework or you are not a fan of your framework's solution, Guzzle to the rescue.

Guzzle is an independent HTTP client for PHP. Installing Guzzle is very easy: you'll need to get composer first (http://www.sitepoint.com/php-dependency-management-with-composer/). Once you got composer installed and ready, create your basic composer.json file, which would look like the following:

    {
        "name": "jd/guzzle demo",
        "authors": [
            {
                "name": "John Doe",
                "email": "john@doe.tst"
            }
        ],
        "require": {

        }
    }

Now just add the guzzle library to the composer.json file in the require section as follows:

    {
        "name": "jd/guzzle demo",
        "authors": [
            {
                "name": "John Doe",
                "email": "john@doe.tst"
            }
        ],
        "require": {
            "guzzle/guzzle": "~3.7"
        }
    }

Having saved the file, you just need to run the composer install command and you should be good to go!

Basics

As a cryptography enthusiast, searching for sources of entropy data for key generation is a hobby of mine. One good source is the ANU Quantum Random Numbers Server at https://qrng.anu.edu.au. Supposedly their RNG is based on quantum physics, so it is worth taking a look at when generating random data with a good entropy level.

Fortunately they have a public API we can consume, so here is where Guzzle will come in handy:

    <?php
    chdir(dirname(__DIR__));

    require_once 'vendor/autoload.php';

    use Guzzle\Http\Client;
    use Guzzle\Http\EntityBody;
    use Guzzle\Http\Message\Request;
    use Guzzle\Http\Message\Response;

    /** @var $client Client */
    $client = new Client("https://qrng.anu.edu.au");

The first two lines should be familiar to anyone using composer and its autoloading mechanisms. When we instantiate a Client object, we should pass as a constructor parameter the URL we want to connect to. Please notice that it does not use path and/or URL parameters – this will come in a minute.

    /** @var $request Request */
    $request = $client->get('/API/jsonI.php?length=10&type=uint8');

    /** @var $response Response */
    $response = $request->send();

    /** @var $body EntityBody */
    $body = $response->getBody(true);

We finally indicate the path (/API/jsonI.php) and the URL parameters (length=10&type=uint8) to the client, building an HTTP GET request via the $client->get() method. If we wanted to build a POST request, the client object has post() and many other HTTP request methods. For sending the request, the $request->send() method should be invoked, catching the response in the $response variable. Finally if we want to see what the response from the remote server was, we execute the $response->getBody() method. TRUE can be sent as a parameter to the getBody() method to get the result as a string.

Path and URL Parameters Manipulation

In the previous example, we used a string to indicate the path and URL parameters to build the request… This might not be the most elegant solution. We might decide to pass on certain URL parameters or use different paths according to certain conditions. We can easily accomplish proper handling of path and URL parameters by making some adjustments to the code.

    /** @var $request Request */
    $request = $client->createRequest();

    $request->setPath('/API/jsonI.php');

    $request->getQuery()
        ->set('length', 10)
        ->set('type', 'uint8');

First we create an empty request from the Guzzle client with the $client->getRequest() method. The request object has several methods; in this case we'll use the setPath() and getQuery() methods. setPath() should be pretty straightforward; as for getQuery, we use Guzzle's fluent interface to set the URL parameters one by one.

Logging

When we develop applications that consume 3rd party services through a network, communication problems often arise, so it is very important to keep a log with all the requests and responses, at least during development stages so we can debug what is going on.

Guzzle provides several plugins. In the case of logging plugins, we have several adapters available at our disposal. For this example we'll use monolog

To have monolog available in your project, you'll have to modify your composer.json file and run the composer update command. Your composer.json file should look like the following:

    {
        "name": "jd/guzzle demo",
        "authors": [
            {
                "name": "John Doe",
                "email": "john@doe.tst"
            }
        ],
        "require": {
            "guzzle/guzzle": "~3.7",
            "monolog/monolog": "1.6.0"
        }
    }

To make Guzzle use monolog and write all requests and responses we send and receive, we need to make some adjustments to our code:

    <?php

    chdir(dirname(__DIR__));

    require_once 'vendor/autoload.php';

    //...
    use Guzzle\Log\MessageFormatter;
    use Guzzle\Log\MonologLogAdapter;
    use Guzzle\Plugin\Log\LogPlugin;
    use Monolog\Handler\StreamHandler;
    use Monolog\Logger;

    $logger = new Logger('client');
    $logger->pushHandler(new StreamHandler('guzzle.log'));

    $logAdapter = new MonologLogAdapter($logger);

    $logPlugin = new LogPlugin($logAdapter, MessageFormatter::DEBUG_FORMAT);

    /** @var $client Client */
    $client = new Client("https://qrng.anu.edu.au");

    $client->addSubscriber($logPlugin);

First we create a monolog instance with $logger = new Logger('client');. Monolog posseses several handlers, in this case we will use the StreamHandler to write to a file named guzzle.log. Then we create guzzle's monolog adapter by instantiating a MonologLogAdapter object. Next we create a log plugin object using Guzzle's log plugin manager LogPlugin. Please notice the MessageFormatter::DEBUG_FORMAT constant which tells Guzzle to log full request and response messages. Please refer to Guzzle's docs to see which message formatters are available. Last, we add the log plugin to the guzzle client by calling $client->addSubscriber($logPlugin);

Interacting with Github's API

Let's have a look at this code and then I´ll explain it line by line

    <?php
    chdir(dirname(__DIR__));

    require_once 'vendor/autoload.php';

    use Guzzle\http\Client;
    use Guzzle\Log\MonologLogAdapter;
    use Guzzle\Plugin\Log\LogPlugin;
    use Guzzle\Log\MessageFormatter;
    use Monolog\Logger;
    use Monolog\Handler\StreamHandler;

    $client = new Client('https://api.github.com');

    $log = new Logger('log');
    $log->pushHandler(new StreamHandler('requests.log'));

    $adapter = new MonologLogAdapter($log);

    $logPlugin = new LogPlugin($adapter, MessageFormatter::DEBUG_FORMAT);

    $client->addSubscriber($logPlugin);

    $request = $client->post('authorizations', array(), 
            json_encode(
                    array(
                            'scopes' => array(
                                    'public_repo',
                                    'user',
                                    'repo',
                                    'gist'
                            ),
                            'note' => 'auto client' . uniqid()
                    )));

    $request->setAuth('john@doe.tst', 'yourpasswordhere');

    $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false);

    $response = $request->send();

    $body = json_decode($response->getBody(true));

    $oauthToken = $body->token;

    $request = $client->get('user/keys');

    $query = $request->getQuery();

    $query->add('access_token', $oauthToken);

    $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false);

    try {

        $response = $request->send();

        $body = $response->getBody(true);

        echo $body;
    } catch (Exception $e) {
        echo $request->getResponse()->getRawHeaders();
    }

Detailed explanation follows.

$client = new Client('https://api.github.com');

We create a client which will use Github's API base URL.

    $log = new Logger('log');
    $log->pushHandler(new StreamHandler('requests.log'));

    $adapter = new MonologLogAdapter($log);    

    $logPlugin = new LogPlugin($adapter, MessageFormatter::DEBUG_FORMAT);

    $client->addSubscriber($logPlugin);

As we previously saw, we set up logging capabilities, so we are able to debug communication issues.

    $request = $client->post('authorizations', array(), 
            json_encode(
                    array(
                            'scopes' => array(
                                    'public_repo',
                                    'user',
                                    'repo',
                                    'gist'
                            ),
                            'note' => 'auto client' . uniqid()
                    )));

First we want to request authorization to use Github's API (http://developer.github.com/v3/oauth/) without having to use our username and password every time, so we need to perform an HTTP POST verb to https://api.github.com/authorizations. Remember that we set up the base URL when we created our client, so we only need to set the path. To see a list of possible parameters that can be sent to Github's API, please refer to their documentation.

    $request->setAuth('john@doe.tst', 'yourpasswordhere');

    $request->getCurlOptions()->set(CURLOPT_SSL_VERIFYPEER, false);

For this one time, we will use HTTP Basic Authentication. Remember that the only mechanism preventing the sending of your credentials in plain text is HTTPS (as shown in the API url) – performing the request through something as insecure as HTTP could lead to someone intercepting your data. In the second line, we are configuring cURL not to verify the SSL certificate, as this can solve several communication issues.

    $response = $request->send();
    $body = json_decode($response->getBody(true));

Finally we send the request. The response is gotten through the getBody() method, the TRUE flag is used to configure Guzzle to return a plain string. Remember that Github's API is RESTful, so everything will be JSON encoded, and thus we will decode it to an object using json_decode().

    $oauthToken = $body->token;

The Oauth token will be contained inside the 'token' attribute. Please refer to Github's API documentation for a detailed explanation of the response format.

    $request = $client->get('user/keys');
    $query = $request->getQuery();
    $query->add('access_token', $oauthToken);

We will be performing a new request. This time we want to list our authorized keys that are configured in our account, mostly for accessing our repositories through SSH. This time an HTTP GET request has to be sent, but instead of using our credentials in plain text, we will use the Oauth token we were given; this token has to be added as a query parameter to the URL we are requesting.

    $response = $request->send();    
    $body = $response->getBody(true);
    echo $body;

Finally we send the request and get the response body, ready to be parsed and used for whichever purpose you like.

Conclusion

This little demonstration is just the tip of the iceberg of what Guzzle is capable of. With a nicely structured plugin system, request and response handling mechanism, you can finally tame the wide quantity of API's the internet has to offer.

If you have a specific use case of Guzzle you'd like to demonstrate or just some general feedback, let us know!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • harris21

    Hello there,

    nice tutorial. Is it possible to demonstrate integration between Twitter API and Guzzle’s Cache plugin? Thanks in advance.

    • Taylor Ren

      Yap. Would like to see the integration with OAuth, in general.

    • http://www.bitfalls.com/ Bruno Skvorc

      We’ll see what we can do!

  • http://www.matthewsetter.com/ Matthew Setter

    Miguel, great article on Guzzle. It’s a fantastic, very fully featured library which integrates really well with so many projects.

  • jmueller

    This looks really promising! Thanks for the tutorial.

  • Gerard van Beek

    I would be interested in integration with Amazon Simple Storage Service