The web service client

The Guzzle\Service namespace contains various abstractions that help to make it easier to interact with a web service API, including commands, service descriptions, and resource iterators.

In this chapter, we'll build a simple Twitter API client.

Creating a client

A class that extends from Guzzle\Service\Client or implements Guzzle\Service\ClientInterface must implement a factory() method in order to be used with a service builder.

Factory method

You can use the factory() method of a client directly if you do not need a service builder.

use mtdowling\TwitterClient;

// Create a client and pass an array of configuration data
$twitter = TwitterClient::factory(array(
    'consumer_key'    => '****',
    'consumer_secret' => '****',
    'token'           => '****',
    'token_secret'    => '****'
));

Note

If you'd like to follow along, here's how to get your Twitter API credentials:

  1. Visit https://dev.twitter.com/apps
  2. Click on an application that you've created
  3. Click on the "OAuth tool" tab
  4. Copy all of the settings under "OAuth Settings"

Implementing a factory method

Creating a client and its factory method is pretty simple. You just need to implement Guzzle\Service\ClientInterface or extend from Guzzle\Service\Client.

namespace mtdowling;

use Guzzle\Common\Collection;
use Guzzle\Plugin\Oauth\OauthPlugin;
use Guzzle\Service\Client;
use Guzzle\Service\Description\ServiceDescription;

/**
 * A simple Twitter API client
 */
class TwitterClient extends Client
{
    public static function factory($config = array())
    {
        // Provide a hash of default client configuration options
        $default = array('base_url' => 'https://api.twitter.com/1.1');

        // The following values are required when creating the client
        $required = array(
            'base_url',
            'consumer_key',
            'consumer_secret',
            'token',
            'token_secret'
        );

        // Merge in default settings and validate the config
        $config = Collection::fromConfig($config, $default, $required);

        // Create a new Twitter client
        $client = new self($config->get('base_url'), $config);

        // Ensure that the OauthPlugin is attached to the client
        $client->addSubscriber(new OauthPlugin($config->toArray()));

        return $client;
    }
}

Service Builder

A service builder is used to easily create web service clients, provides a simple configuration driven approach to creating clients, and allows you to share configuration settings across multiple clients. You can find out more about Guzzle's service builder in Using a service builder.

use Guzzle\Service\Builder\ServiceBuilder;

// Create a service builder and provide client configuration data
$builder = ServiceBuilder::factory('/path/to/client_config.json');

// Get the client from the service builder by name
$twitter = $builder->get('twitter');

The above example assumes you have JSON data similar to the following stored in "/path/to/client_config.json":

{
    "services": {
        "twitter": {
            "class": "mtdowling\\TwitterClient",
            "params": {
                "consumer_key": "****",
                "consumer_secret": "****",
                "token": "****",
                "token_secret": "****"
            }
        }
    }
}

Note

A service builder becomes much more valuable when using multiple web service clients in a single application or if you need to utilize the same client with varying configuration settings (e.g. multiple accounts).

Commands

Commands are a concept in Guzzle that helps to hide the underlying implementation of an API by providing an easy to use parameter driven object for each action of an API. A command is responsible for accepting an array of configuration parameters, serializing an HTTP request, and parsing an HTTP response. Following the command pattern, commands in Guzzle offer a greater level of flexibility when implementing and utilizing a web service client.

Executing commands

You must explicitly execute a command after creating a command using the getCommand() method. A command has an execute() method that may be called, or you can use the execute() method of a client object and pass in the command object. Calling either of these execute methods will return the result value of the command. The result value is the result of parsing the HTTP response with the process() method.

// Get a command from the client and pass an array of parameters
$command = $twitter->getCommand('getMentions', array(
    'count' => 5
));

// Other parameters can be set on the command after it is created
$command['trim_user'] = false;

// Execute the command using the command object.
// The result value contains an array of JSON data from the response
$result = $command->execute();

// You can retrieve the result of the command later too
$result = $command->getResult().

Command object also contains methods that allow you to inspect the HTTP request and response that was utilized with the command.

$request = $command->getRequest();
$response = $command->getResponse();

Note

The format and notation used to retrieve commands from a client can be customized by injecting a custom command factory, Guzzle\Service\Command\Factory\FactoryInterface, on the client using $client->setCommandFactory().

Executing with magic methods

When using method missing magic methods with a command, the command will be executed right away and the result of the command is returned.

$jsonData = $twitter->getMentions(array(
    'count'     => 5,
    'trim_user' => true
));

Creating commands

Commands are created using either the getCommand() method of a client or a magic missing method of a client. Using the getCommand() method allows you to create a command without executing it, allowing for customization of the command or the request serialized by the command.

When a client attempts to create a command, it uses the client's Guzzle\Service\Command\Factory\FactoryInterface. By default, Guzzle will utilize a command factory that first looks for a concrete class for a particular command (concrete commands) followed by a command defined by a service description (operation commands). We'll learn more about concrete commands and operation commands later in this chapter.

// Get a command from the twitter client.
$command = $twitter->getCommand('getMentions');
$result = $command->execute();

Unless you've skipped ahead, running the above code will throw an exception.

PHP Fatal error: Uncaught exception 'GuzzleCommonExceptionInvalidArgumentException' with message 'Command was not found matching getMentions'

This exception was thrown because the "getMentions" command has not yet been implemented. Let's implement one now.

Concrete commands

Commands can be created in one of two ways: create a concrete command class that extends Guzzle\Service\Command\AbstractCommand or create an OperationCommand based on a service description. The recommended approach is to use a service description to define your web service, but you can use concrete commands when custom logic must be implemented for marshaling or unmarshaling a HTTP message.

Commands are the method in which you abstract away the underlying format of the requests that need to be sent to take action on a web service. Commands in Guzzle are meant to be built by executing a series of setter methods on a command object. Commands are only validated right before they are executed. A Guzzle\Service\Client object is responsible for executing commands. Commands created for your web service must implement Guzzle\Service\Command\CommandInterface, but it's easier to extend the Guzzle\Service\Command\AbstractCommand class, implement the build() method, and optionally implement the process() method.

Serializing requests

The build() method of a command is responsible for using the arguments of the command to build and serialize a HTTP request and set the request on the $request property of the command object. This step is usually taken care of for you when using a service description driven command that uses the default Guzzle\Service\Command\OperationCommand. You may wish to implement the process method yourself when you aren't using a service description or need to implement more complex request serialization.

The following example shows how to implement the getMentions Twitter API method using a concrete command.

namespace mtdowling\Twitter\Command;

use Guzzle\Service\Command\AbstractCommand;

class GetMentions extends AbstractCommand
{
    protected function build()
    {
        // Create the request property of the command
        $this->request = $this->client->get('statuses/mentions_timeline.json');

        // Grab the query object of the request because we will use it for
        // serializing command parameters on the request
        $query = $this->request->getQuery();

        if ($this['count']) {
            $query->set('count', $this['count']);
        }

        if ($this['since_id']) {
            $query->set('since_id', $this['since_id']);
        }

        if ($this['max_id']) {
            $query->set('max_id', $this['max_id']);
        }

        if ($this['trim_user'] !== null) {
            $query->set('trim_user', $this['trim_user'] ? 'true' : 'false');
        }

        if ($this['contributor_details'] !== null) {
            $query->set('contributor_details', $this['contributor_details'] ? 'true' : 'false');
        }

        if ($this['include_entities'] !== null) {
            $query->set('include_entities', $this['include_entities'] ? 'true' : 'false');
        }
    }
}

By default, a client will attempt to find concrete command classes under the Command namespace of a client. First the client will attempt to find an exact match for the name of the command to the name of the command class. If an exact match is not found, the client will calculate a class name using inflection. This is calculated based on the folder hierarchy of a command and converting the CamelCased named commands into snake_case. Here are some examples on how the command names are calculated:

  1. Foo\Command\JarJar -> jar_jar
  2. Foo\Command\Test -> test
  3. Foo\Command\People\GetCurrentPerson -> people.get_current_person

Notice how any sub-namespace beneath Command is converted from \ to . (a period). CamelCasing is converted to lowercased snake_casing (e.g. JarJar == jar_jar).

Parsing responses

The process() method of a command is responsible for converting an HTTP response into something more useful. For example, a service description operation that has specified a model object in the responseClass attribute of the operation will set a Guzzle\Service\Resource\Model object as the result of the command. This behavior can be completely modified as needed-- even if you are using operations and responseClass models. Simply implement a custom process() method that sets the $this->result class property to whatever you choose. You can reuse parts of the default Guzzle response parsing functionality or get inspiration from existing code by using Guzzle\Service\Command\OperationResponseParser and Guzzle\Service\Command\DefaultResponseParser classes.

If you do not implement a custom process() method and are not using a service description, then Guzzle will attempt to guess how a response should be processed based on the Content-Type header of the response. Because the Twitter API sets a Content-Type: application/json header on this response, we do not need to implement any custom response parsing.

Operation commands

Operation commands are commands in which the serialization of an HTTP request and the parsing of an HTTP response are driven by a Guzzle service description. Because request serialization, validation, and response parsing are described using a DSL, creating operation commands is a much faster process than writing concrete commands.

Creating operation commands for our Twitter client can remove a great deal of redundancy from the previous concrete command, and allows for a deeper runtime introspection of the API. Here's an example service description we can use to create the Twitter API client:

{
    "name": "Twitter",
    "apiVersion": "1.1",
    "baseUrl": "https://api.twitter.com/1.1",
    "description": "Twitter REST API client",
    "operations": {
        "GetMentions": {
            "httpMethod": "GET",
            "uri": "statuses/mentions_timeline.json",
            "summary": "Returns the 20 most recent mentions for the authenticating user.",
            "responseClass": "GetMentionsOutput",
            "parameters": {
                "count": {
                    "description": "Specifies the number of tweets to try and retrieve",
                    "type": "integer",
                    "location": "query"
                },
                "since_id": {
                    "description": "Returns results with an ID greater than the specified ID",
                    "type": "integer",
                    "location": "query"
                },
                "max_id": {
                    "description": "Returns results with an ID less than or equal to the specified ID.",
                    "type": "integer",
                    "location": "query"
                },
                "trim_user": {
                    "description": "Limits the amount of data returned for each user",
                    "type": "boolean",
                    "location": "query"
                },
                "contributor_details": {
                    "description": "Adds more data to contributor elements",
                    "type": "boolean",
                    "location": "query"
                },
                "include_entities": {
                    "description": "The entities node will be disincluded when set to false.",
                    "type": "boolean",
                    "location": "query"
                }
            }
        }
    },
    "models": {
        "GetMentionsOutput": {
            "type": "object",
            "additionalProperties": {
                "location": "json"
            }
        }
    }
}

If you're lazy, you can define the API in a less descriptive manner using additionalParameters. additionalParameters define the serialization and validation rules of parameters that are not explicitly defined in a service description.

{
    "name": "Twitter",
    "apiVersion": "1.1",
    "baseUrl": "https://api.twitter.com/1.1",
    "description": "Twitter REST API client",
    "operations": {
        "GetMentions": {
            "httpMethod": "GET",
            "uri": "statuses/mentions_timeline.json",
            "summary": "Returns the 20 most recent mentions for the authenticating user.",
            "responseClass": "GetMentionsOutput",
            "additionalParameters": {
                "location": "query"
            }
        }
    },
    "models": {
        "GetMentionsOutput": {
            "type": "object",
            "additionalProperties": {
                "location": "json"
            }
        }
    }
}

You should attach the service description to the client at the end of the client's factory method:

// ...
class TwitterClient extends Client
{
    public static function factory($config = array())
    {
        // ... same code as before ...

        // Set the service description
        $client->setDescription(ServiceDescription::factory('path/to/twitter.json'));

        return $client;
    }
}

The client can now use operations defined in the service description instead of requiring you to create concrete command classes. Feel free to delete the concrete command class we created earlier.

$jsonData = $twitter->getMentions(array(
    'count'     => 5,
    'trim_user' => true
));

Executing commands in parallel

Much like HTTP requests, Guzzle allows you to send multiple commands in parallel. You can send commands in parallel by passing an array of command objects to a client's execute() method. The client will serialize each request and send them all in parallel. If an error is encountered during the transfer, then a Guzzle\Service\Exception\CommandTransferException is thrown, which allows you to retrieve a list of commands that succeeded and a list of commands that failed.

use Guzzle\Service\Exception\CommandTransferException;

$commands = array();
$commands[] = $twitter->getCommand('getMentions');
$commands[] = $twitter->getCommand('otherCommandName');
// etc...

try {
    $result = $client->execute($commands);
    foreach ($result as $command) {
        echo $command->getName() . ': ' . $command->getResponse()->getStatusCode() . "\n";
    }
} catch (CommandTransferException $e) {
    // Get an array of the commands that succeeded
    foreach ($e->getSuccessfulCommands() as $command) {
        echo $command->getName() . " succeeded\n";
    }
    // Get an array of the commands that failed
    foreach ($e->getFailedCommands() as $command) {
        echo $command->getName() . " failed\n";
    }
}

Note

All commands executed from a client using an array must originate from the same client.

Special command options

Guzzle exposes several options that help to control how commands are validated, serialized, and parsed. Command options can be specified when creating a command or in the command.params parameter in the Guzzle\Service\Client.

command.request_options Option used to add Request options to the request created by a command
command.hidden_params An array of the names of parameters ignored by the additionalParameters parameter schema
command.disable_validation Set to true to disable JSON schema validation of the command's input parameters
command.response_processing Determines how the default response parser will parse the command. One of "raw" no parsing, "model" (the default method used to parse commands using response models defined in service descriptions)
command.headers (deprecated) Option used to specify custom headers. Use command.request_options instead
command.on_complete (deprecated) Option used to add an onComplete method to a command. Use command.after_send event instead
command.response_body (deprecated) Option used to change the entity body used to store a response. Use command.request_options instead

Advanced client configuration

Default command parameters

When creating a client object, you can specify default command parameters to pass into all commands. Any key value pair present in the command.params settings of a client will be added as default parameters to any command created by the client.

$client = new Guzzle\Service\Client(array(
    'command.params' => array(
        'default_1' => 'foo',
        'another'   => 'bar'
    )
));

Magic methods

Client objects will, by default, attempt to create and execute commands when a missing method is invoked on a client. This powerful concept applies to both concrete commands and operation commands powered by a service description. This makes it appear to the end user that you have defined actual methods on a client object, when in fact, the methods are invoked using PHP's magic __call method.

The __call method uses the getCommand() method of a client, which uses the client's internal Guzzle\Service\Command\Factory\FactoryInterface object. The default command factory allows you to instantiate operations defined in a client's service description. The method in which a client determines which command to execute is defined as follows:

  1. The client will first try to find a literal match for an operation in the service description.
  2. If the literal match is not found, the client will try to uppercase the first character of the operation and find the match again.
  3. If a match is still not found, the command factory will inflect the method name from CamelCase to snake_case and attempt to find a matching command.
  4. If a command still does not match, an exception is thrown.
// Use the magic method
$result = $twitter->getMentions();

// This is exactly the same as:
$result = $twitter->getCommand('getMentions')->execute();

You can disable magic methods on a client by passing false to the enableMagicMethod() method.

Custom command factory

A client by default uses the Guzzle\Service\Command\Factory\CompositeFactory which allows multiple command factories to attempt to create a command by a certain name. The default CompositeFactory uses a ConcreteClassFactory and a ServiceDescriptionFactory if a service description is specified on a client. You can specify a custom command factory if your client requires custom command creation logic using the setCommandFactory() method of a client.

Custom resource Iterator factory

Resource iterators can be retrieved from a client using the getIterator($name) method of a client. This method uses a client's internal Guzzle\Service\Resource\ResourceIteratorFactoryInterface object. A client by default uses a Guzzle\Service\Resource\ResourceIteratorClassFactory to attempt to find concrete classes that implement resource iterators. The default factory will first look for matching iterators in the Iterator subdirectory of the client followed by the Model subdirectory of a client. Use the setResourceIteratorFactory() method of a client to specify a custom resource iterator factory.

Plugins and events

Guzzle\Service\Client exposes various events that allow you to hook in custom logic. A client object owns a Symfony\Component\EventDispatcher\EventDispatcher object that can be accessed by calling $client->getEventDispatcher(). You can use the event dispatcher to add listeners (a simple callback function) or event subscribers (classes that listen to specific events of a dispatcher).

Events emitted from a Service Client

A Guzzle\Service\Client object emits the following events:

Event name Description Event data
client.command.create The client created a command object
  • client: Client object
  • command: Command object
command.before_prepare Before a command is validated and built. This is also before a request is created.
  • command: Command being prepared
command.after_prepare After a command instantiates and configures its request object.
  • command: Command that was prepared
command.before_send The client is about to execute a prepared command
  • command: Command to execute
command.after_send The client successfully completed executing a command
  • command: The command that was executed
command.parse_response Called when responseType is class and the response is about to be parsed.
  • command: The command with a response about to be parsed.
use Guzzle\Common\Event;
use Guzzle\Service\Client;

$client = new Client();

// create an event listener that operates on request objects
$client->getEventDispatcher()->addListener('command.after_prepare', function (Event $event) {
    $command = $event['command'];
    $request = $command->getRequest();

    // do something with request
});
use Guzzle\Common\Event;
use Guzzle\Common\Client;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class EventSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(
            'client.command.create' => 'onCommandCreate',
            'command.parse_response' => 'onParseResponse'
        );
    }

    public function onCommandCreate(Event $event)
    {
        $client = $event['client'];
        $command = $event['command'];
        // operate on client and command
    }

    public function onParseResponse(Event $event)
    {
        $command = $event['command'];
        // operate on the command
    }
}

$client = new Client();

$client->addSubscriber(new EventSubscriber());
comments powered by Disqus