From 5bb5547a9a7275ae61eff8e1a02cfa694e89feab Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Wed, 18 May 2016 12:48:33 +0200 Subject: [PATCH] ws/style/uses/docs --- .gitignore | 6 +- examples/generator.php | 2 +- lib/API.php | 320 +++++++++++++++-------------- lib/API/Call.php | 24 ++- lib/API/ContentType.php | 81 +++++++- lib/API/Invoker.php | 31 +-- lib/API/Iterator.php | 12 +- lib/API/Links.php | 40 ++-- lib/Exception/RequestException.php | 34 ++- 9 files changed, 336 insertions(+), 214 deletions(-) diff --git a/.gitignore b/.gitignore index c2ecf0a..8b13789 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -# -/nbproject/ -/tmp/ -composer.lock -vendor/ + diff --git a/examples/generator.php b/examples/generator.php index d488e97..9d962d4 100755 --- a/examples/generator.php +++ b/examples/generator.php @@ -14,7 +14,7 @@ $api = new API([ "Authorization" => "token ".getenv("GITHUB_TOKEN") ], null, $cli, $log); -$api(function() use($api) { +$api(function($api) { $count = 0; $events = yield $api->repos->m6w6->{"ext-http"}->issues->events(); while ($events) { diff --git a/lib/API.php b/lib/API.php index f0b7620..ba384c5 100644 --- a/lib/API.php +++ b/lib/API.php @@ -2,90 +2,104 @@ namespace seekat; -use seekat\API\ContentType; -use seekat\Exception\RequestException; +use Countable; +use Generator; +use http\{ + Client, + Client\Request, + Client\Response, + Header, + Message\Body, + QueryString, + Url +}; +use InvalidArgumentException; +use IteratorAggregate; +use Psr\Log\{ + LoggerInterface, + NullLogger +}; +use seekat\{ + API\Call, + API\ContentType, + API\Invoker, + API\Iterator, + API\Links, + Exception\RequestException +}; +use React\Promise\{ + ExtendedPromiseInterface, + function reject, + function resolve +}; +use Throwable; +use UnexpectedValueException; -use http\Url; -use http\Header; -use http\Client; -use http\Client\Request; -use http\Client\Response; -use http\Message\Body; -use http\QueryString; - -use Psr\Log\LoggerInterface; -use Psr\Log\NullLogger; - -use React\Promise\ExtendedPromiseInterface; -use function React\Promise\resolve; -use function React\Promise\reject; -use function React\Promise\map; - -class API implements \IteratorAggregate, \Countable { +class API implements IteratorAggregate, Countable { /** * The current API endpoint URL - * @var \http\Url + * @var Url */ private $__url; - + /** * Logger - * @var \Psr\Log\LoggerInterface + * @var LoggerInterface */ private $__log; - + /** * The HTTP client - * @var \http\Client + * @var Client */ private $__client; - + /** * Default headers to send out to the API endpoint * @var array */ private $__headers; - + /** * Current endpoint data's Content-Type - * @var \http\Header + * @var Header */ private $__type; - + /** * Current endpoint's data * @var array|object */ private $__data; - + /** * Current endpoints links - * @var seekat\API\Links + * @var Links */ private $__links; - + /** * Create a new API endpoint root * - * @var array $headers Standard request headers, defaults to ["Accept" => "application/vnd.github.v3+json"] - * @var \http\Url The API's endpoint, defaults to https://api.github.com - * @var \http\Client $client The HTTP client to use for executing requests - * @var \Psr\Log\LoggerInterface $log A logger + * @param array $headers Standard request headers, defaults to ["Accept" => "application/vnd.github.v3+json"] + * @param Url $url The API's endpoint, defaults to https://api.github.com + * @param Client $client The HTTP client to use for executing requests + * @param LoggerInterface $log A logger */ function __construct(array $headers = null, Url $url = null, Client $client = null, LoggerInterface $log = null) { $this->__log = $log ?? new NullLogger; $this->__url = $url ?? new Url("https://api.github.com"); $this->__client = $client ?? new Client; $this->__headers = (array) $headers + [ - "Accept" => "application/vnd.github.v3+json" + "Accept" => "application/vnd.github.v3+json" ]; } - + /** * Ascend one level deep into the API endpoint * - * @var string|int $seg The "path" element to ascend into - * @return \seekat\API Endpoint clone referring to {$parent}/{$seg} + * @param string|int $seg The "path" element to ascend into + * @return API Endpoint clone referring to {$parent}/{$seg} */ function __get($seg) : API { if (substr($seg, -4) === "_url") { @@ -97,34 +111,34 @@ class API implements \IteratorAggregate, \Countable { $that->__url->path .= "/".urlencode($seg); $this->exists($seg, $that->__data); } - + $this->__log->debug(__FUNCTION__."($seg)", [ "url" => [ (string) $this->__url, (string) $that->__url ], ]); - + return $that; } - + /** * Call handler that actually queues a data fetch and returns a promise * - * @var string $method The API's "path" element to ascend into - * @var array $args Array of arguments forwarded to \seekat\API::get() - * @return \React\Promise\ExtendedPromiseInterface + * @param string $method The API's "path" element to ascend into + * @param array $args Array of arguments forwarded to \seekat\API::get() + * @return ExtendedPromiseInterface */ function __call(string $method, array $args) : ExtendedPromiseInterface { /* We cannot implement an explicit then() method, * because the Promise implementation might think * we're actually implementing Thenable, - * which might cause an infite loop. + * which might cause an infinite loop. */ if ($method === "then") { return $this->get()->then(...$args); } - + /* * very short-hand version: * ->users->m6w6->gists->get()->then(...) @@ -134,39 +148,39 @@ class API implements \IteratorAggregate, \Countable { if (is_callable(current($args))) { return $this->$method->get()->then(current($args)); } - + /* standard access */ if ($this->exists($method)) { return $this->$method->get(...$args); } - + /* fetch resource, unless already localized, and try for {$method}_url */ - return $this->$method->get(...$args)->otherwise(function($error) use($method, $args) { + return $this->$method->get(...$args)->otherwise(function(Throwable $error) use($method, $args) { if ($this->exists($method."_url", $url)) { - + $this->__log->info(__FUNCTION__."($method): ". $error->getMessage(), [ "url" => (string) $this->__url ]); - + $url = new Url(uri_template($url, (array) current($args))); return $this->withUrl($url)->get(...$args); } - + $this->__log->error(__FUNCTION__."($method): ". $error->getMessage(), [ "url" => (string) $this->__url ]); - + throw $error; }); } - + /** * Clone handler ensuring the underlying url will be cloned, too */ function __clone() { $this->__url = clone $this->__url; } - + /** * The string handler for the endpoint's data * @@ -176,36 +190,37 @@ class API implements \IteratorAggregate, \Countable { if (is_scalar($this->__data)) { return (string) $this->__data; } - + /* FIXME */ return json_encode($this->__data); } - + /** * Import handler for the endpoint's underlying data * - * \seekat\Deferred will call this when the request will have finished. + * \seekat\Call will call this when the request will have finished. * - * @var \http\Client\Response $response - * @return \seekat\API self + * @param Response $response + * @return API self + * @throws UnexpectedValueException + * @throws RequestException + * @throws \Exception */ function import(Response $response) : API { - //addcslashes($response, "\0..\40\42\47\134\140\177..\377") - $this->__log->info(__FUNCTION__.": ". $response->getInfo(), [ "url" => (string) $this->__url ]); - + if ($response->getResponseCode() >= 400) { $e = new RequestException($response); - + $this->__log->critical(__FUNCTION__.": ".$e->getMessage(), [ "url" => (string) $this->__url, ]); - + throw $e; } - + if (!($type = $response->getHeader("Content-Type", Header::class))) { $e = new RequestException($response); $this->__log->error( @@ -214,65 +229,66 @@ class API implements \IteratorAggregate, \Countable { ]); throw $e; } - + try { $this->__type = new ContentType($type); $this->__data = $this->__type->parseBody($response->getBody()); - + if (($link = $response->getHeader("Link", Header::class))) { - $this->__links = new API\Links($link); + $this->__links = new Links($link); } } catch (\Exception $e) { $this->__log->error(__FUNCTION__.": ".$e->getMessage(), [ "url" => (string) $this->__url ]); - + throw $e; } - + return $this; } - + /** * Export the endpoint's underlying data * + * @param * @return mixed */ function export(&$type = null) { $type = clone $this->__type; return $this->__data; } - + /** * Create a copy of the endpoint with specific data * - * @var mixed $data - * @return \seekat\API clone + * @param mixed $data + * @return API clone */ function withData($data) : API { $that = clone $this; $that->__data = $data; return $that; } - + /** * Create a copy of the endpoint with a specific Url, but with data reset * - * @var \http\Url $url - * @return \seekat\API clone + * @param Url $url + * @return API clone */ function withUrl(Url $url) : API { $that = $this->withData(null); $that->__url = $url; return $that; } - + /** * Create a copy of the endpoint with a specific header added/replaced * - * @var string $name - * @var mixed $value - * @return \seekat\API clone + * @param string $name + * @param mixed $value + * @return API clone */ function withHeader(string $name, $value) : API { $that = clone $this; @@ -283,16 +299,15 @@ class API implements \IteratorAggregate, \Countable { } return $that; } - + /** * Create a copy of the endpoint with a customized accept header * - * Changes the returned endpoint's accept header to - * "application/vnd.github.v3.{$type}" + * Changes the returned endpoint's accept header to "application/vnd.github.v3.{$type}" * - * @var string $type The expected return data type, e.g. "raw", "html", etc. - * @var bool $keepdata Whether to keep already fetched data - * @return \seekat\API clone + * @param string $type The expected return data type, e.g. "raw", "html", etc. + * @param bool $keepdata Whether to keep already fetched data + * @return API clone */ function as(string $type, bool $keepdata = true) : API { switch(substr($type, 0, 1)) { @@ -311,16 +326,16 @@ class API implements \IteratorAggregate, \Countable { } return $that; } - + /** * Create an iterator over the endpoint's underlying data * - * @return \seekat\API\Iterator + * @return Iterator */ - function getIterator() : API\Iterator { - return new API\Iterator($this); + function getIterator() : Iterator { + return new Iterator($this); } - + /** * Count the underlying data's entries * @@ -329,78 +344,78 @@ class API implements \IteratorAggregate, \Countable { function count() : int { return count($this->__data); } - + /** * Perform a GET request against the endpoint's underlying URL * - * @var mixed $args The HTTP query string parameters - * @var array $headers The request's additional HTTP headers - * @return \React\Promise\ExtendedPromiseInterface + * @param mixed $args The HTTP query string parameters + * @param array $headers The request's additional HTTP headers + * @return ExtendedPromiseInterface */ function get($args = null, array $headers = null) : ExtendedPromiseInterface { return $this->__xfer("GET", $args, null, $headers); } - + /** * Perform a DELETE request against the endpoint's underlying URL * - * @var mixed $args The HTTP query string parameters - * @var array $headers The request's additional HTTP headers - * @return \React\Promise\ExtendedPromiseInterface + * @param mixed $args The HTTP query string parameters + * @param array $headers The request's additional HTTP headers + * @return ExtendedPromiseInterface */ function delete($args = null, array $headers = null) : ExtendedPromiseInterface { return $this->__xfer("DELETE", $args, null, $headers); } - + /** * Perform a POST request against the endpoint's underlying URL * - * @var mixed $body The HTTP message's body - * @var mixed $args The HTTP query string parameters - * @var array $headers The request's additional HTTP headers - * @return \React\Promise\ExtendedPromiseInterface + * @param mixed $body The HTTP message's body + * @param mixed $args The HTTP query string parameters + * @param array $headers The request's additional HTTP headers + * @return ExtendedPromiseInterface */ function post($body = null, $args = null, array $headers = null) : ExtendedPromiseInterface { return $this->__xfer("POST", $args, $body, $headers); } - + /** * Perform a PUT request against the endpoint's underlying URL * - * @var mixed $body The HTTP message's body - * @var mixed $args The HTTP query string parameters - * @var array $headers The request's additional HTTP headers - * @return \React\Promise\ExtendedPromiseInterface + * @param mixed $body The HTTP message's body + * @param mixed $args The HTTP query string parameters + * @param array $headers The request's additional HTTP headers + * @return ExtendedPromiseInterface */ function put($body = null, $args = null, array $headers = null) : ExtendedPromiseInterface { return $this->__xfer("PUT", $args, $body, $headers); } - + /** * Perform a PATCH request against the endpoint's underlying URL * - * @var mixed $body The HTTP message's body - * @var mixed $args The HTTP query string parameters - * @var array $headers The request's additional HTTP headers - * @return \React\Promise\ExtendedPromiseInterface + * @param mixed $body The HTTP message's body + * @param mixed $args The HTTP query string parameters + * @param array $headers The request's additional HTTP headers + * @return ExtendedPromiseInterface */ function patch($body = null, $args = null, array $headers = null) : ExtendedPromiseInterface { return $this->__xfer("PATCH", $args, $body, $headers); } - + /** * Accessor to any hypermedia links * - * @return null|\seekat\API\Links + * @return null|Links */ function links() { return $this->__links; } - + /** * Perform a GET request against the link's "first" relation * - * @return \React\Promise\ExtendedPromiseInterface + * @return ExtendedPromiseInterface */ function first() : ExtendedPromiseInterface { if ($this->links() && ($first = $this->links()->getFirst())) { @@ -408,11 +423,11 @@ class API implements \IteratorAggregate, \Countable { } return reject($this->links()); } - + /** * Perform a GET request against the link's "prev" relation * - * @return \React\Promise\ExtendedPromiseInterface + * @return ExtendedPromiseInterface */ function prev() : ExtendedPromiseInterface { if ($this->links() && ($prev = $this->links()->getPrev())) { @@ -420,11 +435,11 @@ class API implements \IteratorAggregate, \Countable { } return reject($this->links()); } - + /** * Perform a GET request against the link's "next" relation * - * @return \React\Promise\ExtendedPromiseInterface + * @return ExtendedPromiseInterface */ function next() : ExtendedPromiseInterface { if ($this->links() && ($next = $this->links()->getNext())) { @@ -432,11 +447,11 @@ class API implements \IteratorAggregate, \Countable { } return reject($this->links()); } - + /** * Perform a GET request against the link's "last" relation * - * @return \React\Promise\ExtendedPromiseInterface + * @return ExtendedPromiseInterface */ function last() : ExtendedPromiseInterface { if ($this->links() && ($last = $this->links()->getLast())) { @@ -444,11 +459,11 @@ class API implements \IteratorAggregate, \Countable { } return reject($this->links()); } - + /** * Perform all queued HTTP transfers * - * @return \seekat\API self + * @return API self */ function send() : API { $this->__log->debug(__FUNCTION__.": start loop"); @@ -458,19 +473,20 @@ class API implements \IteratorAggregate, \Countable { $this->__log->debug(__FUNCTION__.": end loop"); return $this; } - + /** * Run the send loop through a generator * - * @param callable|\Generator $cbg A \Generator or a factory of a \Generator yielding promises - * @return \React\Promise\ExtendedPromiseInterface The promise of the generator's return value + * @param callable|Generator $cbg A \Generator or a factory of a \Generator yielding promises + * @return ExtendedPromiseInterface The promise of the generator's return value + * @throws InvalidArgumentException */ function __invoke($cbg) : ExtendedPromiseInterface { $this->__log->debug(__FUNCTION__); - - $invoker = new API\Invoker($this->__client); - if ($cbg instanceof \Generator) { + $invoker = new Invoker($this->__client); + + if ($cbg instanceof Generator) { return $invoker->iterate($cbg)->promise(); } @@ -480,7 +496,7 @@ class API implements \IteratorAggregate, \Countable { })->promise(); } - throw \InvalidArgumentException( + throw InvalidArgumentException( "Expected callable or Generator, got ".( is_object($cbg) ? "instance of ".get_class($cbg) @@ -488,12 +504,12 @@ class API implements \IteratorAggregate, \Countable { ) ); } - + /** * Check for a specific key in the endpoint's underlying data * - * @var string $seg - * @var mixed &$val + * @param string $seg + * @param mixed &$val * @return bool */ function exists($seg, &$val = null) : bool { @@ -507,7 +523,7 @@ class API implements \IteratorAggregate, \Countable { $val = null; $exists = false; } - + $this->__log->debug(__FUNCTION__."($seg) in ".( is_object($this->__data) ? get_class($this->__data) @@ -520,18 +536,18 @@ class API implements \IteratorAggregate, \Countable { "url" => (string) $this->__url, "val" => $val, ]); - + return $exists; } - + /** * Queue the actual HTTP transfer through \seekat\API\Deferred and return the promise * - * @var string $method The HTTP request method - * @var mixed $args The HTTP query string parameters - * @var mixed $body Thee HTTP message's body - * @var array $headers The request's additional HTTP headers - * @return \React\Promise\ExtendedPromiseInterface + * @param string $method The HTTP request method + * @param mixed $args The HTTP query string parameters + * @param mixed $body Thee HTTP message's body + * @param array $headers The request's additional HTTP headers + * @return ExtendedPromiseInterface */ private function __xfer(string $method, $args = null, $body = null, array $headers = null) : ExtendedPromiseInterface { if (isset($this->__data)) { @@ -541,24 +557,24 @@ class API implements \IteratorAggregate, \Countable { "body" => $body, "headers" => $headers, ]); - + return resolve($this); } - + $url = $this->__url->mod(["query" => new QueryString($args)]); $request = new Request($method, $url, ((array) $headers) + $this->__headers, $body = is_array($body) ? json_encode($body) : ( is_resource($body) ? new Body($body) : ( is_scalar($body) ? (new Body)->append($body) : $body))); - + $this->__log->info(__FUNCTION__."($method) -> request", [ "url" => (string) $this->__url, "args" => $this->__url->query, "body" => $body, "headers" => $headers, ]); - - return (new API\Call($this, $this->__client, $request))->promise(); + + return (new Call($this, $this->__client, $request))->promise(); } } diff --git a/lib/API/Call.php b/lib/API/Call.php index 56e15dc..787f98d 100644 --- a/lib/API/Call.php +++ b/lib/API/Call.php @@ -3,9 +3,11 @@ namespace seekat\API; use Exception; -use http\Client; -use http\Client\Request; -use http\Client\Response; +use http\ { + Client, + Client\Request, + Client\Response +}; use React\Promise\Deferred; use seekat\API; use SplObserver; @@ -15,7 +17,7 @@ class Call extends Deferred implements SplObserver { /** * The endpoint - * @var \seekat\API + * @var API */ private $api; @@ -40,9 +42,9 @@ class Call extends Deferred implements SplObserver /** * Create a deferred promise for the response of $request * - * @var \seekat\API $api The endpoint of the request - * @var Client $client The HTTP client to send the request - * @var Request The request to execute + * @param API $api The endpoint of the request + * @param Client $client The HTTP client to send the request + * @param Request $request The request to execute */ function __construct(API $api, Client $client, Request $request) { $this->api = $api; @@ -52,7 +54,7 @@ class Call extends Deferred implements SplObserver parent::__construct(function($resolve, $reject) { return $this->cancel($resolve, $reject); }); - + $client->attach($this); $client->enqueue($request); /* start off */ @@ -64,9 +66,9 @@ class Call extends Deferred implements SplObserver * * Import the response's data on success and resolve the promise. * - * @var SplSubject $client The observed HTTP client - * @var Request The request which generated the update - * @var object $progress The progress information + * @param SplSubject $client The observed HTTP client + * @param Request $request The request which generated the update + * @param object $progress The progress information */ function update(SplSubject $client, Request $request = null, $progress = null) { if ($request !== $this->request) { diff --git a/lib/API/ContentType.php b/lib/API/ContentType.php index b2eb5fb..929dc0c 100644 --- a/lib/API/ContentType.php +++ b/lib/API/ContentType.php @@ -2,13 +2,26 @@ namespace seekat\API; -use http\Header; -use http\Message\Body; +use http\{ + Header, + Message\Body +}; + +use InvalidArgumentException; +use UnexpectedValueException; class ContentType { + /** + * API version + * @var int + */ static private $version = 3; - + + /** + * Content type handler map + * @var array + */ static private $types = [ "json" => "self::fromJson", "base64" => "self::fromBase64", @@ -20,20 +33,43 @@ class ContentType "text/plain"=> "self::fromData", ]; + /** + * Content type abbreviation + * @var string + */ private $type; + /** + * Register a content type handler + * @param string $type The content type (abbreviation) + * @param callable $handler The handler as function(Body $body):mixed; + */ static function register(string $type, callable $handler) { self::$types[$type] = $handler; } + /** + * Check whether a handler is registered for a particular content type + * @param string $type The (abbreviated) content type + * @return bool + */ static function registered(string $type) : bool { return isset(self::$types[$type]); } + /** + * Unregister a content type handler + * @param string $type + */ static function unregister(string $type) { unset(self::$types[$type]); } + /** + * Get/set the API version to use + * @param int $v if not null, update the API version + * @return int the previously set version + */ static function version(int $v = null) : int { $api = self::$version; if (isset($v)) { @@ -41,29 +77,48 @@ class ContentType } return $api; } - + + /** + * @param Body $json + * @return mixed + * @throws UnexpectedValueException + */ private static function fromJson(Body $json) { $decoded = json_decode($json); if (!isset($decoded) && json_last_error()) { - throw new \UnexpectedValueException("Could not decode JSON: ". + throw new UnexpectedValueException("Could not decode JSON: ". json_last_error_msg()); } return $decoded; } + /** + * @param Body $base64 + * @return string + * @throws UnexpectedValueException + */ private static function fromBase64(Body $base64) : string { if (false === ($decoded = base64_decode($base64))) { - throw new \UnexpectedValueExcpeption("Could not decode BASE64"); + throw new UnexpectedValueException("Could not decode BASE64"); } + return $decoded; } + /** + * @param Body $data + * @return string + */ private static function fromData(Body $data) : string { return (string) $data; } + /** + * @param Header $contentType + * @throws InvalidArgumentException + */ function __construct(Header $contentType) { if (strcasecmp($contentType->name, "Content-Type")) { - throw new \InvalidArgumentException( + throw new InvalidArgumentException( "Expected Content-Type header, got ". $contentType->name); } $vapi = static::version(); @@ -75,15 +130,25 @@ class ContentType )/x", "\\1", current(array_keys($contentType->getParams()->params))); } + /** + * Get the (abbreviated) content type name + * @return string + */ function getType() : string { return $this->type; } + /** + * Parse a response message's body according to its content type + * @param Body $data + * @return mixed + * @throws UnexpectedValueException + */ function parseBody(Body $data) { $type = $this->getType(); if (static::registered($type)) { return call_user_func(self::$types[$type], $data, $type); } - throw new \UnexpectedValueException("Unhandled content type '$type'"); + throw new UnexpectedValueException("Unhandled content type '$type'"); } } diff --git a/lib/API/Invoker.php b/lib/API/Invoker.php index e52eedc..c36315e 100644 --- a/lib/API/Invoker.php +++ b/lib/API/Invoker.php @@ -4,11 +4,12 @@ namespace seekat\API; use Generator; use http\Client; -use React\Promise\Deferred; -use React\Promise\PromiseInterface; -use React\Promise\ExtendedPromiseInterface; - -use function React\Promise\all; +use React\Promise\{ + Deferred, + ExtendedPromiseInterface, + PromiseInterface, + function all +}; class Invoker extends Deferred { @@ -32,7 +33,7 @@ class Invoker extends Deferred /** * Create a new generator invoker - * @param \http\Client $client + * @param Client $client */ function __construct(Client $client) { $this->client = $client; @@ -45,8 +46,8 @@ class Invoker extends Deferred /** * Invoke $generator to create a \Generator which yields promises * - * @param callable $generator as function() : \Generator, creating a generator yielding promises - * @return \seekat\API\Invoker + * @param callable $generator as function():\Generator, creating a generator yielding promises + * @return Invoker */ function invoke(callable $generator) : Invoker { $this->iterate($generator()); @@ -56,8 +57,8 @@ class Invoker extends Deferred /** * Iterate over $gen, a \Generator yielding promises * - * @param \Generator $gen - * @return \seekat\API\Invoker + * @param Generator $gen + * @return Invoker */ function iterate(Generator $gen) : Invoker { $this->cancelled = false; @@ -77,8 +78,8 @@ class Invoker extends Deferred /** * Get the generator's result - * - * @return \React\Promise\ExtendedPromiseInterface + * + * @return ExtendedPromiseInterface */ function result() : ExtendedPromiseInterface { return $this->promise(); @@ -86,9 +87,9 @@ class Invoker extends Deferred /** * Promise handler - * - * @param array|\React\Promise\PromiseInterface $promise - * @param \Generator $gen + * + * @param array|PromiseInterface $promise + * @param Generator $gen */ private function give($promise, Generator $gen) { if ($promise instanceof PromiseInterface) { diff --git a/lib/API/Iterator.php b/lib/API/Iterator.php index f0179b4..6c4a9cc 100644 --- a/lib/API/Iterator.php +++ b/lib/API/Iterator.php @@ -2,13 +2,15 @@ namespace seekat\API; +use http\Url; +use Iterator as BaseIterator; use seekat\API; -class Iterator implements \Iterator +class Iterator implements BaseIterator { /** * The endpoint - * @var \seekat\API + * @var API */ private $api; @@ -33,7 +35,7 @@ class Iterator implements \Iterator /** * Create a new iterator over $data returning \seekat\API instances * - * @var \seekat\API $api The endpoint + * @var API $api The endpoint * @var array|object $data */ function __construct(API $api) { @@ -53,7 +55,7 @@ class Iterator implements \Iterator /** * Get the current data entry * - * @return \seekat\API + * @return API */ function current() { return $this->cur; @@ -63,7 +65,7 @@ class Iterator implements \Iterator if (list($key, $cur) = each($this->data)) { $this->key = $key; if ($this->api->$key->exists("url", $url)) { - $url = new \http\Url($url); + $url = new Url($url); $this->cur = $this->api->withUrl($url)->withData($cur); } else { $this->cur = $this->api->$key->withData($cur); diff --git a/lib/API/Links.php b/lib/API/Links.php index 5546e65..325f4b7 100644 --- a/lib/API/Links.php +++ b/lib/API/Links.php @@ -2,19 +2,23 @@ namespace seekat\API; -use http\Header; -use http\Params; -use http\QueryString; -use http\Url; +use http\ { + Header, + Params, + QueryString, + Url +}; +use Serializable; +use UnexpectedValueException; -class Links implements \Serializable +class Links implements Serializable { /** * Parsed "Link" relations - * @var \http\Params + * @var Params */ private $params; - + /** * Parsed "Link" relations * @var array @@ -24,23 +28,33 @@ class Links implements \Serializable /** * Parse the hypermedia link header * - * @var string $header_value The value of the "Link" header + * @param Header $links The Link header + * @throws UnexpectedValueException */ function __construct(Header $links) { if (strcasecmp($links->name, "Link")) { - throw new \UnexpectedValueException("Expected 'Link' header, got: '{$links->name}'"); + throw new UnexpectedValueException("Expected 'Link' header, got: '{$links->name}'"); } $this->unserialize($links->value); } + /** + * @return string + */ function __toString() : string { return $this->serialize(); } + /** + * @return string + */ function serialize() { return (string) $this->params; } + /** + * @param string $links + */ function unserialize($links) { $this->params = new Params($links, ",", ";", "=", Params::PARSE_RFC5988 | Params::PARSE_ESCAPED); @@ -65,7 +79,7 @@ class Links implements \Serializable * * Returns the link's "last" relation if it exists and "next" is not set. * - * @return \http\Url + * @return Url */ function getNext() { if (isset($this->relations["next"])) { @@ -82,7 +96,7 @@ class Links implements \Serializable * * Returns the link's "first" relation if it exists and "prev" is not set. * - * @return \http\Url + * @return Url */ function getPrev() { if (isset($this->relations["prev"])) { @@ -97,7 +111,7 @@ class Links implements \Serializable /** * Get the URL of the link's "last" relation * - * @return \http\Url + * @return Url */ function getLast() { if (isset($this->relations["last"])) { @@ -109,7 +123,7 @@ class Links implements \Serializable /** * Get the URL of the link's "first" relation * - * @return \http\Url + * @return Url */ function getFirst() { if (isset($this->relations["first"])) { diff --git a/lib/Exception/RequestException.php b/lib/Exception/RequestException.php index 877664e..a9da6d6 100644 --- a/lib/Exception/RequestException.php +++ b/lib/Exception/RequestException.php @@ -2,16 +2,30 @@ namespace seekat\Exception; +use Exception as BaseException; +use http\ { + Client\Response, + Header +}; use seekat\Exception; -use http\Header; -use http\Client\Response; - -class RequestException extends \Exception implements Exception +class RequestException extends BaseException implements Exception { + /** + * JSON errors + * @var array + */ private $errors = []; + + /** + * The response of the request which caused the exception + * @var Response + */ private $response; + /** + * @param Response $response + */ function __construct(Response $response) { $this->response = $response; @@ -36,10 +50,19 @@ class RequestException extends \Exception implements Exception parent::__construct($message, $response->getResponseCode(), null); } + /** + * Get JSON errors + * @return array + */ function getErrors() : array { return $this->errors; } + /** + * Combine any errors into a single string + * @staticvar array $reasons + * @return string + */ function getErrorsAsString() { static $reasons = [ "missing" => "The resource %1\$s does not exist\n", @@ -63,6 +86,9 @@ class RequestException extends \Exception implements Exception return $errors; } + /** + * @return string + */ function __toString() : string { return parent::__toString() . "\n". $this->getErrorsAsString(); } -- 2.30.2