10000 Update plugin documentation · php-http/documentation@56b7e8a · GitHub
[go: up one dir, main page]

Skip to content

Commit 56b7e8a

Browse files
committed
Update plugin documentation
1 parent 20259ae commit 56b7e8a

File tree

4 files changed

+144
-157
lines changed

4 files changed

+144
-157
lines changed

docs/httplug/plugins.md

Lines changed: 131 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,184 @@
1-
# Plugin System
1+
# HTTPlug Plugins
22

3-
The plugin system allows to look at requests and responses and replace them if needed, inside an `HttpClient`.
4-
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.
9-
It can also change the behavior of a call,
10-
like retrying the request or emit another one when a redirection response was received.
3+
The plugin system allows to wrap a Client and add some processing logic prior to and/or after sending the actual
4+
request or you can even start a completely new request. This gives you full control over what happens in your workflow.
115

126

137
## Install
148

15-
Install the plugin client in your project with composer:
9+
Install the plugin client in your project with [Composer](https://getcomposer.org/):
1610

1711
``` bash
18-
composer require "php-http/plugins:^1.0"
12+
$ composer require php-http/plugins
1913
```
2014

2115

22-
## Usage
16+
## How it works
17+
18+
In the plugin package, you can find the following content:
19+
20+
- the Plugin Client itself which acts as a wrapper around any kind of HTTP Client (sync/async)
21+
- a Plugin interface
22+
- a set of core plugins (see the full list in the left side navigation)
23+
24+
The Plugin Client accepts an HTTP Client implementation and an array of plugins.
2325

24-
First you need to have some plugins:
26+
Let's see an example:
2527

2628
``` php
29+
use Http\Discovery\HttpClientDiscovery;
30+
use Http\Client\Plugin\PluginClient;
2731
use Http\Client\Plugin\RetryPlugin;
2832
use Http\Client\Plugin\RedirectPlugin;
2933

3034
$retryPlugin = new RetryPlugin();
3135
$redirectPlugin = new RedirectPlugin();
36+
37+
$pluginClient = new PluginClient(
38+
HttpClientDiscovery::find(),
39+
[
40+
$retryPlugin,
41+
$redirectPlugin,
42+
]
43+
);
3244
```
3345

34-
Then you can create a `PluginClient`:
46+
The Plugin Client accepts and implements both `Http\Client\HttpClient` and `Http\Client\HttpAsyncClient`, so you can use
47+
both ways to send a request. In case the passed client implements only one of these interfaces, the Plugin Client
48+
"emulates" the other behavior as a fallback.
3549

36-
``` php
37-
use Http\Discovery\HttpClientDiscovery;
38-
use Http\Client\Plugin\PluginClient;
50+
It is important, that the order of plugins matters. During the request, plugins are called in the order they have
51+
been added, from first to last. Once a response has been received, they are called again in reversed order,
52+
from last to first.
3953

40-
...
54+
In case of our previous example, the execution chain will look like this:
4155

42-
$pluginClient = new PluginClient(HttpClientDiscovery::find(), [
43-
$retryPlugin,
44-
$redirectPlugin
45-
]);
56+
```
57+
Request ---> PluginClient ---> RetryPlugin ---> RedirectPlugin ---> HttpClient ----
58+
| (processing call)
59+
Response <--- PluginClient <--- RetryPlugin <--- RedirectPlugin <--- HttpClient <---
4660
```
4761

48-
You can use the plugin client like a classic `Http\Client\HttpClient` or `Http\Client\HttpAsyncClient` one:
62+
In order to have correct behavior over the global process, you need to understand well how each plugin is used,
63+
and manage a correct order when passing the array to the Plugin Client.
4964

50-
``` php
51-
// Send a request
52-
$response = $pluginClient->sendRequest($request);
65+
Retry Plugin will be best at the end to optimize the retry process, but it can also be good
66+
to have it as the first plugin, if one of the plugins is inconsistent and may need a retry.
5367

54-
// Send an asynchronous request
55-
$promise = $pluginClient->sendAsyncRequest($request);
56-
```
68+
The recommended way to order plugins is the following:
69+
70+
1. Plugins that modify the request should be at the beginning (like Authentication or Cookie Plugin)
71+
2. Plugins which intervene in the workflow should be in the "middle" (like Retry or Redirect Plugin)
72+
3. Plugins which log information should be last (like Logger or History Plugin)
73+
74+
!!! note "Note:"
75+
There can be exceptions to these rules. For example,
76+
for security reasons you might not want to log the authentication information (like `Authorization` header)
77+
and choose to put the Authentication Plugin after the Logger Plugin.
5778

58-
Go to the [tutorial](tutorial.md) to read more about using `HttpClient` and `HttpAsyncClient`
5979

80+
## Implement your own
6081

61-
## Available plugins
82+
When writing your own Plugin, you need to be aware that the Plugin Client is async first.
83+
This means that every plugin must be written with Promises. More about this later.
6284

63-
Each plugin has its own configuration and dependencies, check the documentation for each of the available plugins:
85+
Each plugin must implement the `Http\Client\Plugin\Plugin` interface.
6486

65-
- [Authentication](plugins/authentication.md): Add authentication header on a request
66-
- [Cookie](plugins/cookie.md): Add cookies to request and save them from the response
67-
- [Encoding](plugins/encoding.md): Add support for receiving chunked, deflate or gzip response
68-
- [Error](plugins/error.md): Transform bad response (400 to 599) to exception
69-
- [Redirect](plugins/redirect.md): Follow redirection coming from 3XX responses
70-
- [Retry](plugins/retry.md): Retry a failed call
71-
- [Stopwatch](plugins/stopwatch.md): Log time of a request call by using [the Symfony Stopwatch component](http://symfony.com/doc/current/components/stopwatch.html)
87+
This interface defines the `handleRequest` method that allows to modify behavior of the call:
7288

89+
```php
90+
/**
91+
* Handles the request and returns the response coming from the next callable.
92+
*
93+
* @param RequestInterface $request Request to use.
94+
* @param callable $next Callback to call to have the request, it muse have the request as it first argument.
95+
* @param callable $first First element in the plugin chain, used to to restart a request from the beginning.
96+
*
97+
* @return Promise
98+
*/
99+
public function handleRequest(RequestInterface $request, callable $next, callable $first);
100+
```
73101

74-
## Order of plugins
102+
The `$request` comes from an upstream plugin or Plugin Client itself.
103+
You can replace it and pass a new version downstream if you need.
75104

76-
When you inject an array of plugins into the `PluginClient`, the order of the plugins matters.
105+
!!! note "Note:"
106+
Be aware that the request is immutable.
77107

78-
During the request, plugins are called in the order they have in the array, from first to last plugin.
79-
Once a response has been received, they are called again in inverse order, from last to first.
80108

81-
i.e. with the following code:
109+
The `$next` callable is the next plugin in the execution chain. When you need to call it, you must pass the `$request`
110+
as the first argument of this callable.
82111

83-
```php
84-
use Http\Discovery\HttpClientDiscovery;
85-
use Http\Client\Plugin\PluginClient;
86-
use Http\Client\Plugin\RetryPlugin;
87-
use Http\Client\Plugin\RedirectPlugin;
112+
For example a simple plugin setting a header would look like this:
88113

89-
$retryPlugin = new RetryPlugin();
90-
$redirectPlugin = new RedirectPlugin();
114+
``` php
115+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
116+
{
117+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
91118

92-
$pluginClient = new PluginClient(HttpClientDiscovery::find(), [
93-
$retryPlugin,
94-
$redirectPlugin
95-
]);
119+
return $next($newRequest);
120+
}
96121
```
97122

98-
The execution chain will look like this:
123+
The `$first` callable is the first plugin in the chain. It allows you to completely reboot the execution chain, or send
124+
another request if needed, while still going through all the defined plugins.
125+
Like in case of the `$next` callable, you must pass the `$request` as the first argument.
99126

100127
```
101-
Request ---> PluginClient ---> RetryPlugin ---> RedirectPlugin ---> HttpClient ----
102-
| (processing call)
103-
Response <--- PluginClient <--- RetryPlugin <--- RedirectPlugin <--- HttpClient <---
128+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
129+
{
130+
if ($someCondition) {
131+
$newRequest = new Request();
132+
$promise = $first($newRequest);
133+
134+
// Use the promise to do some jobs ...
135+
}
136+
137+
return $next($request);
138+
}
104139
```
105140

106-
In order to have correct behavior over the global process, you need to understand well each plugin used,
107-
and manage a correct order when passing the array to the `PluginClient`
141+
!!! warning "Warning:"
142+
In this example the condition is not superfluous:
143+
you need to have some way to not call the `$first` callable each time
144+
or you will end up in an infinite execution loop.
108145

109-
`RetryPlugin` will be best at the end to optimize the retry process, but it can also be good
110-
to have it as the first plugin, if one of the plugins is inconsistent and may need a retry.
146+
The `$next` and `$first` callables will return a Promise (defined in `php-http/promise`).
147+
You can manipulate the `ResponseInterface` or the `Exception` by using the `then` method of the promise.
148+
149+
```
150+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
151+
{
152+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
153+
154+
return $next($request)->then(function (ResponseInterface $response) {
155+
return $response->withHeader('MyResponseHeader', 'value');
156+
}, function (Exception $exception) {
157+
echo $exception->getMessage();
158+
159+
throw $exception;
160+
});
161+
}
162+
```
163+
164+
!!! warning "Warning:"
165+
Contract for the `Http\Promise\Promise` is temporary until a
166+
[PSR is released](https://groups.google.com/forum/?fromgroups#!topic/php-fig/wzQWpLvNSjs).
167+
Once it is out, we will use this PSR in HTTPlug and deprecate the old contract.
111168

112-
The recommended way to order plugins is the following rules:
113169

114-
1. Plugins that modify the request should be at the beginning (like the `AuthenticationPlugin` or the `CookiePlugin`)
115-
2. Plugins which intervene in the workflow should be in the "middle" (like the `RetryPlugin` or the `RedirectPlugin`)
116-
3. Plugins which log information should be last (like the `LoggerPlugin` or the `HistoryPlugin`)
170+
To better understand the whole process check existing implementations in the
171+
[plugin repository](https://github.com/php-http/plugins).
117172

118-
However, there can be exceptions to these rules. For example,
119-
for security reasons you might not want to log the authentication header
120-
and chose to put the AuthenticationPlugin after the LoggerPlugin.
121173

174+
## Contribution
122175

123-
## Implementing your own Plugin
176+
We are always open to contributions. Either in form of Pull Requests to the core package or self-made plugin packages.
177+
We encourage everyone to prefer sending Pull Requests, however we don't promise that every plugin gets
178+
merged into the core. If this is the case, it is not because we think your work is not good enough. We try to keep
179+
the core as small as possible with the most widely used plugin implementations.
124180

125-
Read this [documentation](plugins/plugin-implementation.md) if you want to create your own Plugin.
181+
Even if we think that a plugin is not suitable for the core, we want to help you sharing your work with the community.
182+
You can always open a Pull Request to place a link and a small description of your plugin on the
183+
[Third Party Plugins](plugins/third-party-plugins.md) page. In special cases,
184+
we might offer you to host your package under the PHP HTTP namespace.
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
# Authentication Plugin
22

3-
This plugin uses the [authentication component](/components/authentication) to authenticate requests sent through
4-
the client.
3+
This plugin uses the [authentication component](http://docs.httplug.io/en/latest/components/authentication/)
4+
from `php-http/message` to authenticate requests sent through the client.
55

66

7-
## Usage
8-
97
``` php
8+
use Http\Discovery\HttpClientDiscovery;
9+
use Http\Message\Authentication\BasicAuth;
1010
use Http\Plugins\PluginClient;
1111
use Http\Plugins\AuthenticationPlugin;
1212

13-
$pluginClient = new PluginClient(new HttpClient(), [new AuthenticationPlugin(new AuthenticationMethod()]);
13+
$authentication = new BasicAuth('username', 'password');
14+
$authenticationPlugin = new AuthenticationPlugin($authentication);
15+
16+
$pluginClient = new PluginClient(
17+
HttpClientDiscovery::find(),
18+
[$authenticationPlugin]
19+
);
1420
```
1521

16-
Check the [authentication component documentation](/components/authentication)
22+
Check the [authentication component documentation](http://docs.httplug.io/en/latest/components/authentication/)
1723
for the list of available authentication methods.

docs/httplug/plugins/plugin-implementation.md

Lines changed: 0 additions & 79 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Third Party Plugins

0 commit comments

Comments
 (0)
0