From c3017c025f20cbc70887d956f9c749747113eb7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Mon, 30 Jul 2018 12:39:44 +0200 Subject: [PATCH] Improve API documentation --- README.md | 47 ++++++----- src/Browser.php | 132 ++++++++++++++++++++++++------ src/Io/Sender.php | 5 +- src/Io/Transaction.php | 17 ++-- src/Message/MessageFactory.php | 14 ++-- src/Message/ResponseException.php | 10 ++- 6 files changed, 163 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index b028931..b6cb0a5 100644 --- a/README.md +++ b/README.md @@ -112,10 +112,10 @@ The `Browser` offers several methods that resemble the HTTP protocol methods: ```php $browser->get($url, array $headers = array()); $browser->head($url, array $headers = array()); -$browser->post($url, array $headers = array(), $content = ''); -$browser->delete($url, array $headers = array(), $content = ''); -$browser->put($url, array $headers = array(), $content = ''); -$browser->patch($url, array $headers = array(), $content = ''); +$browser->post($url, array $headers = array(), string|ReadableStreamInterface $content = ''); +$browser->delete($url, array $headers = array(), string|ReadableStreamInterface $content = ''); +$browser->put($url, array $headers = array(), string|ReadableStreamInterface $content = ''); +$browser->patch($url, array $headers = array(), string|ReadableStreamInterface $content = ''); ``` All the above methods default to sending requests as HTTP/1.0. @@ -328,12 +328,17 @@ $browser->post($url, array(), $stream)->then(function (ResponseInterface $respon #### submit() -The `submit($url, array $fields, $headers = array(), $method = 'POST')` method can be used to submit an array of field values similar to submitting a form (`application/x-www-form-urlencoded`). +The `submit($url, array $fields, $headers = array(), $method = 'POST'): PromiseInterface` method can be used to +submit an array of field values similar to submitting a form (`application/x-www-form-urlencoded`). + +```php +$browser->submit($url, array('user' => 'test', 'password' => 'secret')); +``` #### send() -The `send(RequestInterface $request)` method can be used to send an arbitrary -instance implementing the [`RequestInterface`](#requestinterface) (PSR-7). +The `send(RequestInterface $request): PromiseInterface` method can be used to +send an arbitrary instance implementing the [`RequestInterface`](#requestinterface) (PSR-7). All the above [predefined methods](#methods) default to sending requests as HTTP/1.0. If you need a custom HTTP protocol method or version, then you may want to use this @@ -348,7 +353,8 @@ $browser->send($request)->then(…); #### withOptions() -The `withOptions(array $options)` method can be used to change the [options](#options) to use: +The `withOptions(array $options): Browser` method can be used to +change the [options](#options) to use: ```php $newBrowser = $browser->withOptions($options); @@ -361,8 +367,8 @@ See [options](#options) for more details. #### withBase() -The `withBase($baseUri)` method can be used to change the base URI used to -resolve relative URIs to. +The `withBase($baseUri): Browser` method can be used to +change the base URI used to resolve relative URIs to. ```php $newBrowser = $browser->withBase('http://api.example.com/v3'); @@ -371,12 +377,12 @@ $newBrowser = $browser->withBase('http://api.example.com/v3'); Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method actually returns a *new* [`Browser`](#browser) instance with the given base URI applied. -Any requests to relative URIs will then be processed by first prepending the -base URI. -Please note that this merely prepends the base URI and does *not* resolve any -relative path references (like `../` etc.). -This is mostly useful for API calls where all endpoints (URIs) are located -under a common base URI scheme. +Any requests to relative URIs will then be processed by first prepending +the (absolute) base URI. +Please note that this merely prepends the base URI and does *not* resolve +any relative path references (like `../` etc.). +This is mostly useful for (RESTful) API calls where all endpoints (URIs) +are located under a common base URI scheme. ```php // will request http://api.example.com/v3/example @@ -385,7 +391,8 @@ $newBrowser->get('/example')->then(…); #### withoutBase() -The `withoutBase()` method can be used to remove the base URI. +The `withoutBase(): Browser` method can be used to +remove the base URI. ```php $newBrowser = $browser->withoutBase(); @@ -431,9 +438,11 @@ a request promise if the remote server returns a non-success status code (anything but 2xx or 3xx). You can control this behavior via the ["obeySuccessCode" option](#options). -The `getCode()` method can be used to return the HTTP response status code. +The `getCode(): int` method can be used to +return the HTTP response status code. -The `getResponse()` method can be used to access its underlying [`ResponseInteface`](#responseinterface) object. +The `getResponse(): ResponseInterface` method can be used to +access its underlying [`ResponseInterface`](#responseinterface) object. ## Advanced diff --git a/src/Browser.php b/src/Browser.php index 62af472..0ab3237 100644 --- a/src/Browser.php +++ b/src/Browser.php @@ -11,6 +11,7 @@ use React\EventLoop\LoopInterface; use React\Promise\PromiseInterface; use React\Socket\ConnectorInterface; +use React\Stream\ReadableStreamInterface; class Browser { @@ -20,7 +21,34 @@ class Browser private $options = array(); /** - * Instantiate the Browser + * The `Browser` is responsible for sending HTTP requests to your HTTP server + * and keeps track of pending incoming HTTP responses. + * It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage). + * + * ```php + * $loop = React\EventLoop\Factory::create(); + * + * $browser = new Browser($loop); + * ``` + * + * If you need custom connector settings (DNS resolution, TLS parameters, timeouts, + * proxy servers etc.), you can explicitly pass a custom instance of the + * [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface): + * + * ```php + * $connector = new \React\Socket\Connector($loop, array( + * 'dns' => '127.0.0.1', + * 'tcp' => array( + * 'bindto' => '192.168.10.1:0' + * ), + * 'tls' => array( + * 'verify_peer' => false, + * 'verify_peer_name' => false + * ) + * )); + * + * $browser = new Browser($loop, $connector); + * ``` * * @param LoopInterface $loop * @param ConnectorInterface|null $connector [optional] Connector to use. @@ -34,7 +62,7 @@ public function __construct(LoopInterface $loop, ConnectorInterface $connector = /** * @param string|UriInterface $url URI for the request. - * @param array $headers + * @param array $headers * @return PromiseInterface */ public function get($url, array $headers = array()) @@ -43,9 +71,9 @@ public function get($url, array $headers = array()) } /** - * @param string|UriInterface $url URI for the request. - * @param array $headers - * @param string $content + * @param string|UriInterface $url URI for the request. + * @param array $headers + * @param string|ReadableStreamInterface $content * @return PromiseInterface */ public function post($url, array $headers = array(), $content = '') @@ -54,8 +82,8 @@ public function post($url, array $headers = array(), $content = '') } /** - * @param string|UriInterface $url URI for the request. - * @param array $headers + * @param string|UriInterface $url URI for the request. + * @param array $headers * @return PromiseInterface */ public function head($url, array $headers = array()) @@ -64,9 +92,9 @@ public function head($url, array $headers = array()) } /** - * @param string|UriInterface $url URI for the request. - * @param array $headers - * @param string $content + * @param string|UriInterface $url URI for the request. + * @param array $headers + * @param string|ReadableStreamInterface $content * @return PromiseInterface */ public function patch($url, array $headers = array(), $content = '') @@ -75,9 +103,9 @@ public function patch($url, array $headers = array(), $content = '') } /** - * @param string|UriInterface $url URI for the request. - * @param array $headers - * @param string $content + * @param string|UriInterface $url URI for the request. + * @param array $headers + * @param string|ReadableStreamInterface $content * @return PromiseInterface */ public function put($url, array $headers = array(), $content = '') @@ -86,9 +114,9 @@ public function put($url, array $headers = array(), $content = '') } /** - * @param string|UriInterface $url URI for the request. - * @param array $headers - * @param string $content + * @param string|UriInterface $url URI for the request. + * @param array $headers + * @param string|ReadableStreamInterface $content * @return PromiseInterface */ public function delete($url, array $headers = array(), $content = '') @@ -97,10 +125,16 @@ public function delete($url, array $headers = array(), $content = '') } /** - * @param string|UriInterface $url URI for the request. - * @param array $fields - * @param array $headers - * @param string $method + * Submits an array of field values similar to submitting a form (`application/x-www-form-urlencoded`). + * + * ```php + * $browser->submit($url, array('user' => 'test', 'password' => 'secret')); + * ``` + * + * @param string|UriInterface $url URI for the request. + * @param array $fields + * @param array $headers + * @param string $method * @return PromiseInterface */ public function submit($url, array $fields, $headers = array(), $method = 'POST') @@ -112,6 +146,19 @@ public function submit($url, array $fields, $headers = array(), $method = 'POST' } /** + * Sends an arbitrary instance implementing the [`RequestInterface`](#requestinterface) (PSR-7). + * + * All the above [predefined methods](#methods) default to sending requests as HTTP/1.0. + * If you need a custom HTTP protocol method or version, then you may want to use this + * method: + * + * ```php + * $request = new Request('OPTIONS', $url); + * $request = $request->withProtocolVersion('1.1'); + * + * $browser->send($request)->then(…); + * ``` + * * @param RequestInterface $request * @return PromiseInterface */ @@ -128,11 +175,26 @@ public function send(RequestInterface $request) } /** - * Creates a new Browser instance with the given absolute base URI + * Changes the base URI used to resolve relative URIs to. + * + * ```php + * $newBrowser = $browser->withBase('http://api.example.com/v3'); + * ``` * - * This is mostly useful for using (RESTful) HTTP APIs. - * Any relative URI passed to any of the request methods will simply be - * appended behind the given `$baseUri`. + * Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method + * actually returns a *new* [`Browser`](#browser) instance with the given base URI applied. + * + * Any requests to relative URIs will then be processed by first prepending + * the (absolute) base URI. + * Please note that this merely prepends the base URI and does *not* resolve + * any relative path references (like `../` etc.). + * This is mostly useful for (RESTful) API calls where all endpoints (URIs) + * are located under a common base URI scheme. + * + * ```php + * // will request http://api.example.com/v3/example + * $newBrowser->get('/example')->then(…); + * ``` * * By definition of this library, a given base URI MUST always absolute and * can not contain any placeholders. @@ -155,7 +217,16 @@ public function withBase($baseUri) } /** - * Creates a new Browser instance *without* a base URL + * Removes the base URI. + * + * ```php + * $newBrowser = $browser->withoutBase(); + * ``` + * + * Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method + * actually returns a *new* [`Browser`](#browser) instance without any base URI applied. + * + * See also [`withBase()`](#withbase). * * @return self * @see self::withBase() @@ -169,6 +240,17 @@ public function withoutBase() } /** + * Changes the [options](#options) to use: + * + * ```php + * $newBrowser = $browser->withOptions($options); + * ``` + * + * Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withOptions()` method + * actually returns a *new* [`Browser`](#browser) instance with the [options](#options) applied. + * + * See [options](#options) for more details. + * * @param array $options * @return self */ diff --git a/src/Io/Sender.php b/src/Io/Sender.php index 65ca223..03d43cc 100644 --- a/src/Io/Sender.php +++ b/src/Io/Sender.php @@ -10,6 +10,7 @@ use React\HttpClient\Request as RequestStream; use React\HttpClient\Response as ResponseStream; use React\Promise; +use React\Promise\PromiseInterface; use React\Promise\Deferred; use React\Socket\Connector; use React\Socket\ConnectorInterface; @@ -88,8 +89,8 @@ public function send(RequestInterface $request, MessageFactory $messageFactory) $body = $request->getBody(); // automatically assign a Content-Length header if the body size is known - if ($body->getSize() !== null && $body->getSize() !== 0 && $request->hasHeader('Content-Length') !== null) { - $request = $request->withHeader('Content-Length', $body->getSize()); + if ($body->getSize() !== null && $body->getSize() !== 0 && !$request->hasHeader('Content-Length')) { + $request = $request->withHeader('Content-Length', (string)$body->getSize()); } if ($body instanceof ReadableStreamInterface && $body->isReadable() && !$request->hasHeader('Content-Length')) { diff --git a/src/Io/Transaction.php b/src/Io/Transaction.php index 90d4835..2cacde8 100644 --- a/src/Io/Transaction.php +++ b/src/Io/Transaction.php @@ -2,14 +2,13 @@ namespace Clue\React\Buzz\Io; -use Clue\React\Buzz\Browser; -use Clue\React\Buzz\Io\Sender; use Clue\React\Buzz\Message\ResponseException; use Clue\React\Buzz\Message\MessageFactory; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; use React\Promise; +use React\Promise\PromiseInterface; use React\Promise\Stream; use React\Stream\ReadableStreamInterface; use Exception; @@ -19,8 +18,8 @@ */ class Transaction { - private $browser; private $request; + private $sender; private $messageFactory; private $numRequests = 0; @@ -54,7 +53,7 @@ public function send() return $this->next($this->request); } - protected function next(RequestInterface $request) + private function next(RequestInterface $request) { $this->progress('request', array($request)); @@ -108,7 +107,7 @@ function ($e) use ($stream) { * @param ResponseInterface $response * @param RequestInterface $request * @throws ResponseException - * @return ResponseInterface + * @return ResponseInterface|PromiseInterface */ public function onResponse(ResponseInterface $response, RequestInterface $request) { @@ -127,6 +126,12 @@ public function onResponse(ResponseInterface $response, RequestInterface $reques return $response; } + /** + * @param ResponseInterface $response + * @param RequestInterface $request + * @return PromiseInterface + * @throws \RuntimeException + */ private function onResponseRedirect(ResponseInterface $response, RequestInterface $request) { // resolve location relative to last request URI @@ -145,7 +150,7 @@ private function onResponseRedirect(ResponseInterface $response, RequestInterfac /** * @param RequestInterface $request * @param UriInterface $location - * @return \Clue\React\Buzz\Message\RequestInterface + * @return RequestInterface */ private function makeRedirectRequest(RequestInterface $request, UriInterface $location) { diff --git a/src/Message/MessageFactory.php b/src/Message/MessageFactory.php index cbe5c92..307bed8 100644 --- a/src/Message/MessageFactory.php +++ b/src/Message/MessageFactory.php @@ -2,10 +2,11 @@ namespace Clue\React\Buzz\Message; +use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; use RingCentral\Psr7\Request; -use RingCentral\Psr7\Uri; use RingCentral\Psr7\Response; -use Psr\Http\Message\UriInterface; +use RingCentral\Psr7\Uri; use RingCentral; use React\Stream\ReadableStreamInterface; @@ -21,7 +22,7 @@ class MessageFactory * @param string|UriInterface $uri * @param array $headers * @param string|ReadableStreamInterface $content - * @return RequestInterface + * @return Request */ public function request($method, $uri, $headers = array(), $content = '') { @@ -36,7 +37,7 @@ public function request($method, $uri, $headers = array(), $content = '') * @param string $reason * @param array $headers * @param ReadableStreamInterface|string $body - * @return ResponseInterface + * @return Response * @uses self::body() */ public function response($version, $status, $reason, $headers = array(), $body = '') @@ -60,9 +61,9 @@ public function body($body) } /** - * Creates a new instance of UriInterface for the given URI string or instance + * Creates a new instance of UriInterface for the given URI string * - * @param UriInterface|string $uri + * @param string $uri * @return UriInterface */ public function uri($uri) @@ -100,7 +101,6 @@ public function uriRelative(UriInterface $base, $uri) * @param UriInterface $base * @return UriInterface * @throws \UnexpectedValueException - * @see Browser::resolve() */ public function expandBase(UriInterface $uri, UriInterface $base) { diff --git a/src/Message/ResponseException.php b/src/Message/ResponseException.php index 8d66559..2b5ec44 100644 --- a/src/Message/ResponseException.php +++ b/src/Message/ResponseException.php @@ -6,9 +6,13 @@ use Psr\Http\Message\ResponseInterface; /** - * A ResponseException will be returned for valid Response objects that use an HTTP error code + * The `ResponseException` is an `Exception` sub-class that will be used to reject + * a request promise if the remote server returns a non-success status code + * (anything but 2xx or 3xx). + * You can control this behavior via the ["obeySuccessCode" option](#options). * - * You can access the original ResponseInterface object via its getter. + * The `getCode(): int` method can be used to + * return the HTTP response status code. */ class ResponseException extends RuntimeException { @@ -28,7 +32,7 @@ public function __construct(ResponseInterface $response, $message = null, $code } /** - * get Response message object + * Access its underlying [`ResponseInterface`](#responseinterface) object. * * @return ResponseInterface */