scratch – Blame information for rev 87

Subversion Repositories:
Rev:
Rev Author Line No. Line
87 office 1 ============
2 Event System
3 ============
4  
5 Guzzle uses an event emitter to allow you to easily extend the behavior of a
6 request, change the response associated with a request, and implement custom
7 error handling. All events in Guzzle are managed and emitted by an
8 **event emitter**.
9  
10 Event Emitters
11 ==============
12  
13 Clients, requests, and any other class that implements the
14 ``GuzzleHttp\Common\HasEmitterInterface`` interface have a
15 ``GuzzleHttp\Common\EventEmitter`` object. You can add event *listeners* and
16 event *subscribers* to an event *emitter*.
17  
18 emitter
19 An object that implements ``GuzzleHttp\Common\EventEmitterInterface``. This
20 object emits named events to event listeners. You may register event
21 listeners on subscribers on an emitter.
22  
23 event listeners
24 Callable functions that are registered on an event emitter for specific
25 events. Event listeners are registered on an emitter with a *priority*
26 setting. If no priority is provided, ``0`` is used by default.
27  
28 event subscribers
29 Classes that tell an event emitter what methods to listen to and what
30 functions on the class to invoke when the event is triggered. Event
31 subscribers subscribe event listeners to an event emitter. They should be
32 used when creating more complex event based logic in applications (i.e.,
33 cookie handling is implemented using an event subscriber because it's
34 easier to share a subscriber than an anonymous function and because
35 handling cookies is a complex process).
36  
37 priority
38 Describes the order in which event listeners are invoked when an event is
39 emitted. The higher a priority value, the earlier the event listener will
40 be invoked (a higher priority means the listener is more important). If
41 no priority is provided, the priority is assumed to be ``0``.
42  
43 When specifying an event priority, you can pass ``"first"`` or ``"last"`` to
44 dynamically specify the priority based on the current event priorities
45 associated with the given event name in the emitter. Use ``"first"`` to set
46 the priority to the current highest priority plus one. Use ``"last"`` to
47 set the priority to the current lowest event priority minus one. It is
48 important to remember that these dynamic priorities are calculated only at
49 the point of insertion into the emitter and they are not rearranged after
50 subsequent listeners are added to an emitter.
51  
52 propagation
53 Describes whether or not other event listeners are triggered. Event
54 emitters will trigger every event listener registered to a specific event
55 in priority order until all of the listeners have been triggered **or**
56 until the propagation of an event is stopped.
57  
58 Getting an EventEmitter
59 -----------------------
60  
61 You can get the event emitter of ``GuzzleHttp\Common\HasEmitterInterface``
62 object using the the ``getEmitter()`` method. Here's an example of getting a
63 client object's event emitter.
64  
65 .. code-block:: php
66  
67 $client = new GuzzleHttp\Client();
68 $emitter = $client->getEmitter();
69  
70 .. note::
71  
72 You'll notice that the event emitter used in Guzzle is very similar to the
73 `Symfony2 EventDispatcher component <https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher>`_.
74 This is because the Guzzle event system is based on the Symfony2 event
75 system with several changes. Guzzle uses its own event emitter to improve
76 performance, isolate Guzzle from changes to the Symfony, and provide a few
77 improvements that make it easier to use for an HTTP client (e.g., the
78 addition of the ``once()`` method).
79  
80 Adding Event Listeners
81 ----------------------
82  
83 After you have the emitter, you can register event listeners that listen to
84 specific events using the ``on()`` method. When registering an event listener,
85 you must tell the emitter what event to listen to (e.g., "before", "after",
86 "headers", "complete", "error", etc.), what callable to invoke when the
87 event is triggered, and optionally provide a priority.
88  
89 .. code-block:: php
90  
91 use GuzzleHttp\Event\BeforeEvent;
92  
93 $emitter->on('before', function (BeforeEvent $event) {
94 echo $event->getRequest();
95 });
96  
97 When a listener is triggered, it is passed an event that implements the
98 ``GuzzleHttp\Common\EventInterface`` interface, the name of the event, and the
99 event emitter itself. The above example could more verbosely be written as
100 follows:
101  
102 .. code-block:: php
103  
104 use GuzzleHttp\Event\BeforeEvent;
105  
106 $emitter->on('before', function (
107 BeforeEvent $event,
108 $name,
109 EmitterInterface $emitter
110 ) {
111 echo $event->getRequest();
112 });
113  
114 You can add an event listener that automatically removes itself after it is
115 triggered using the ``once()`` method of an event emitter.
116  
117 .. code-block:: php
118  
119 $client = new GuzzleHttp\Client();
120 $client->getEmitter()->once('before', function () {
121 echo 'This will only happen once... per request!';
122 });
123  
124 Event Propagation
125 -----------------
126  
127 Event listeners can prevent other event listeners from being triggered by
128 stopping an event's propagation.
129  
130 Stopping event propagation can be useful, for example, if an event listener has
131 changed the state of the subject to such an extent that allowing subsequent
132 event listeners to be triggered could place the subject in an inconsistent
133 state. This technique is used in Guzzle extensively when intercepting error
134 events with responses.
135  
136 You can stop the propagation of an event using the ``stopPropagation()`` method
137 of a ``GuzzleHttp\Common\EventInterface`` object:
138  
139 .. code-block:: php
140  
141 use GuzzleHttp\Event\ErrorEvent;
142  
143 $emitter->on('error', function (ErrorEvent $event) {
144 $event->stopPropagation();
145 });
146  
147 After stopping the propagation of an event, any subsequent event listeners that
148 have not yet been triggered will not be triggered. You can check to see if the
149 propagation of an event was stopped using the ``isPropagationStopped()`` method
150 of the event.
151  
152 .. code-block:: php
153  
154 $client = new GuzzleHttp\Client();
155 $emitter = $client->getEmitter();
156 // Note: assume that the $errorEvent was created
157 if ($emitter->emit('error', $errorEvent)->isPropagationStopped()) {
158 echo 'It was stopped!';
159 }
160  
161 .. hint::
162  
163 When emitting events, the event that was emitted is returned from the
164 emitter. This allows you to easily chain calls as shown in the above
165 example.
166  
167 Event Subscribers
168 -----------------
169  
170 Event subscribers are classes that implement the
171 ``GuzzleHttp\Common\EventSubscriberInterface`` object. They are used to register
172 one or more event listeners to methods of the class. Event subscribers tell
173 event emitters exactly which events to listen to and what method to invoke on
174 the class when the event is triggered by called the ``getEvents()`` method of
175 a subscriber.
176  
177 The following example registers event listeners to the ``before`` and
178 ``complete`` event of a request. When the ``before`` event is emitted, the
179 ``onBefore`` instance method of the subscriber is invoked. When the
180 ``complete`` event is emitted, the ``onComplete`` event of the subscriber is
181 invoked. Each array value in the ``getEvents()`` return value MUST
182 contain the name of the method to invoke and can optionally contain the
183 priority of the listener (as shown in the ``before`` listener in the example).
184  
185 .. code-block:: php
186  
187 use GuzzleHttp\Event\EmitterInterface;
188 use GuzzleHttp\Event\SubscriberInterface;
189 use GuzzleHttp\Event\BeforeEvent;
190 use GuzzleHttp\Event\CompleteEvent;
191  
192 class SimpleSubscriber implements SubscriberInterface
193 {
194 public function getEvents()
195 {
196 return [
197 // Provide name and optional priority
198 'before' => ['onBefore', 100],
199 'complete' => ['onComplete'],
200 // You can pass a list of listeners with different priorities
201 'error' => [['beforeError', 'first'], ['afterError', 'last']]
202 ];
203 }
204  
205 public function onBefore(BeforeEvent $event, $name, EmitterInterface $emitter)
206 {
207 echo 'Before!';
208 }
209  
210 public function onComplete(CompleteEvent $event, $name, EmitterInterface $emitter)
211 {
212 echo 'Complete!';
213 }
214 }
215  
216 .. note::
217  
218 You can specify event priorities using integers or ``"first"`` and
219 ``"last"`` to dynamically determine the priority.
220  
221 Event Priorities
222 ================
223  
224 When adding event listeners or subscribers, you can provide an optional event
225 priority. This priority is used to determine how early or late a listener is
226 triggered. Specifying the correct priority is an important aspect of ensuring
227 a listener behaves as expected. For example, if you wanted to ensure that
228 cookies associated with a redirect were added to a cookie jar, you'd need to
229 make sure that the listener that collects the cookies is triggered before the
230 listener that performs the redirect.
231  
232 In order to help make the process of determining the correct event priority of
233 a listener easier, Guzzle provides several pre-determined named event
234 priorities. These priorities are exposed as constants on the
235 ``GuzzleHttp\Event\RequestEvents`` object.
236  
237 last
238 Use ``"last"`` as an event priority to set the priority to the current
239 lowest event priority minus one.
240  
241 first
242 Use ``"first"`` as an event priority to set the priority to the current
243 highest priority plus one.
244  
245 ``GuzzleHttp\Event\RequestEvents::EARLY``
246 Used when you want a listener to be triggered as early as possible in the
247 event chain.
248  
249 ``GuzzleHttp\Event\RequestEvents::LATE``
250 Used when you want a listener to be to be triggered as late as possible in
251 the event chain.
252  
253 ``GuzzleHttp\Event\RequestEvents::PREPARE_REQUEST``
254 Used when you want a listener to be trigger while a request is being
255 prepared during the ``before`` event. This event priority is used by the
256 ``GuzzleHttp\Subscriber\Prepare`` event subscriber which is responsible for
257 guessing a Content-Type, Content-Length, and Expect header of a request.
258 You should subscribe after this event is triggered if you want to ensure
259 that this subscriber has already been triggered.
260  
261 ``GuzzleHttp\Event\RequestEvents::SIGN_REQUEST``
262 Used when you want a listener to be triggered when a request is about to be
263 signed. Any listener triggered at this point should expect that the request
264 object will no longer be mutated. If you are implementing a custom
265 signature subscriber, then you should use this event priority to sign
266 requests.
267  
268 ``GuzzleHttp\Event\RequestEvents::VERIFY_RESPONSE``
269 Used when you want a listener to be triggered when a response is being
270 validated during the ``complete`` event. The
271 ``GuzzleHttp\Subscriber\HttpError`` event subscriber uses this event
272 priority to check if an exception should be thrown due to a 4xx or 5xx
273 level response status code. If you are doing any kind of verification of a
274 response during the complete event, it should happen at this priority.
275  
276 ``GuzzleHttp\Event\RequestEvents::REDIRECT_RESPONSE``
277 Used when you want a listener to be triggered when a response is being
278 redirected during the ``complete`` event. The
279 ``GuzzleHttp\Subscriber\Redirect`` event subscriber uses this event
280 priority when performing redirects.
281  
282 You can use the above event priorities as a guideline for determining the
283 priority of you event listeners. You can use these constants and add to or
284 subtract from them to ensure that a listener happens before or after the named
285 priority.
286  
287 .. note::
288  
289 "first" and "last" priorities are not adjusted after they added to an
290 emitter. For example, if you add a listener with a priority of "first",
291 you can still add subsequent listeners with a higher priority which would
292 be triggered before the listener added with a priority of "first".
293  
294 Working With Request Events
295 ===========================
296  
297 Requests emit lifecycle events when they are transferred.
298  
299 .. important::
300  
301 Request lifecycle events may be triggered multiple times due to redirects,
302 retries, or reusing a request multiple times. Use the ``once()`` method
303 of an event emitter if you only want the event to be triggered once. You
304 can also remove an event listener from an emitter by using the emitter which
305 is provided to the listener.
306  
307 .. _before_event:
308  
309 before
310 ------
311  
312 The ``before`` event is emitted before a request is sent. The event emitted is
313 a ``GuzzleHttp\Event\BeforeEvent``.
314  
315 .. code-block:: php
316  
317 use GuzzleHttp\Client;
318 use GuzzleHttp\Common\EmitterInterface;
319 use GuzzleHttp\Event\BeforeEvent;
320  
321 $client = new Client(['base_url' => 'http://httpbin.org']);
322 $request = $client->createRequest('GET', '/');
323 $request->getEmitter()->on(
324 'before',
325 function (BeforeEvent $e, $name, EmitterInterface $emitter) {
326 echo $name . "\n";
327 // "before"
328 echo $e->getRequest()->getMethod() . "\n";
329 // "GET" / "POST" / "PUT" / etc.
330 echo get_class($e->getClient());
331 // "GuzzleHttp\Client"
332 }
333 );
334  
335 You can intercept a request with a response before the request is sent over the
336 wire. The ``intercept()`` method of the ``BeforeEvent`` accepts a
337 ``GuzzleHttp\Message\ResponseInterface``. Intercepting the event will prevent
338 the request from being sent over the wire and stops the propagation of the
339 ``before`` event, preventing subsequent event listeners from being invoked.
340  
341 .. code-block:: php
342  
343 use GuzzleHttp\Client;
344 use GuzzleHttp\Event\BeforeEvent;
345 use GuzzleHttp\Message\Response;
346  
347 $client = new Client(['base_url' => 'http://httpbin.org']);
348 $request = $client->createRequest('GET', '/status/500');
349 $request->getEmitter()->on('before', function (BeforeEvent $e) {
350 $response = new Response(200);
351 $e->intercept($response);
352 });
353  
354 $response = $client->send($request);
355 echo $response->getStatusCode();
356 // 200
357  
358 .. attention::
359  
360 Any exception encountered while executing the ``before`` event will trigger
361 the ``error`` event of a request.
362  
363 .. _headers_event:
364  
365 headers
366 -------
367  
368 The ``headers`` event is emitted after the headers of a response have been
369 received before any of the response body has been downloaded. The event
370 emitted is a ``GuzzleHttp\Event\HeadersEvent``.
371  
372 This event can be useful if you need to conditionally wrap the response body
373 of a request in a special decorator or if you only want to conditionally
374 download a response body based on response headers.
375  
376 This event cannot be intercepted.
377  
378 .. code-block:: php
379  
380 use GuzzleHttp\Client;
381 use GuzzleHttp\Event\HeadersEvent;
382  
383 $client = new Client(['base_url' => 'http://httpbin.org']);
384 $request = $client->createRequest('GET', '/stream/100');
385 $request->getEmitter()->on('headers', function (HeadersEvent $e) {
386 echo $e->getResponse();
387 // Prints the response headers
388  
389 // Wrap the response body in a custom decorator if the response has a body
390 if ($e->getResponse()->getHeader('Content-Length') ||
391 $e->getResponse()->getHeader('Content-Encoding')
392 ) {
393 $customBody = new MyCustomStreamDecorator($e->getResponse()->getBody());
394 $e->getResponse()->setBody($customBody);
395 }
396 });
397  
398 .. note::
399  
400 A response may or may not yet have a body associated with it. If a request
401 used a ``save_to`` request option, then the response will have a body.
402 Otherwise, the response will have no body but you are free to associate one
403 with the response. As an example, this is done in the
404 `progress subscriber <https://github.com/guzzle/progress-subscriber/blob/master/src/Progress.php>`_.
405  
406 .. _complete_event:
407  
408 complete
409 --------
410  
411 The ``complete`` event is emitted after a transaction completes and an entire
412 response has been received. The event is a ``GuzzleHttp\Event\CompleteEvent``.
413  
414 You can intercept the ``complete`` event with a different response if needed
415 using the ``intercept()`` method of the event. This can be useful, for example,
416 for changing the response for caching.
417  
418 .. code-block:: php
419  
420 use GuzzleHttp\Client;
421 use GuzzleHttp\Event\CompleteEvent;
422 use GuzzleHttp\Message\Response;
423  
424 $client = new Client(['base_url' => 'http://httpbin.org']);
425 $request = $client->createRequest('GET', '/status/302');
426 $cachedResponse = new Response(200);
427  
428 $request->getEmitter()->on(
429 'complete',
430 function (CompleteEvent $e) use ($cachedResponse) {
431 if ($e->getResponse()->getStatusCode() == 302) {
432 // Intercept the original transaction with the new response
433 $e->intercept($cachedResponse);
434 }
435 }
436 );
437  
438 $response = $client->send($request);
439 echo $response->getStatusCode();
440 // 200
441  
442 .. attention::
443  
444 Any ``GuzzleHttp\Exception\RequestException`` encountered while executing
445 the ``complete`` event will trigger the ``error`` event of a request.
446  
447 .. _error_event:
448  
449 error
450 -----
451  
452 The ``error`` event is emitted when a request fails (whether it's from a
453 networking error or an HTTP protocol error). The event emitted is a
454 ``GuzzleHttp\Event\ErrorEvent``.
455  
456 This event is useful for retrying failed requests. Here's an example of
457 retrying failed basic auth requests by re-sending the original request with
458 a username and password.
459  
460 .. code-block:: php
461  
462 use GuzzleHttp\Client;
463 use GuzzleHttp\Event\ErrorEvent;
464  
465 $client = new Client(['base_url' => 'http://httpbin.org']);
466 $request = $client->createRequest('GET', '/basic-auth/foo/bar');
467 $request->getEmitter()->on('error', function (ErrorEvent $e) {
468 if ($e->getResponse()->getStatusCode() == 401) {
469 // Add authentication stuff as needed and retry the request
470 $e->getRequest()->setHeader('Authorization', 'Basic ' . base64_encode('foo:bar'));
471 // Get the client of the event and retry the request
472 $newResponse = $e->getClient()->send($e->getRequest());
473 // Intercept the original transaction with the new response
474 $e->intercept($newResponse);
475 }
476 });
477  
478 .. attention::
479  
480 If an ``error`` event is intercepted with a response, then the ``complete``
481 event of a request is triggered. If the ``complete`` event fails, then the
482 ``error`` event is triggered once again.