-
Notifications
You must be signed in to change notification settings - Fork 56
Add documentation for async client #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ composer require "php-http/discovery" | |
|
||
## HTTP Client Discovery | ||
|
||
This type of discovery finds installed HTTP Clients. | ||
This type of discovery finds a HTTPClient implementation. | ||
|
||
``` php | ||
use Http\Client\HttpClient; | ||
|
@@ -42,10 +42,34 @@ class MyClass | |
} | ||
``` | ||
|
||
## HTTP Async Client Discovery | ||
|
||
This type of discovery finds a HttpAsyncClient implementation. | ||
|
||
``` php | ||
use Http\Client\HttpAsyncClient; | ||
use Http\Discovery\HttpAsyncClientDiscovery; | ||
|
||
class MyClass | ||
{ | ||
/** | ||
* @var HttpAsyncClient | ||
*/ | ||
protected $httpAsyncClient; | ||
|
||
/** | ||
* @param HttpAsyncClient|null $httpAsyncClient to do HTTP requests. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the description required? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think the good description would be "If not set, autodiscovery will be used to find an asynchronous client" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm...you are right, we should explain why this parameter is optional. Is it explained where the HttpClient is documented? |
||
*/ | ||
public function __construct(HttpAsyncClient $httpAsyncClient = null) | ||
{ | ||
$this->httpAsyncClient = $httpAsyncClient ?: HttpAsyncClientDiscovery::find(); | ||
} | ||
} | ||
``` | ||
|
||
## PSR-7 Message Factory Discovery | ||
|
||
This type of discovery finds installed [PSR-7](http://www.php-fig.org/psr/psr-7/) Message implementations and their [factories](message-factory.md). | ||
This type of discovery finds a [PSR-7](http://www.php-fig.org/psr/psr-7/) Message implementation and their [factories](message-factory.md). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it is "a" implementation, then it can't be "their factories". Singulars and plurals should be synchronized. Same for URI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. |
||
|
||
``` php | ||
use Http\Message\MessageFactory; | ||
|
@@ -71,7 +95,7 @@ class MyClass | |
|
||
## PSR-7 URI Factory Discovery | ||
|
||
This type of discovery finds installed [PSR-7](http://www.php-fig.org/psr/psr-7/) URI implementations and their factories. | ||
This type of discovery finds a [PSR-7](http://www.php-fig.org/psr/psr-7/) URI implementation and their factories. | ||
|
||
``` php | ||
use Http\Message\UriFactory; | ||
|
@@ -117,7 +141,7 @@ Classes registered manually are put on top of the list. | |
|
||
### Writing your own discovery | ||
|
||
Each discovery service is based on the `ClassDiscovery` and has to specify a `cache` field and a `class` field to specify classes for the corresponding service. The fields need to be redeclared in each discovery class. If `ClassDiscovery` would declare them, they would be shared between the discovery classes which would make no sense. | ||
Each discovery service is based on the `ClassDiscovery` and has to specify a `cache` field and a `class` field to specify classes for the corresponding service. The fields need to be redeclared in each discovery class. If `ClassDiscovery` would declare them, they would be shared between the discovery classes which would make no sense. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rather say properties instead fo fields. I would also add an explanation why this is the case: because Discoveries are static classes. |
||
|
||
Here is an example discovery: | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,22 +5,35 @@ Httplug is an abstraction for HTTP clients. There are two main use cases: | |
1. Usage in a project | ||
2. Usage in a reusable package | ||
|
||
In both cases, the client provides a `sendRequest` method to send a PSR-7 `RequestInterface` and returns a PSR-7 `ResponseInterface` or throws an exception that implements `Http\Client\Exception`. | ||
In both cases, the client provides a `sendRequest` method to send a PSR-7 `RequestInterface` and returns a PSR-7 `ResponseInterface` | ||
or throws an exception that implements `Http\Client\Exception`. | ||
|
||
See the [tutorial](tutorial.md) for a concrete example. | ||
There is also the HttpAsyncClient, available in [php-http/httplug-async](https://packagist.org/packages/php-http/httplug-async), which provides the `sendAsyncRequest` method to send a request asynchronously and returns a `Http\Client\Promise`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add quotes to make HttpAsyncClient rendered as do we use the same namespace as the normal client? or a different one? maybe we should have Http\Client\Experimental\ , to make the migration easier once PSR is out. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't like to mix functional and organisation into the same namespace :/ IMO Promise is not there before many months (maybe years), if we look at how long PSR7 has been in discussion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The class name should indeed be wrapped in backticks, and probably the FQCN would be good. I don't think mixing the namespace is a problem: if it is not necessary, the package won't be installed anyway. Also, we can merge the two repositores as soon as we have a consensus about the Promise (either wait for PSR or roll our own): no BC breaks that way (except the Promise of course). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my reasoning is that if you have code that needs to be compatible with both systems, reusing the same namespace for two different things is not convenient. we could also go with something less "negative" than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure I get the point. What is "both systems" and what makes them incompatible/inconvenient? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the one based on our own Promise interface and the future one based on a PSR. |
||
It can be used later to retrieve a PSR-7 `ResponseInterface` or an exception that implements `Http\Client\Exception`. | ||
|
||
Contract for the HttpAsyncClient is still experimental and will be merged into Httplug repository once we considered it stable. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @sagikazarmark can markdown do a box with a warning in it? i would say that is to be considered experimental until PSR-? (and link to the PSR) is released. we will use the PSR promise once it is released and deprecate the separate client. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hm, in terms of migration, we might actually better use a different namespace for the async client, to make migration manageble. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or we use a new major version ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually you can write HTML in markdown documents. http://www.mkdocs.org/user-guide/styling-your-docs/ You mean move the client to the main contract and use the PSR promise, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I add a warning box, but don't know if current theme support it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think he was talking when we will move async client to the main contract, yes. With the current namespace, if we use a new interface it will assure a BC break as we will use the same namespace. But i don't think Promise PSR will be here soon, so we can always throws a 2.0 version at this time, WDYT ? |
||
|
||
See the [tutorial](tutorial.md) for a concrete example. | ||
|
||
## Httplug implementations | ||
|
||
Httplug implementations typically are either HTTP clients of their own, or they are adapters wrapping existing clients like Guzzle 6. In the latter case, they will depend on the required client implementation, so you only need to require the adapter and not the actual client. | ||
Httplug implementations typically are either HTTP clients of their own, or they are adapters wrapping existing clients like Guzzle 6. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "or adapters..." I think you can omit the "they are" part in case of an "either .... or ...." structure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i had it like this to make it more clear that implementations can be one or the other things. reading over it quickly could otherwise lead to confusion as we talk about clients and then stuff that wraps clients (the later are a different kind of clients) |
||
In the latter case, they will depend on the required client implementation, so you only need to require the adapter and not the actual client. | ||
|
||
See [packagist](https://packagist.org/providers/php-http/client-implementation) for the full list of implementations. | ||
There is two kind of implementation: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two kinds of |
||
|
||
* [php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation), the standard implementation, send requests with a synchronous workflow | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use dashes (-) for lists, at least it should be consistent and I think dashes are more common. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would be more explicit: ", the synchronous implementation that waits for the response / error before returning from the |
||
* [php-http/client-async-implementation](https://packagist.org/providers/php-http/client-async-implementation), send requests with an asynchronous workflow by returning promises | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't async-client be better? Not sure... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would prefer async-client-implementation There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. , the asynchronous implementation that immediately returns a |
||
|
||
See [https://packagist.org/providers/php-http/client-implementation](https://packagist.org/providers/php-http/client-implementation) or [https://packagist.org/providers/php-http/client-async-implementation](https://packagist.org/providers/php-http/client-async-implementation) for | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need the links here again. Something like: check the links above for the full list of implementations. |
||
the full list of implementations. | ||
|
||
Note: Until Httplug 1.0 becomes stable, we will focus on the Guzzle6 adapter. | ||
|
||
## Usage in a project | ||
|
||
When writing an application, you need to require a concrete [client implementation](https://packagist.org/providers/php-http/client-implementation). | ||
When writing an application, you need to require a concrete [client implementation](https://packagist.org/providers/php-http/client-implementation) or | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think simply concrete implementation is enough, we explained the virtual package architecture well enough above. |
||
a concrete [async client implementation](https://packagist.org/providers/php-http/client-async-implementation). | ||
|
||
See [virtual package](virtual-package.md) for more information on the topic of working with Httplug implementations. | ||
|
||
|
@@ -29,17 +42,20 @@ See [virtual package](virtual-package.md) for more information on the topic of w | |
|
||
In many cases, packages are designed to be reused from the very beginning. For example, API clients are usually used in other packages/applications, not on their own. | ||
|
||
In these cases, they should **not rely on a concrete implementation** (like Guzzle 6), but only require any implementation of Httplug. Httplug uses the concept of virtual packages. Instead of depending on only the interfaces, which would be missing an implementation, or depending on one concrete implementation, you should depend on the virtual package `php-http/client-implementation`. There is no package with that name, but all clients and adapters implementing Httplug declare that they provide this virtual package. | ||
In these cases, they should **not rely on a concrete implementation** (like Guzzle 6), but only require any implementation of Httplug. | ||
Httplug uses the concept of virtual packages. Instead of depending on only the interfaces, which would be missing an implementation, | ||
or depending on one concrete implementation, you should depend on the virtual package `php-http/client-implementation` or `php-http/async-client-implementation`. | ||
There is no package with that name, but all clients and adapters implementing Httplug declare that they provide one of this virtual package or both. | ||
|
||
You need to edit the `composer.json` of your package to add the virtual package. For development (installing the package standalone, running tests), add a concrete implementation in the `require-dev` section to make the project installable: | ||
|
||
``` json | ||
... | ||
"require": { | ||
"php-http/client-implementation": "^1.0" | ||
}, | ||
"require-dev": { | ||
"php-http/guzzle6-adapter": "^1.0" | ||
"require": { | ||
"php-http/client-implementation": "^1.0" | ||
}, | ||
"require-dev": { | ||
"php-http/guzzle6-adapter": "^1.0" | ||
}, | ||
... | ||
``` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,13 +32,118 @@ require('vendor/autoload.php'); | |
TODO: create client instance with discovery and do some requests | ||
``` | ||
|
||
## Handling errors | ||
## Using an asynchronous client | ||
|
||
TODO: explain how to handle exceptions, distinction between network exception and HttpException. | ||
When using an asynchronous client, it will use a PSR-7 `RequestInterface` and returns a `Http\Client\Promise` : | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Asynchronus client accepts a PSR-7 RequestInterface? |
||
|
||
```php | ||
$httpAsyncClient = new HttpAsyncClientImplementation(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we have discovery for this? should we, if we don't have currently? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do. I think I merged it today. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. then the tutorial should use it i think, instead of this line which can't be executed in reality (you would need to know your real client/adapter class name and use that instead) |
||
$promise = $httpAsyncClient->sendAsyncRequest($request); | ||
``` | ||
|
||
## Doing parallel requests | ||
This promise allows you to : | ||
|
||
* Add callbacks for when the response is available or an errors happens by using the then method: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the part that comes after this bullet is very long. lets maybe do an overview list and then subsections with the code examples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Didn't catch it. You are right. |
||
|
||
```php | ||
$promise->then(function (ResponseInterface $response) { | ||
// onFulfilled callback | ||
echo 'The response is available'; | ||
|
||
return $response; | ||
}, function (Exception $e) { | ||
// onRejected callback | ||
echo 'An error happens'; | ||
|
||
throw $e; | ||
}); | ||
``` | ||
|
||
This method will return another promise so you can manipulate the response and/or exception and | ||
still provide a way to interact with this object for your users: | ||
|
||
```php | ||
$promise->then(function (ResponseInterface $response) { | ||
// onFulfilled callback | ||
echo 'The response is available'; | ||
|
||
return $response; | ||
}, function (Exception $e) { | ||
// onRejected callback | ||
echo 'An error happens'; | ||
|
||
throw $e; | ||
})->then(function (ResponseInterface $response) { | ||
echo 'Response stil available'; | ||
|
||
return $response; | ||
}, function (Exception $e) { | ||
throw $e | ||
}); | ||
``` | ||
|
||
In order to achieve the chain callback, if you read previous examples carefully, callbacks provided to the `then` method __must__ | ||
return a PSR-7 `ResponseInterface` or throw a `Http\Client\Exception`. For both of the callbacks, if it returns a PSR-7 `ResponseInterface` | ||
it will call the `onFulfilled` callback for the next element in the chain, if it throws a `Http\Client\Exception` it will call the `onRejected` | ||
callback. | ||
|
||
i.e. you can inverse the behavior of a call: | ||
|
||
```php | ||
$promise->then(function (ResponseInterface $response) use($request) { | ||
// onFulfilled callback | ||
echo 'The response is available, but it\'s not ok...'; | ||
|
||
throw new HttpException('My error message', $request, $response); | ||
}, function (Exception $e) { | ||
// onRejected callback | ||
echo 'An error happens, but it\'s ok...'; | ||
|
||
return $exception->getResponse(); | ||
}); | ||
``` | ||
|
||
* Get the state of the promise with `$promise->getState()` will return of one `Promise::PENDING`, `Promise::FULFILLED` or `Promise::REJECTED` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dashes here as well |
||
* Get the response of the promise if it's in `FULFILLED` state with `$promise->getResponse()` call | ||
* Get the error of the promise if it's in `REJECTED` state with `$promise->getRequest()` call | ||
* wait for the callback to be fulfilled or rejected with the `$promise->wait()` call. The `wait` will return nothing, it will simply call one the callback | ||
pass to the `then` method depending on the result of the call. It the promise has already been fulfilled or rejected it will do nothing. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think this is too much detail for the tutorial. the tutorial should show a typical basic use case and can link to the reference documentation for the full list of options. otherwise the tutorial will get very large, and contain information that is not found elsewhere - which would be unexpected. |
||
|
||
Here is a full example of a classic usage when using the `sendAsyncRequest` method: | ||
|
||
```php | ||
$httpAsyncClient = new HttpAsyncClientImplementation(); | ||
|
||
$promise = $httpAsyncClient->sendAsyncRequest($request); | ||
$promise->then(function (ResponseInterface $response) { | ||
echo 'The response is available'; | ||
|
||
return $response; | ||
}, function (Exception $e) { | ||
echo 'An error happens'; | ||
|
||
throw $e; | ||
}); | ||
|
||
// Do some stuff not depending on the response, calling another request, etc .. | ||
... | ||
|
||
// We need now the response for our final treatment | ||
$promise->wait(); | ||
|
||
if (Promise::FULFILLED === $promise->getState()) { | ||
$response = $promise->getResponse(); | ||
} else { | ||
throw new \Exception('Response not available'); | ||
} | ||
|
||
// Do your stuff with the response | ||
... | ||
``` | ||
|
||
## Handling errors | ||
|
||
TODO explain sendRequests and how to work with BatchResult and BatchException | ||
TODO: explain how to handle exceptions, distinction between network exception and HttpException. | ||
|
||
|
||
## Writing a reusable package | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure about writing this as HTTPClient - maybe with a space, so "HTTP client"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same for Http Async Client (to be consistent) ?