Skip to content

Commit 25fa2b5

Browse files
committed
Add plugin documentation
1 parent ae9d39c commit 25fa2b5

File tree

8 files changed

+220
-2
lines changed

8 files changed

+220
-2
lines changed

docs/plugins.md

Lines changed: 199 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,202 @@
11
# Plugin System
22

3-
The plugin system will allow to manipulate requests and responses inside a `HttpClient`.
3+
The plugin system allow to manipulate requests and responses inside a `HttpClient`.
44

5-
TODO: finalize system and document.
5+
By using the `Http\Client\Plugin\PluginClient` you can inject a `HttpClient` or a `HttpAsyncClient`, and an array
6+
of plugins implementing the `Http\Client\Plugin\Plugin` interface.
7+
8+
Each plugin can modify the `RequestInterface` sent or the `ResponseInterface` received. It can also change the behavior of a call
9+
like retrying the request or emit another one when a redirection is present.
10+
11+
## Install
12+
13+
Install the plugin client in your project with composer:
14+
15+
``` bash
16+
composer require "php-http/plugins:^1.0"
17+
```
18+
19+
## Usage
20+
21+
First you need to have some plugins:
22+
23+
```php
24+
use Http\Client\Plugin\RetryPlugin;
25+
use Http\Client\Plugin\RedirectPlugin;
26+
27+
$retryPlugin = new RetryPlugin();
28+
$redirectPlugin = new RedirectPlugin();
29+
```
30+
31+
Then you can create a `PluginClient`:
32+
33+
```php
34+
use Http\Discovery\HttpClientDiscovery;
35+
use Http\Client\Plugin\PluginClient;
36+
37+
...
38+
39+
$pluginClient = new PluginClient(HttpClientDiscovery::find(), [
40+
$retryPlugin,
41+
$redirectPlugin
42+
]);
43+
```
44+
45+
After you can use this plugin client like a classic `Http\Client\HttpClient` or `Http\Client\HttpAsyncClient` one:
46+
47+
```php
48+
// Send a request
49+
$response = $pluginClient->sendRequest($request);
50+
51+
// Send an asynchronous request
52+
$promise = $pluginClient->sendAsyncRequest($request);
53+
```
54+
55+
Read the [tutorial](tutorial.md) to know more about `HttpClient` and `HttpAsyncClient`
56+
57+
## Available plugins
58+
59+
Each plugin have its own configuration and dependencies, check the documentation for each of the available plugin:
60+
61+
- [Authentication](plugins/authentication.md): Add authentication header on a request
62+
- [Cookie](plugins/cookie.md): Add cookie to request and save them from the response by using a CookieJar
63+
- [Encoding](plugins/encoding.md): Add support for receiving chunked, deflate or gzip response
64+
- [Error](plugins/error.md): Transform bad response (400 to 599) to exception
65+
- [Redirect](plugins/redirect.md): Follow redirection coming from 3XX responses
66+
- [Retry](plugins/retry.md): Retry a failed call
67+
- [Stopwatch](plugins/stopwatch.md): Log time of a request call by using the Stopwatch component
68+
69+
## Order of plugins
70+
71+
When you inject an array of plugins into the `PluginClient`, order of the plugins in array matters.
72+
73+
Plugins are transformed into a plugin chain, where the first element in array will be called first, and,
74+
obviously, the last element will be called at the end. However when the `ResponseInterface` or the `Promise` is
75+
received from the underlying client, this execution chain is revert and the last plugin defined will be called first.
76+
77+
i.e. with the following code:
78+
79+
```php
80+
use Http\Discovery\HttpClientDiscovery;
81+
use Http\Client\Plugin\PluginClient;
82+
use Http\Client\Plugin\RetryPlugin;
83+
use Http\Client\Plugin\RedirectPlugin;
84+
85+
$retryPlugin = new RetryPlugin();
86+
$redirectPlugin = new RedirectPlugin();
87+
88+
$pluginClient = new PluginClient(HttpClientDiscovery::find(), [
89+
$retryPlugin,
90+
$redirectPlugin
91+
]);
92+
```
93+
94+
The execution chain will look like this:
95+
96+
```
97+
Request ---> PluginClient ---> RetryPlugin ---> RedirectPlugin ---> HttpClient ----
98+
| (processing call)
99+
Response <--- PluginClient <--- RetryPlugin <--- RedirectPlugin <--- HttpClient <---
100+
```
101+
102+
In order to have correct behavior over the global process, you need to understand well each plugin used,
103+
and manage a correct order when passing the array to the `PluginClient`
104+
105+
`RetryPlugin` will be best at then for example if you want to optimize the retry process, but also it can be good
106+
to have it in the first plugin, if one is inconsistent and may need a retry.
107+
108+
Our recommendation, which apply to most of the use case, is to organize your plugins with the following rule:
109+
110+
1. Plugins that modify the request should be at the beginning (like the `AuthenticationPlugin` or the `CookiePlugin`)
111+
2. Plugins which intervene in the workflow should be in the "middle" (like the `RetryPlugin` or the `RedirectPlugin`)
112+
3. Plugins which log and a debug information about a call should be last (like the `LoggerPlugin` or the `HistoryPlugin`)
113+
114+
However there may be exception in this rule, if, for security reason, you don't want to log the authentication header, it will be better to put
115+
this plugin after the `LoggerPlugin` one.
116+
117+
## Implementing your own Plugin
118+
119+
When writing your own Plugin, you need to be aware that the `PluginClient` is async first. It means that every plugin must
120+
be written by respecting the `HttpAsyncClient` contract and use `Promise` as the return.
121+
122+
Each plugin must implement the `Http\Client\Plugin\Plugin` interface.
123+
124+
This interface defined the `handleRequest` method which allow to modify behavior of the call:
125+
126+
```php
127+
/**
128+
* handle the request and return the response coming from the next callable
129+
*
130+
* @param RequestInterface $request Request to use
131+
* @param callable $next Callback to call to have the request, it muse have the request as it first argument
132+
* @param callable $first First element in the plugin chain, used to to restart a request from the beginning
133+
*
134+
* @return Promise
135+
*/
136+
public function handleRequest(RequestInterface $request, callable $next, callable $first);
137+
```
138+
139+
The `$request` is the one created by the client, you can transform it, or do what you like with it. Always be aware that
140+
the request is immutable.
141+
142+
```
143+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
144+
{
145+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
146+
}
147+
```
148+
149+
The `$next` callable is the next plugin in the execution chain. When you need to call it, you must pass the `$request`
150+
as the first argument of this callable.
151+
152+
```
153+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
154+
{
155+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
156+
157+
return $next($request);
158+
}
159+
```
160+
161+
162+
The `$first` callable is the first plugin in the execution. It allows you to completely reboot the execution chain, or sends
163+
other request if needed while still using all the plugins defined. Like the `$next` callable, you must pass the `$request`
164+
as the first argument of this callable.
165+
166+
```
167+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
168+
{
169+
if ($someCondition) {
170+
$newRequest = new Request();
171+
$promise = $first($newRequest);
172+
173+
// Use the promise do some jobs ...
174+
}
175+
176+
return $next($request);
177+
}
178+
```
179+
180+
In this example the condition is not superfluous, you need to have some way to not calling the $first callable each time or
181+
you will end up with a infinite execution loop.
182+
183+
The `$next` and `$first` callable will return a `Promise`, you can manipulate the `ResponseInterface` or the `Exception`
184+
by using the `then` method of the promise.
185+
186+
```
187+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
188+
{
189+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
190+
191+
return $next($request)->then(function (ResponseInterface $response) {
192+
return $response->withHeader('MyResponseHeader', 'value');
193+
}, function (Exception $exception) {
194+
echo $exception->getMessage();
195+
196+
throw $exception;
197+
});
198+
}
199+
```
200+
201+
Anyway it is always a good practice to read existing implementation inside the [plugin repository](https://github.com/php-http/plugins) to better understand the whole
202+
process;

docs/plugins/authentication.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Authentication Plugin
2+
3+
TODO: explain the authentication plugin

docs/plugins/cookie.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Cookie Plugin
2+
3+
TODO: explain the cookie plugin

docs/plugins/encoding.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Encoding Plugin
2+
3+
TODO: explain the encoding plugin

docs/plugins/error.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Error Plugin
2+
3+
TODO: explain the error plugin

docs/plugins/redirect.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Redirect Plugin
2+
3+
TODO: explain the redirect plugin

docs/plugins/retry.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Retry Plugin
2+
3+
TODO: explain the retry plugin

docs/plugins/stopwatch.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Stopwatch Plugin
2+
3+
TODO: explain the stopwatch plugin

0 commit comments

Comments
 (0)