summaryrefslogtreecommitdiff
path: root/vendor/guzzle/guzzle/docs/webservice-client/webservice-client.rst
blob: 7ec771e1c92d6e3d5126bfd0752672f000e4ab9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
======================
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 <https://dev.twitter.com/docs/api/1.1>`_.

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 :doc:`service builder <using-the-service-builder>`.

Factory method
--------------

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

.. code-block:: php

    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``.

.. code-block:: php

    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 :doc:`using-the-service-builder`.

.. code-block:: php

    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":

.. code-block:: 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 <http://en.wikipedia.org/wiki/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.

.. code-block:: php

    // 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.

.. code-block:: php

    $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.

.. code-block:: php

    $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.

.. code-block:: php

    // 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 'Guzzle\Common\Exception\InvalidArgumentException' 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
:doc:`create an OperationCommand based on a service description <guzzle-service-descriptions>`. 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.

.. important::::

    When implementing a custom ``build()`` method, be sure to set the class property of ``$this->request`` to an
    instantiated and ready to send request.

The following example shows how to implement the ``getMentions``
`Twitter API <https://dev.twitter.com/docs/api/1.1/get/statuses/mentions_timeline>`_ method using a concrete command.

.. code-block:: php

    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:

#. ``Foo\Command\JarJar`` **->** jar_jar
#. ``Foo\Command\Test`` **->** test
#. ``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:

.. code-block:: json

    {
        "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.

.. code-block:: json

    {
        "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:

.. code-block:: php

    // ...
    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.

.. code-block:: php

    $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.

.. code-block:: php

    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 :ref:`Request options <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.

.. code-block:: php

    $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.

.. code-block:: php

    // 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).

.. _service-client-events:

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.   | * command: Command being prepared        |
|                              | This is also before a request is created.  |                                          |
+------------------------------+--------------------------------------------+------------------------------------------+
| command.after_prepare        | After a command instantiates and           | * command: Command that was prepared     |
|                              | configures its request object.             |                                          |
+------------------------------+--------------------------------------------+------------------------------------------+
| command.before_send          | The client is about to execute a prepared  | * command: Command to execute            |
|                              | command                                    |                                          |
+------------------------------+--------------------------------------------+------------------------------------------+
| command.after_send           | The client successfully completed          | * command: The command that was executed |
|                              | executing a command                        |                                          |
+------------------------------+--------------------------------------------+------------------------------------------+
| command.parse_response       | Called when ``responseType`` is ``class``  | * command: The command with a response   |
|                              | and the response is about to be parsed.    |   about to be parsed.                    |
+------------------------------+--------------------------------------------+------------------------------------------+

.. code-block:: php

    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
    });

.. code-block:: php

    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());