Skip to content

Commit 0b8604f

Browse files
committed
Merge pull request #87 from php-http/plugins-redirect-retry-library-custom
add documentation for retry and redirect plugins, split building own, document library usage
2 parents ca27f93 + 2778145 commit 0b8604f

13 files changed

+246
-108
lines changed

clients/curl-client.rst

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,24 @@ To install the cURL client, run:
1212
1313
$ composer require php-http/curl-client
1414
15-
Usage
16-
-----
17-
18-
The cURL client needs a :ref:`message <message-factory>` and a :ref:`stream <message-factory>`
19-
factory in order to to work::
15+
.. include:: includes/install-message-factory.inc
2016

21-
use Http\Client\Curl\Client;
17+
.. include:: includes/install-discovery.inc
2218

23-
$client = new Client($messageFactory, $streamFactory);
19+
Usage
20+
-----
2421

25-
Using `php-http/message <https://packagist.org/packages/php-http/message>`_::
22+
The cURL client needs a :ref:`message factory <message-factory>` and a
23+
:ref:`stream factory <stream-factory>` in order to to work. You can either specify the factory
24+
explicitly::
2625

2726
use Http\Client\Curl\Client;
2827
use Http\Message\MessageFactory\DiactorosMessageFactory;
2928
use Http\Message\StreamFactory\DiactorosStreamFactory;
3029

3130
$client = new Client(new DiactorosMessageFactory(), new DiactorosStreamFactory());
3231

33-
Using `php-http/discovery <https://packagist.org/packages/php-http/discovery>`_::
32+
Or you can use :doc:`../discovery`::
3433

3534
use Http\Client\Curl\Client;
3635
use Http\Discovery\MessageFactoryDiscovery;
@@ -53,7 +52,8 @@ You can use `cURL options <http://php.net/curl_setopt>`_ to configure Client::
5352
];
5453
$client = new Client(MessageFactoryDiscovery::find(), StreamFactoryDiscovery::find(), $options);
5554

56-
These options cannot be used (will be overwritten by Client):
55+
The following options can not be changed in the set up. Most of them are to be provided with the
56+
request instead:
5757

5858
* CURLOPT_CUSTOMREQUEST
5959
* CURLOPT_FOLLOWLOCATION

clients/includes/further-reading.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
* Use :ref:`plugins <plugins>` to customize the way HTTP requests are sent and
1+
* Use :doc:`plugins </plugins/introduction>` to customize the way HTTP requests are sent and
22
responses processed by following redirects, adding Authentication or Cookie
33
headers and more.
44
* Learn how you can decouple your code from any PSR-7 implementation by using a

components/client-common.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
.. _client-common:
2-
31
Client Common
42
=============
53

discovery.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ Discovery
22
=========
33

44
The discovery service allows to find and use installed resources.
5+
56
Under the hood it uses `Puli`_ for the discovery logic. All of our packages provide Puli resources.
67
Discovery is simply a convenience wrapper to statically access clients and factories for when
78
Dependency Injection is not an option. Discovery is useful in libraries that want to offer
89
zero-configuration services relying on the virtual packages. If you have Dependency Injection available,
910
using Puli directly is more elegant (see for example the Symfony HttplugBundle).
1011

12+
Consumers of libraries using discovery still need to make sure they install one of the implementations.
13+
Discovery can only find installed code, not fetch code from other sources.
14+
1115
Currently available discovery services:
1216

1317
- HTTP Client Discovery
@@ -32,7 +36,7 @@ In both cases you have to install the discovery package itself:
3236

3337
.. code-block:: bash
3438
35-
$ composer require php-http/discovery
39+
$ composer require php-http/discovery
3640
3741
As mentioned above, discovery relies on Puli. In order to use discovery, you need to also set up Puli.
3842
The easiest way is installing the composer-plugin which automatically configures all the composer packages to act as

httplug/library-developers.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ When you construct HTTP message objects in your library, you should not depend
6969
on a concrete PSR-7 message implementation. Instead, use the
7070
:ref:`PHP-HTTP message factory <message-factory>`.
7171

72+
Plugins
73+
-------
74+
75+
If your library relies on specific plugins, the recommended way is to provide a factory method for
76+
your users, so they can create the correct client from a base HttpClient. See
77+
:ref:`plugin-client.libraries` for a concrete example.
78+
7279
User documentation
7380
------------------
7481

httplug/migrating.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The monolithic Ivory package has been separated into several smaller, more speci
1212
Instead of ``Ivory\HttpAdapter\PsrHttpAdapter``, use ``Http\Client\HttpClient``.
1313
The HttpClient simply has a method to send requests.
1414

15-
If you used the ``Ivory\HttpAdapter\HttpAdapter``, have a look at :ref:`client-common`
15+
If you used the ``Ivory\HttpAdapter\HttpAdapter``, have a look at :doc:`../components/client-common`
1616
and use the ``Http\Client\Utils\HttpMethodsClient`` which wraps any HttpClient
1717
and provides the convenience methods to send requests without creating
1818
RequestInterface instances.
@@ -26,7 +26,7 @@ Instead of the ``$options`` argument, configure the client appropriately during
2626
If you need different settings, create different instances of the client.
2727
You can use :doc:`/plugins/index` to further tune your client.
2828

29-
If you used the ``request`` method, have a look at :ref:`client-common` and
29+
If you used the ``request`` method, have a look at :doc:`../components/client-common` and
3030
use the ``Http\Client\Utils\HttpMethodsClient`` which wraps any HttpClient and
3131
provides the convenience methods to send requests without creating
3232
RequestInterface instances.

message/message-factory.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ The ``MessageFactory`` aims to provide an easy way to construct messages.
2020
Usage
2121
-----
2222

23+
.. _stream-factory:
24+
2325
This package provides interfaces for PSR-7 factories including:
2426

2527
- ``MessageFactory``

plugins/build-your-own.rst

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
Building Custom Plugins
2+
=======================
3+
4+
When writing your own Plugin, you need to be aware that the Plugin Client is async first.
5+
This means that every plugin must be written with Promises. More about this later.
6+
7+
Each plugin must implement the ``Http\Client\Plugin\Plugin`` interface.
8+
9+
This interface defines the ``handleRequest`` method that allows to modify behavior of the call::
10+
11+
/**
12+
* Handles the request and returns 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+
The ``$request`` comes from an upstream plugin or Plugin Client itself.
23+
You can replace it and pass a new version downstream if you need.
24+
25+
.. note::
26+
27+
Be aware that the request is immutable.
28+
29+
The ``$next`` callable is the next plugin in the execution chain. When you need to call it, you must pass the ``$request``
30+
as the first argument of this callable.
31+
32+
For example a simple plugin setting a header would look like this::
33+
34+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
35+
{
36+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
37+
38+
return $next($newRequest);
39+
}
40+
41+
The ``$first`` callable is the first plugin in the chain. It allows you to completely reboot the execution chain, or send
42+
another request if needed, while still going through all the defined plugins.
43+
Like in case of the ``$next`` callable, you must pass the ``$request`` as the first argument::
44+
45+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
46+
{
47+
if ($someCondition) {
48+
$newRequest = new Request();
49+
$promise = $first($newRequest);
50+
51+
// Use the promise to do some jobs ...
52+
}
53+
54+
return $next($request);
55+
}
56+
57+
.. warning::
58+
59+
In this example the condition is not superfluous:
60+
you need to have some way to not call the ``$first`` callable each time
61+
or you will end up in an infinite execution loop.
62+
63+
The ``$next`` and ``$first`` callables will return a :doc:`/components/promise`.
64+
You can manipulate the ``ResponseInterface`` or the ``Exception`` by using the
65+
``then`` method of the promise::
66+
67+
public function handleRequest(RequestInterface $request, callable $next, callable $first)
68+
{
69+
$newRequest = $request->withHeader('MyHeader', 'MyValue');
70+
71+
return $next($request)->then(function (ResponseInterface $response) {
72+
return $response->withHeader('MyResponseHeader', 'value');
73+
}, function (Exception $exception) {
74+
echo $exception->getMessage();
75+
76+
throw $exception;
77+
});
78+
}
79+
80+
.. warning::
81+
82+
Contract for the ``Http\Promise\Promise`` is temporary until a
83+
PSR_ is released. Once it is out, we will use this PSR in HTTPlug and
84+
deprecate the old contract.
85+
86+
To better understand the whole process check existing implementations in the
87+
`plugin repository`_.
88+
89+
Contributing Your Plugins to PHP-HTTP
90+
-------------------------------------
91+
92+
We are open to contributions. If the plugin is of general interest and is not too complex, the best
93+
is to do a Pull Request to ``php-http/plugins``. Please see the :doc:`contribution guide <../development/contributing>`.
94+
We don't promise that every plugin gets merged into the core. We need to keep the core as small as
95+
possible with only the most widely used plugins to keep it maintainable.
96+
97+
The alternative is providing your plugins in your own repository. Please let us know when you do,
98+
we would like to add a list of existing third party plugins to the list of plugins.
99+
100+
.. _PSR: https://groups.google.com/forum/?fromgroups#!topic/php-fig/wzQWpLvNSjs
101+
.. _plugin repository: https://github.com/php-http/plugins

plugins/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ request or you can even start a completely new request. This gives you full cont
99
.. toctree::
1010

1111
introduction
12+
build-your-own
1213
authentication
1314
cache
1415
content-length

plugins/introduction.rst

Lines changed: 44 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ Install the plugin client in your project with Composer_:
1010
1111
$ composer require php-http/plugins
1212
13-
14-
1513
How it works
1614
------------
1715

@@ -69,105 +67,61 @@ The recommended way to order plugins is the following:
6967

7068
.. note::
7169

72-
There can be exceptions to these rules. For example,
73-
for security reasons you might not want to log the authentication information (like `Authorization` header)
74-
and choose to put the Authentication Plugin after the Logger Plugin.
75-
76-
Implement your own
77-
------------------
70+
There can be exceptions to these rules. For example, for security reasons you might not want
71+
to log the authentication information (like ``Authorization`` header) and choose to put the
72+
:doc:`Authentication Plugin <authentication>` after the doc:`Logger Plugin <logger>`.
7873

79-
When writing your own Plugin, you need to be aware that the Plugin Client is async first.
80-
This means that every plugin must be written with Promises. More about this later.
8174

82-
Each plugin must implement the ``Http\Client\Plugin\Plugin`` interface.
75+
Configuration Options
76+
---------------------
8377

84-
This interface defines the ``handleRequest`` method that allows to modify behavior of the call::
78+
The PluginClient accepts an array of configuration options that can tweak its behavior.
8579

86-
/**
87-
* Handles the request and returns the response coming from the next callable.
88-
*
89-
* @param RequestInterface $request Request to use.
90-
* @param callable $next Callback to call to have the request, it muse have the request as it first argument.
91-
* @param callable $first First element in the plugin chain, used to to restart a request from the beginning.
92-
*
93-
* @return Promise
94-
*/
95-
public function handleRequest(RequestInterface $request, callable $next, callable $first);
80+
.. _plugin-client.max-restarts:
9681

97-
The ``$request`` comes from an upstream plugin or Plugin Client itself.
98-
You can replace it and pass a new version downstream if you need.
82+
``max_restarts``: int (default 10)
83+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9984

100-
.. note::
101-
102-
Be aware that the request is immutable.
85+
To prevent issues with faulty plugins or endless redirects, the ``PluginClient`` injects a security
86+
check to the start of the plugin chain. If the same request is restarted more than specified by
87+
that value, execution is aborted and an error is raised.
10388

104-
The ``$next`` callable is the next plugin in the execution chain. When you need to call it, you must pass the ``$request``
105-
as the first argument of this callable.
89+
.. _plugin-client.libraries:
10690

107-
For example a simple plugin setting a header would look like this::
91+
Libraries that Require Plugins
92+
------------------------------
10893

109-
public function handleRequest(RequestInterface $request, callable $next, callable $first)
110-
{
111-
$newRequest = $request->withHeader('MyHeader', 'MyValue');
94+
When :doc:`writing a library based on HTTPlug <../httplug/library-developers>`, you might require
95+
specific plugins to be active. The recommended way for doing this is to provide a factory method
96+
for the ``PluginClient`` that your library users should use. This allows them to inject their own
97+
plugins or configure a different client. For example::
11298

113-
return $next($newRequest);
114-
}
99+
$myApiClient = new My\Api\Client('https://api.example.org', My\Api\HttpClientFactory::create('john', 's3cr3t'));
115100

116-
The ``$first`` callable is the first plugin in the chain. It allows you to completely reboot the execution chain, or send
117-
another request if needed, while still going through all the defined plugins.
118-
Like in case of the ``$next`` callable, you must pass the ``$request`` as the first argument::
101+
use Http\Client\HttpClient;
102+
use Http\Discovery\HttpClientDiscovery;
119103

120-
public function handleRequest(RequestInterface $request, callable $next, callable $first)
104+
class HttpClientFactory
121105
{
122-
if ($someCondition) {
123-
$newRequest = new Request();
124-
$promise = $first($newRequest);
125-
126-
// Use the promise to do some jobs ...
106+
/**
107+
* Build the HTTP client to talk with the API.
108+
*
109+
* @param string $user Username
110+
* @param string $pass Password
111+
* @param HttpClient $client Base HTTP client
112+
*
113+
* @return HttpClient
114+
*/
115+
public static function create($user, $pass, HttpClient $client = null)
116+
{
117+
if (!$client) {
118+
$client = HttpClientDiscovery::find();
119+
}
120+
return new PluginClient($client, [
121+
new Http\Client\Plugin\ErrorPlugin(),
122+
new AuthenticationPlugin(
123+
// This API has it own authentication algorithm
124+
new ApiAuthentication(Client::AUTH_OAUTH_TOKEN, $user, $pass)
125+
),
126+
]);
127127
}
128-
129-
return $next($request);
130-
}
131-
132-
.. warning::
133-
134-
In this example the condition is not superfluous:
135-
you need to have some way to not call the ``$first`` callable each time
136-
or you will end up in an infinite execution loop.
137-
138-
The ``$next`` and ``$first`` callables will return a :doc:`/components/promise`.
139-
You can manipulate the ``ResponseInterface`` or the ``Exception`` by using the
140-
``then`` method of the promise::
141-
142-
public function handleRequest(RequestInterface $request, callable $next, callable $first)
143-
{
144-
$newRequest = $request->withHeader('MyHeader', 'MyValue');
145-
146-
return $next($request)->then(function (ResponseInterface $response) {
147-
return $response->withHeader('MyResponseHeader', 'value');
148-
}, function (Exception $exception) {
149-
echo $exception->getMessage();
150-
151-
throw $exception;
152-
});
153-
}
154-
155-
.. warning::
156-
157-
Contract for the ``Http\Promise\Promise`` is temporary until a
158-
PSR_ is released. Once it is out, we will use this PSR in HTTPlug and
159-
deprecate the old contract.
160-
161-
To better understand the whole process check existing implementations in the
162-
`plugin repository`_.
163-
164-
Contribution
165-
------------
166-
167-
We are always open to contributions. Either in form of Pull Requests to the core package or self-made plugin packages.
168-
We encourage everyone to prefer sending Pull Requests, however we don't promise that every plugin gets
169-
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
170-
the core as small as possible with the most widely used plugin implementations.
171-
172-
.. _PSR: https://groups.google.com/forum/?fromgroups#!topic/php-fig/wzQWpLvNSjs
173-
.. _plugin repository: https://github.com/php-http/plugins

0 commit comments

Comments
 (0)