Skip to content

Commit ff1c1b0

Browse files
committed
Add plugin documentation
1 parent ae9d39c commit ff1c1b0

9 files changed

+215
-2
lines changed

docs/plugins.md

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

3-
The plugin system will allow to manipulate requests and responses inside a `HttpClient`.
3+
The plugin system allows to look at requests and responses and replace them if needed, inside an `HttpClient`.
44

5-
TODO: finalize system and document.
5+
Using the `Http\Client\Plugin\PluginClient`, you can inject an `HttpClient`, or an `HttpAsyncClient`, and an array
6+
of plugins implementing the `Http\Client\Plugin\Plugin` interface.
7+
8+
Each plugin can replace 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 response was received.
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+
You can use the 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+
Go to the [tutorial](tutorial.md) to read more about using `HttpClient` and `HttpAsyncClient`
56+
57+
## Available plugins
58+
59+
Each plugin has its own configuration and dependencies, check the documentation for each of the available plugins:
60+
61+
- [Authentication](plugins/authentication.md): Add authentication header on a request
62+
- [Cookie](plugins/cookie.md): Add cookies to request and save them from the response
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 Symfony Stopwatch component](http://symfony.com/doc/current/components/stopwatch.html)
68+
69+
## Order of plugins
70+
71+
When you inject an array of plugins into the `PluginClient`, the order of the plugins matters.
72+
73+
During the request, plugins are called in the order they have in the array, from first to last plugin. Once a response has been received,
74+
they are called again in inverse order, from last to first.
75+
76+
i.e. with the following code:
77+
78+
```php
79+
use Http\Discovery\HttpClientDiscovery;
80+
use Http\Client\Plugin\PluginClient;
81+
use Http\Client\Plugin\RetryPlugin;
82+
use Http\Client\Plugin\RedirectPlugin;
83+
84+
$retryPlugin = new RetryPlugin();
85+
$redirectPlugin = new RedirectPlugin();
86+
87+
$pluginClient = new PluginClient(HttpClientDiscovery::find(), [
88+
$retryPlugin,
89+
$redirectPlugin
90+
]);
91+
```
92+
93+
The execution chain will look like this:
94+
95+
```
96+
Request ---> PluginClient ---> RetryPlugin ---> RedirectPlugin ---> HttpClient ----
97+
| (processing call)
98+
Response <--- PluginClient <--- RetryPlugin <--- RedirectPlugin <--- HttpClient <---
99+
```
100+
101+
In order to have correct behavior over the global process, you need to understand well each plugin used,
102+
and manage a correct order when passing the array to the `PluginClient`
103+
104+
`RetryPlugin` will be best at the end to optimize the retry process, but it can also be good
105+
to have it as the first plugin, if one of the plugins is inconsistent and may need a retry.
106+
107+
The recommended way to order plugins is the following rules:
108+
109+
1. Plugins that modify the request should be at the beginning (like the `AuthenticationPlugin` or the `CookiePlugin`)
110+
2. Plugins which intervene in the workflow should be in the "middle" (like the `RetryPlugin` or the `RedirectPlugin`)
111+
3. Plugins which log information should be last (like the `LoggerPlugin` or the `HistoryPlugin`)
112+
113+
However, there can be exceptions to these rules. For example, for security reasons you might not want to log the authentication header
114+
and chose to put the AuthenticationPlugin after the LoggerPlugin.
115+
116+
## Implementing your own Plugin
117+
118+
Read this [documentation](plugins/plugin-implementation.md) if you want to create your own Plugin.

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/plugin-implementation.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Implementing your own Plugin
2+
3+
When writing your own Plugin, you need to be aware that the `PluginClient` is async first. This means that every plugin must
4+
be written by respecting the `HttpAsyncClient` contract and returns a `Promise`.
5+
6+
Each plugin must implement the `Http\Client\Plugin\Plugin` interface.
7+
8+
This interface defines the `handleRequest` method that allows to modify behavior of the call:
9+
10+
```php
11+
/**
12+
* handle the request and return the response coming from the next callable
13+
*
14+
* @param RequestInterface $request Request to use
15+
* @param callable $next Callback to call to have the request, it muse have the request as it first argument
16+
* @param callable $first First element in the plugin chain, used to to restart a request from the beginning
17+
*
18+
* @return Promise
19+
*/
20+
public function handleRequest(RequestInterface $request, callable $next, callable $first);
21+
```
22+
23+
The `$request` comes from upstream. You can replace it and pass a new version downstream if you need. Always be aware that
24+
the request is immutable.
25+
26+
The `$next` callable is the next plugin in the execution chain. When you need to call it, you must pass the `$request`
27+
as the first argument of this callable.
28+
29+
```
30+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
31+
{
32+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
33+
34+
return $next($newRequest);
35+
}
36+
```
37+
38+
39+
The `$first` callable is the first plugin in the execution. It allows you to completely reboot the execution chain, or send
40+
other request if needed, while still going through all the defined plugins. Like the `$next` callable, you must pass the `$request`
41+
as the first argument of this callable.
42+
43+
```
44+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
45+
{
46+
if ($someCondition) {
47+
$newRequest = new Request();
48+
$promise = $first($newRequest);
49+
50+
// Use the promise do some jobs ...
51+
}
52+
53+
return $next($request);
54+
}
55+
```
56+
57+
In this example the condition is not superfluous, you need to have some way to not calling the $first callable each time or
58+
you will end up with a infinite execution loop.
59+
60+
The `$next` and `$first` callable will return a `Promise`. You can manipulate the `ResponseInterface` or the `Exception`
61+
by using the `then` method of the promise.
62+
63+
```
64+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
65+
{
66+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
67+
68+
return $next($request)->then(function (ResponseInterface $response) {
69+
return $response->withHeader('MyResponseHeader', 'value');
70+
}, function (Exception $exception) {
71+
echo $exception->getMessage();
72+
73+
throw $exception;
74+
});
75+
}
76+
```
77+
78+
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
79+
process.

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)