scratch – Blame information for rev 87
?pathlinks?
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. |