From 6668e2f2bced26cb5b945c1efd43b5cfcd118691 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 20 Aug 2014 21:00:04 +0200 Subject: [PATCH 01/34] Started new pov on the base api code --- composer.json | 9 +++-- .../BaseApi/{Api.php => ApiFactory.php} | 3 +- .../Http/ClientAdapter/AbstractAdapter.php | 24 +++++++++++++ .../Http/ClientAdapter/GuzzleAdapter.php | 19 ++++++++++ lib/Nekland/BaseApi/Http/ClientInterface.php | 8 +++++ .../{HttpClient.php => HttpClientFactory.php} | 36 +++---------------- 6 files changed, 62 insertions(+), 37 deletions(-) rename lib/Nekland/BaseApi/{Api.php => ApiFactory.php} (94%) create mode 100644 lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php create mode 100644 lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php rename lib/Nekland/BaseApi/Http/{HttpClient.php => HttpClientFactory.php} (80%) diff --git a/composer.json b/composer.json index 49cb89a..271c09e 100644 --- a/composer.json +++ b/composer.json @@ -19,8 +19,11 @@ "autoload": { "psr-0": { "Nekland\\": "lib/" } }, + "require": { + "php": ">=5.4" + }, "require-dev": { - "php": ">=5.4", - "phpunit/phpunit": ">=3.7" + "phpspec/phpspec": "dev-master", + "guzzlehttp/guzzle": "~4.2" } -} \ No newline at end of file +} diff --git a/lib/Nekland/BaseApi/Api.php b/lib/Nekland/BaseApi/ApiFactory.php similarity index 94% rename from lib/Nekland/BaseApi/Api.php rename to lib/Nekland/BaseApi/ApiFactory.php index 2ecc674..e0cf227 100644 --- a/lib/Nekland/BaseApi/Api.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -11,10 +11,9 @@ namespace Nekland\BaseApi; - use Nekland\BaseApi\Http\ClientInterface; -abstract class Api implements ApiInterface +abstract class ApiFactory implements ApiInterface { /** * @var ClientInterface diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php new file mode 100644 index 0000000..858390d --- /dev/null +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php @@ -0,0 +1,24 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ +namespace Nekland\BaseApi\Http\ClientAdapter; + + +use Nekland\BaseApi\Http\ClientInterface; + +abstract class AbstractAdapter implements ClientInterface +{ + private $options; + + public function __construct(array $options = []) + { + $this->options = $options; + } +} diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php new file mode 100644 index 0000000..365c850 --- /dev/null +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -0,0 +1,19 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Http\ClientAdapter; + + +use Nekland\BaseApi\Http\ClientInterface; + +class GuzzleAdapter extends AbstractAdapter +{ +} diff --git a/lib/Nekland/BaseApi/Http/ClientInterface.php b/lib/Nekland/BaseApi/Http/ClientInterface.php index e79f4e2..5f6f220 100644 --- a/lib/Nekland/BaseApi/Http/ClientInterface.php +++ b/lib/Nekland/BaseApi/Http/ClientInterface.php @@ -16,5 +16,13 @@ interface ClientInterface { public function get($path, array $parameters = [], array $headers = []); + public function post($path, array $parameters = [], array $headers = []); + + public function put($path, array $parameters = [], array $headers = []); + + public function delete($path, array $parameters = [], array $headers = []); + + public function setFormatter(); + public function authenticate($method, array $options); } diff --git a/lib/Nekland/BaseApi/Http/HttpClient.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php similarity index 80% rename from lib/Nekland/BaseApi/Http/HttpClient.php rename to lib/Nekland/BaseApi/Http/HttpClientFactory.php index f253a94..7842fc6 100644 --- a/lib/Nekland/BaseApi/Http/HttpClient.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -11,12 +11,11 @@ namespace Nekland\BaseApi\Http; -use Guzzle\Http\Client as GuzzleClient; use Guzzle\Http\Exception\ServerErrorResponseException; use Nekland\BaseApi\Http\Auth\AuthFactory; use Nekland\BaseApi\Http\Auth\AuthListener; -abstract class HttpClient implements ClientInterface +abstract class HttpClientFactory implements ClientInterface { /** * @var array @@ -31,25 +30,15 @@ abstract class HttpClient implements ClientInterface */ private $headers = []; - /** - * @var \Guzzle\Http\Message\Request - */ - private $lastRequest; - - /** - * @var \Guzzle\Http\Message\Response - */ - private $lastResponse; - /** * @var AuthFactory */ private $authFactory; - public function __construct(array $options = []) + public function __construct(array $options = [], $client) { - $this->options = array_merge($this->options, $options); - $this->client = new GuzzleClient($this->options['base_url'], $this->options); + $this->options = array_merge_recursive($this->options, $options); + $this->client = $this->createGuzzleClient(); $this->authFactory = new AuthFactory(); $this->clearHeaders(); @@ -73,7 +62,6 @@ public function get($path, array $parameters = [], array $headers = []) return $this->request($path, null, 'GET', $headers, array('query' => $parameters))->getBody(); } - public function request($path, $body = null, $httpMethod = 'GET', array $headers = array(), array $options = array()) { $request = $this->createRequest($httpMethod, $path, $body, $headers, $options); @@ -125,22 +113,6 @@ protected function createRequest($httpMethod, $path, $body = null, array $header ); } - /** - * @return mixed - */ - public function getLastRequest() - { - return $this->lastRequest; - } - - /** - * @return mixed - */ - public function getLastResponse() - { - return $this->lastResponse; - } - /** * @return AuthFactory */ From 69899d303999d7d63a030608e0bd929998d2137c Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Thu, 21 Aug 2014 13:23:33 +0200 Subject: [PATCH 02/34] Added new way to retrieve api objects --- lib/Nekland/BaseApi/ApiFactory.php | 30 +++++++++++++++---- .../BaseApi/Exception/MissingApiException.php | 22 ++++++++++++++ 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 lib/Nekland/BaseApi/Exception/MissingApiException.php diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index e0cf227..30a2eaf 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -11,6 +11,8 @@ namespace Nekland\BaseApi; +use Nekland\BaseApi\Exception\MissingApiException; + use Nekland\BaseApi\Http\ClientInterface; abstract class ApiFactory implements ApiInterface @@ -25,12 +27,6 @@ public function __construct(ClientInterface $httpClient) $this->client = $httpClient; } - /** - * @param string $name - * @return \Nekland\BaseApi\Api\AbstractApi - */ - abstract public function api($name); - /*** * @param string $method * @param array $options @@ -47,4 +43,26 @@ public function getClient() { return $this->client; } + + public function __call($name) + { + $apiName = str_replace(['get', 'Api'], '', str_replace('Api', '', $name)); + + foreach ($this->getApiNamespaces() as $namespace) { + $class = $namespace . '\\' . $apiName; + if (class_exists($class)) { + return new $class($this->client); + } + } + + throw new MissingApiException($apiName); + } + + /** + * Return array of namespaces where AbstractApi instance are localized + * + * + * @return string[] Example: ['Nekland\BaseApi\Api'] + */ + abstract protected function getApiNamespaces(); } diff --git a/lib/Nekland/BaseApi/Exception/MissingApiException.php b/lib/Nekland/BaseApi/Exception/MissingApiException.php new file mode 100644 index 0000000..b734135 --- /dev/null +++ b/lib/Nekland/BaseApi/Exception/MissingApiException.php @@ -0,0 +1,22 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Exception; + +class MissingApiException extends \Exception +{ + public function __construct($name, $message = '', $code = 0, \Exception $previous = null) + { + parent::__construct( + sprintf('The api "%s" does not exists, please check the docs.%s', $name, $message, $code, $previous) + ); + } +} From 9dfdc301a013f97142cfd0057a5c6a19e5692d1f Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Fri, 22 Aug 2014 13:27:12 +0200 Subject: [PATCH 03/34] Added transformers --- lib/Nekland/BaseApi/Api/AbstractApi.php | 44 ++++++++++++++++-- lib/Nekland/BaseApi/ApiFactory.php | 26 +++++++++-- .../Http/ClientAdapter/GuzzleAdapter.php | 46 ++++++++++++++++++- .../BaseApi/Http/HttpClientFactory.php | 2 +- .../BaseApi/Transformer/JsonTransformer.php | 18 ++++++++ .../Transformer/TransformerInterface.php | 26 +++++++++++ 6 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 lib/Nekland/BaseApi/Transformer/JsonTransformer.php create mode 100644 lib/Nekland/BaseApi/Transformer/TransformerInterface.php diff --git a/lib/Nekland/BaseApi/Api/AbstractApi.php b/lib/Nekland/BaseApi/Api/AbstractApi.php index 2f45db4..b586515 100644 --- a/lib/Nekland/BaseApi/Api/AbstractApi.php +++ b/lib/Nekland/BaseApi/Api/AbstractApi.php @@ -11,20 +11,54 @@ namespace Nekland\BaseApi\Api; - use Nekland\BaseApi\Api; +use Nekland\BaseApi\Http\ClientInterface; +use Nekland\BaseApi\Transformer\JsonTransformer; +use Nekland\BaseApi\Transformer\TransformerInterface; abstract class AbstractApi { - protected $api; + /** + * @var ClientInterface + */ + private $client; + + /** + * @var TransformerInterface + */ + private $transformer; + + public function __construct(ClientInterface $client, TransformerInterface $transformer = null) + { + $this->client = $client; + $this->transformer = $transformer ?: new JsonTransformer(); + } - public function __construct(Api $api) + /** + * Set the transformer that will be used to return data + * + * @param TransformerInterface $transformer + * @return self + */ + public function setTransformer(TransformerInterface $transformer) { - $this->api = $api; + $this->transformer = $transformer; + + return $this; } protected function get($path, array $parameters = [], array $requestHeaders = []) { - return json_decode((string) $this->api->getClient()->get($path, $parameters, $requestHeaders), true); + return $this->transformer->transform($this->getClient()->get($path, $parameters, $requestHeaders)); + } + + protected function getClient() + { + return $this->client; + } + + protected function getTransformer() + { + return $this->transformer; } } diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 30a2eaf..e2cd09e 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -11,20 +11,27 @@ namespace Nekland\BaseApi; +use Nekland\BaseApi\Api\AbstractApi; use Nekland\BaseApi\Exception\MissingApiException; use Nekland\BaseApi\Http\ClientInterface; +use Nekland\BaseApi\Http\HttpClientFactory; abstract class ApiFactory implements ApiInterface { + /** + * @var HttpClientFactory + */ + private $clientFactory; + /** * @var ClientInterface */ private $client; - public function __construct(ClientInterface $httpClient) + public function __construct(HttpClientFactory $httpClientFactory) { - $this->client = $httpClient; + $this->clientFactory = $httpClientFactory; } /*** @@ -44,6 +51,11 @@ public function getClient() return $this->client; } + /** + * @param string $name + * @return AbstractApi + * @throws \RuntimeException|MissingApiException + */ public function __call($name) { $apiName = str_replace(['get', 'Api'], '', str_replace('Api', '', $name)); @@ -51,7 +63,15 @@ public function __call($name) foreach ($this->getApiNamespaces() as $namespace) { $class = $namespace . '\\' . $apiName; if (class_exists($class)) { - return new $class($this->client); + $api = new $class($this->getClient()); + + if ($api instanceof AbstractApi) { + return $api; + } + + throw new \RuntimeException( + sprintf('The API %s is found but does not implements AbstractApi.', $apiName) + ); } } diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 365c850..16656c5 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -12,8 +12,52 @@ namespace Nekland\BaseApi\Http\ClientAdapter; -use Nekland\BaseApi\Http\ClientInterface; +use GuzzleHttp\Client; class GuzzleAdapter extends AbstractAdapter { + /** + * @var \GuzzleHttp\Client + */ + private $guzzle; + + /** + * @var + */ + private $formatter; + + public function __construct(Client $client = null) + { + $this->guzzle = $client ?: new Client(); + } + + public function get($path, array $parameters = [], array $headers = []) + { + // TODO: Implement get() method. + } + + public function post($path, array $parameters = [], array $headers = []) + { + // TODO: Implement post() method. + } + + public function put($path, array $parameters = [], array $headers = []) + { + // TODO: Implement put() method. + } + + public function delete($path, array $parameters = [], array $headers = []) + { + // TODO: Implement delete() method. + } + + public function setFormatter() + { + // TODO: Implement setFormatter() method. + } + + public function authenticate($method, array $options) + { + // TODO: Implement authenticate() method. + } } diff --git a/lib/Nekland/BaseApi/Http/HttpClientFactory.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php index 7842fc6..6e3fae3 100644 --- a/lib/Nekland/BaseApi/Http/HttpClientFactory.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -35,7 +35,7 @@ abstract class HttpClientFactory implements ClientInterface */ private $authFactory; - public function __construct(array $options = [], $client) + public function __construct(array $options = []) { $this->options = array_merge_recursive($this->options, $options); $this->client = $this->createGuzzleClient(); diff --git a/lib/Nekland/BaseApi/Transformer/JsonTransformer.php b/lib/Nekland/BaseApi/Transformer/JsonTransformer.php new file mode 100644 index 0000000..46e185c --- /dev/null +++ b/lib/Nekland/BaseApi/Transformer/JsonTransformer.php @@ -0,0 +1,18 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Transformer; + + +class JsonTransformer implements TransformerInterface +{ + +} \ No newline at end of file diff --git a/lib/Nekland/BaseApi/Transformer/TransformerInterface.php b/lib/Nekland/BaseApi/Transformer/TransformerInterface.php new file mode 100644 index 0000000..3090829 --- /dev/null +++ b/lib/Nekland/BaseApi/Transformer/TransformerInterface.php @@ -0,0 +1,26 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Transformer; + + +interface TransformerInterface +{ + const UNKNOWN = 'unknown'; + /** + * Depending on what formatter will be used, the data will be transform. + * + * @param string $data + * @param string $type Type of data that is sent + * @return mixed + */ + public function transform($data, $type = self::UNKNOWN); +} \ No newline at end of file From 9afdf6f1316d1fb469829bb3cf77264a55919834 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Fri, 22 Aug 2014 17:54:23 +0200 Subject: [PATCH 04/34] Json transformer --- .../BaseApi/Http/ClientAdapter/GuzzleAdapter.php | 10 ---------- lib/Nekland/BaseApi/Http/ClientInterface.php | 2 -- lib/Nekland/BaseApi/Transformer/JsonTransformer.php | 12 +++++++++++- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 16656c5..c88480f 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -21,11 +21,6 @@ class GuzzleAdapter extends AbstractAdapter */ private $guzzle; - /** - * @var - */ - private $formatter; - public function __construct(Client $client = null) { $this->guzzle = $client ?: new Client(); @@ -51,11 +46,6 @@ public function delete($path, array $parameters = [], array $headers = []) // TODO: Implement delete() method. } - public function setFormatter() - { - // TODO: Implement setFormatter() method. - } - public function authenticate($method, array $options) { // TODO: Implement authenticate() method. diff --git a/lib/Nekland/BaseApi/Http/ClientInterface.php b/lib/Nekland/BaseApi/Http/ClientInterface.php index 5f6f220..ead154c 100644 --- a/lib/Nekland/BaseApi/Http/ClientInterface.php +++ b/lib/Nekland/BaseApi/Http/ClientInterface.php @@ -22,7 +22,5 @@ public function put($path, array $parameters = [], array $headers = []); public function delete($path, array $parameters = [], array $headers = []); - public function setFormatter(); - public function authenticate($method, array $options); } diff --git a/lib/Nekland/BaseApi/Transformer/JsonTransformer.php b/lib/Nekland/BaseApi/Transformer/JsonTransformer.php index 46e185c..2cddd6e 100644 --- a/lib/Nekland/BaseApi/Transformer/JsonTransformer.php +++ b/lib/Nekland/BaseApi/Transformer/JsonTransformer.php @@ -14,5 +14,15 @@ class JsonTransformer implements TransformerInterface { - + /** + * Depending on what formatter will be used, the data will be transform. + * + * @param string $data + * @param string $type Type of data that is sent + * @return array + */ + public function transform($data, $type = self::UNKNOWN) + { + return json_decode($data, true); + } } \ No newline at end of file From 74fa9d84e5a615d482ded134694d6c8284b42a74 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Fri, 22 Aug 2014 18:32:45 +0200 Subject: [PATCH 05/34] Added get method that should work --- .../Http/ClientAdapter/AbstractAdapter.php | 32 +++++++++++++++++-- .../Http/ClientAdapter/GuzzleAdapter.php | 8 +++-- lib/Nekland/BaseApi/Http/ClientInterface.php | 32 +++++++++++++++++++ .../BaseApi/Http/HttpClientFactory.php | 9 +----- .../Transformer/TransformerInterface.php | 2 +- 5 files changed, 70 insertions(+), 13 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php index 858390d..21cee34 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php @@ -15,10 +15,38 @@ abstract class AbstractAdapter implements ClientInterface { - private $options; + /** + * @var mixed[] + */ + private $options = [ + 'base_url' => '', + 'user_agent' => 'php-base-api (https://github.com/Nekland/BaseApi)' + ]; public function __construct(array $options = []) { - $this->options = $options; + $this->options = array_merge($this->options, $options); + } + + /** + * Complete headers using options + * + * @param array $headers + * @return array + */ + protected function getHeaders(array $headers = []) + { + return array_merge(['User-Agent' => $this->options['user_agent']], $headers); + } + + /** + * Generate a complete URL using the option "base_url" + * + * @param string $path The api uri + * @return string + */ + protected function getPath($path) + { + return $this->options['base_url'] . $path; } } diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index c88480f..65e52bb 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -21,14 +21,18 @@ class GuzzleAdapter extends AbstractAdapter */ private $guzzle; - public function __construct(Client $client = null) + public function __construct(array $options = [], Client $client = null) { + parent::__construct($options); $this->guzzle = $client ?: new Client(); } public function get($path, array $parameters = [], array $headers = []) { - // TODO: Implement get() method. + return $this->guzzle->get($this->getPath($path), [ + 'headers' => $this->getHeaders($headers), + 'body' => $parameters + ])->getBody(); } public function post($path, array $parameters = [], array $headers = []) diff --git a/lib/Nekland/BaseApi/Http/ClientInterface.php b/lib/Nekland/BaseApi/Http/ClientInterface.php index ead154c..46e5d7b 100644 --- a/lib/Nekland/BaseApi/Http/ClientInterface.php +++ b/lib/Nekland/BaseApi/Http/ClientInterface.php @@ -14,12 +14,44 @@ interface ClientInterface { + /** + * Execute a GET request on the given path + * + * @param string $path The path of the URL to get + * @param array $parameters The parameters of the request + * @param array $headers The headers of the request + * @return string + */ public function get($path, array $parameters = [], array $headers = []); + /** + * Execute a POST request on the given path + * + * @param string $path The path of the URL to get + * @param array $parameters The parameters of the request + * @param array $headers The headers of the request + * @return string + */ public function post($path, array $parameters = [], array $headers = []); + /** + * Execute a PUT request on the given path + * + * @param string $path The path of the URL to get + * @param array $parameters The parameters of the request + * @param array $headers The headers of the request + * @return string + */ public function put($path, array $parameters = [], array $headers = []); + /** + * Execute a DELETE request on the given path + * + * @param string $path The path of the URL to get + * @param array $parameters The parameters of the request + * @param array $headers The headers of the request + * @return string + */ public function delete($path, array $parameters = [], array $headers = []); public function authenticate($method, array $options); diff --git a/lib/Nekland/BaseApi/Http/HttpClientFactory.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php index 6e3fae3..0e3ecb7 100644 --- a/lib/Nekland/BaseApi/Http/HttpClientFactory.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -15,15 +15,8 @@ use Nekland\BaseApi\Http\Auth\AuthFactory; use Nekland\BaseApi\Http\Auth\AuthListener; -abstract class HttpClientFactory implements ClientInterface +class HttpClientFactory { - /** - * @var array - */ - private $options = [ - 'base_url' => '', - 'user_agent' => 'php-base-api (https://github.com/Nekland/BaseApi)' - ]; /** * @var array diff --git a/lib/Nekland/BaseApi/Transformer/TransformerInterface.php b/lib/Nekland/BaseApi/Transformer/TransformerInterface.php index 3090829..e103688 100644 --- a/lib/Nekland/BaseApi/Transformer/TransformerInterface.php +++ b/lib/Nekland/BaseApi/Transformer/TransformerInterface.php @@ -11,10 +11,10 @@ namespace Nekland\BaseApi\Transformer; - interface TransformerInterface { const UNKNOWN = 'unknown'; + /** * Depending on what formatter will be used, the data will be transform. * From bcf57b41f4cc64d686774d8d2a7797cfc37bff14 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Fri, 22 Aug 2014 19:25:21 +0200 Subject: [PATCH 06/34] Added CacheStrategy support --- lib/Nekland/BaseApi/Api/AbstractApi.php | 36 ++++++++++++++++--- .../BaseApi/Cache/CacheStrategyInterface.php | 23 ++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php diff --git a/lib/Nekland/BaseApi/Api/AbstractApi.php b/lib/Nekland/BaseApi/Api/AbstractApi.php index b586515..bf31b56 100644 --- a/lib/Nekland/BaseApi/Api/AbstractApi.php +++ b/lib/Nekland/BaseApi/Api/AbstractApi.php @@ -12,6 +12,7 @@ namespace Nekland\BaseApi\Api; use Nekland\BaseApi\Api; +use Nekland\BaseApi\Cache\CacheStrategyInterface; use Nekland\BaseApi\Http\ClientInterface; use Nekland\BaseApi\Transformer\JsonTransformer; use Nekland\BaseApi\Transformer\TransformerInterface; @@ -28,10 +29,19 @@ abstract class AbstractApi */ private $transformer; - public function __construct(ClientInterface $client, TransformerInterface $transformer = null) - { + /** + * @var \Nekland\BaseApi\Cache\CacheStrategyInterface + */ + private $cache; + + public function __construct( + ClientInterface $client, + TransformerInterface $transformer = null, + CacheStrategyInterface $cache = null + ) { $this->client = $client; $this->transformer = $transformer ?: new JsonTransformer(); + $this->cache = $cache; } /** @@ -47,9 +57,27 @@ public function setTransformer(TransformerInterface $transformer) return $this; } - protected function get($path, array $parameters = [], array $requestHeaders = []) + protected function get($path, array $parameters = [], array $headers = []) + { + $client = $this->getClient(); + + $closure = function ($path, array $parameters = [], array $headers = []) use ($client) { + return $client->get($path, $parameters, $headers); + }; + + return $this->transformer->transform($this->execute( + $closure, + ['path' => $path, 'parameters' => $parameters, 'headers' => $headers] + )); + } + + private function execute($closure, $parameters) { - return $this->transformer->transform($this->getClient()->get($path, $parameters, $requestHeaders)); + if ($this->cache !== null) { + return $this->cache->prepare($closure, $parameters); + } + + return $closure($parameters['path'], $parameters['parameters'], $parameters['headers']); } protected function getClient() diff --git a/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php b/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php new file mode 100644 index 0000000..27ac3e9 --- /dev/null +++ b/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php @@ -0,0 +1,23 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Cache; + + +interface CacheStrategyInterface +{ + /** + * @param callable $closure + * @param array $parameters + * @return mixed + */ + public function prepare($closure, $parameters); +} \ No newline at end of file From c905809cbc2116e3c698c1398ca22d11d019d11f Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 26 Aug 2014 21:18:25 +0200 Subject: [PATCH 07/34] Added event dispatcher --- composer.json | 9 +- lib/Nekland/BaseApi/Api/AbstractApi.php | 87 ++++++++---- lib/Nekland/BaseApi/ApiFactory.php | 4 +- lib/Nekland/BaseApi/Http/Auth/AuthFactory.php | 2 +- ...nterface.php => AuthStrategyInterface.php} | 2 +- .../Cache/CacheStrategyInterface.php | 10 +- .../Http/ClientAdapter/AbstractAdapter.php | 31 ++++- .../Http/ClientAdapter/GuzzleAdapter.php | 48 +++---- lib/Nekland/BaseApi/Http/ClientInterface.php | 43 ++---- lib/Nekland/BaseApi/Http/Event/Events.php | 18 +++ .../BaseApi/Http/Event/RequestEvent.php | 71 ++++++++++ .../BaseApi/Http/HttpClientFactory.php | 124 ++++++++++-------- lib/Nekland/BaseApi/Http/Request.php | 82 ++++++++++++ .../BaseApi/Http/HttpClientFactorySpec.php | 14 ++ spec/Nekland/BaseApi/Http/RequestSpec.php | 31 +++++ 15 files changed, 424 insertions(+), 152 deletions(-) rename lib/Nekland/BaseApi/Http/Auth/{AuthInterface.php => AuthStrategyInterface.php} (93%) rename lib/Nekland/BaseApi/{ => Http}/Cache/CacheStrategyInterface.php (67%) create mode 100644 lib/Nekland/BaseApi/Http/Event/Events.php create mode 100644 lib/Nekland/BaseApi/Http/Event/RequestEvent.php create mode 100644 lib/Nekland/BaseApi/Http/Request.php create mode 100644 spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php create mode 100644 spec/Nekland/BaseApi/Http/RequestSpec.php diff --git a/composer.json b/composer.json index 271c09e..b6dce68 100644 --- a/composer.json +++ b/composer.json @@ -11,17 +11,18 @@ "homepage": "http://nekland.fr" } ], + "require": { + "php": ">=5.4", + "symfony/event-dispatcher": "~2.3" + }, "suggest": { "nekland/youtube-api": "Youtube API made easy !", "nekland/soundcloud-api": "Soundcloud API made easy !", - "guzzle/guzzle": "The default HttpClient will not work without this library." + "guzzle/guzzle": "The default HttpClient will not work without this library." }, "autoload": { "psr-0": { "Nekland\\": "lib/" } }, - "require": { - "php": ">=5.4" - }, "require-dev": { "phpspec/phpspec": "dev-master", "guzzlehttp/guzzle": "~4.2" diff --git a/lib/Nekland/BaseApi/Api/AbstractApi.php b/lib/Nekland/BaseApi/Api/AbstractApi.php index bf31b56..bed98a7 100644 --- a/lib/Nekland/BaseApi/Api/AbstractApi.php +++ b/lib/Nekland/BaseApi/Api/AbstractApi.php @@ -29,19 +29,9 @@ abstract class AbstractApi */ private $transformer; - /** - * @var \Nekland\BaseApi\Cache\CacheStrategyInterface - */ - private $cache; - - public function __construct( - ClientInterface $client, - TransformerInterface $transformer = null, - CacheStrategyInterface $cache = null - ) { - $this->client = $client; + public function __construct(ClientInterface $client, TransformerInterface $transformer = null) { + $this->client = $client; $this->transformer = $transformer ?: new JsonTransformer(); - $this->cache = $cache; } /** @@ -57,34 +47,81 @@ public function setTransformer(TransformerInterface $transformer) return $this; } - protected function get($path, array $parameters = [], array $headers = []) + /** + * Execute a http get query + * + * @param string $path + * @param array $body + * @param array $headers + * @return array|mixed + */ + protected function get($path, array $body = [], array $headers = []) + { + $client = $this->getClient(); + $request = $client::createRequest('GET', $path, $body, $headers); + + return $this->transformer->transform($client->send($request)); + } + + /** + * Execute a http put query + * + * @param string $path + * @param array $body + * @param array $headers + * @return array|mixed + */ + protected function put($path, array $body = [], array $headers = []) { $client = $this->getClient(); + $request = $client::createRequest('PUT', $path, $body, $headers); + + return $this->transformer->transform($client->send($request)); + } - $closure = function ($path, array $parameters = [], array $headers = []) use ($client) { - return $client->get($path, $parameters, $headers); - }; + /** + * Execute a http post query + * + * @param string $path + * @param array $body + * @param array $headers + * @return array|mixed + */ + protected function post($path, array $body = [], array $headers = []) + { + $client = $this->getClient(); + $request = $client::createRequest('POST', $path, $body, $headers); - return $this->transformer->transform($this->execute( - $closure, - ['path' => $path, 'parameters' => $parameters, 'headers' => $headers] - )); + return $this->transformer->transform($client->send($request)); } - private function execute($closure, $parameters) + /** + * Execute a http delete query + * + * @param string $path + * @param array $body + * @param array $headers + * @return array|mixed + */ + protected function delete($path, array $body = [], array $headers = []) { - if ($this->cache !== null) { - return $this->cache->prepare($closure, $parameters); - } + $client = $this->getClient(); + $request = $client::createRequest('DELETE', $path, $body, $headers); - return $closure($parameters['path'], $parameters['parameters'], $parameters['headers']); + return $this->transformer->transform($client->send($request)); } + /** + * @return ClientInterface + */ protected function getClient() { return $this->client; } + /** + * @return TransformerInterface + */ protected function getTransformer() { return $this->transformer; diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index e2cd09e..329e24b 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -40,7 +40,7 @@ public function __construct(HttpClientFactory $httpClientFactory) */ public function authenticate($method, array $options) { - $this->client->authenticate($method, $options); + $this->clientFactory->authenticate($method, $options); } /** @@ -48,7 +48,7 @@ public function authenticate($method, array $options) */ public function getClient() { - return $this->client; + return $this->clientFactory->createHttpClient(); } /** diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php index d6360ff..c394136 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php @@ -34,7 +34,7 @@ public function __construct() /** * @param string $authName - * @return AuthInterface + * @return AuthStrategyInterface * @throws \RuntimeException */ public function get($authName) diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthInterface.php b/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php similarity index 93% rename from lib/Nekland/BaseApi/Http/Auth/AuthInterface.php rename to lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php index ead792b..38a88f3 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthInterface.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php @@ -12,7 +12,7 @@ namespace Nekland\BaseApi\Http\Auth; -interface AuthInterface +interface AuthStrategyInterface { /** * @param array $options diff --git a/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php b/lib/Nekland/BaseApi/Http/Cache/CacheStrategyInterface.php similarity index 67% rename from lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php rename to lib/Nekland/BaseApi/Http/Cache/CacheStrategyInterface.php index 27ac3e9..37271ee 100644 --- a/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php +++ b/lib/Nekland/BaseApi/Http/Cache/CacheStrategyInterface.php @@ -12,12 +12,12 @@ namespace Nekland\BaseApi\Cache; +use Nekland\BaseApi\Http\Event\RequestEvent; + interface CacheStrategyInterface { /** - * @param callable $closure - * @param array $parameters - * @return mixed + * @param RequestEvent $event */ - public function prepare($closure, $parameters); -} \ No newline at end of file + public function execute(RequestEvent $event); +} diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php index 21cee34..b6b8760 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php @@ -8,10 +8,13 @@ * For the full license, take a look to the LICENSE file * on the root directory of this project */ + namespace Nekland\BaseApi\Http\ClientAdapter; use Nekland\BaseApi\Http\ClientInterface; +use Nekland\BaseApi\Http\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; abstract class AbstractAdapter implements ClientInterface { @@ -23,8 +26,14 @@ abstract class AbstractAdapter implements ClientInterface 'user_agent' => 'php-base-api (https://github.com/Nekland/BaseApi)' ]; - public function __construct(array $options = []) + /** + * @var \Symfony\Component\EventDispatcher\EventDispatcher + */ + private $dispatcher; + + public function __construct(EventDispatcher $eventDispatcher, array $options = []) { + $this->dispatcher = $eventDispatcher; $this->options = array_merge($this->options, $options); } @@ -49,4 +58,24 @@ protected function getPath($path) { return $this->options['base_url'] . $path; } + + /** + * @param string $method + * @param string $path + * @param array $parameters + * @param array $headers + * @return Request + */ + public static function createRequest($method, $path, array $parameters = [], array $headers = []) + { + return new Request($method, $path, $parameters, $headers); + } + + /** + * @return EventDispatcher + */ + public function getEventDispatcher() + { + return $this->dispatcher; + } } diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 65e52bb..83602a0 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -13,6 +13,10 @@ use GuzzleHttp\Client; +use Nekland\BaseApi\Http\Event\Events; +use Nekland\BaseApi\Http\Event\RequestEvent; +use Nekland\BaseApi\Http\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; class GuzzleAdapter extends AbstractAdapter { @@ -21,37 +25,35 @@ class GuzzleAdapter extends AbstractAdapter */ private $guzzle; - public function __construct(array $options = [], Client $client = null) + public function __construct(array $options = [], EventDispatcher $dispatcher = null, Client $client = null) { - parent::__construct($options); + parent::__construct($dispatcher, $options); $this->guzzle = $client ?: new Client(); } - public function get($path, array $parameters = [], array $headers = []) + public function send(Request $request) { - return $this->guzzle->get($this->getPath($path), [ - 'headers' => $this->getHeaders($headers), - 'body' => $parameters - ])->getBody(); - } + $method = $request->getMethod(); - public function post($path, array $parameters = [], array $headers = []) - { - // TODO: Implement post() method. - } + if (!in_array($method, ['get', 'put', 'post', 'delete'])) { + throw new \BadMethodCallException(sprintf( + 'The http method "%s" does not exists or is not supported.', + $method + )); + } - public function put($path, array $parameters = [], array $headers = []) - { - // TODO: Implement put() method. - } + $event = new RequestEvent($request); - public function delete($path, array $parameters = [], array $headers = []) - { - // TODO: Implement delete() method. - } + $this->getEventDispatcher()->dispatch(Events::ON_REQUEST_EVENT, $event); - public function authenticate($method, array $options) - { - // TODO: Implement authenticate() method. + if ($event->requestCompleted()) { + return $event->getResponse(); + } + + + return $this->guzzle->$method($this->getPath($request->getPath()), [ + 'headers' => $this->getHeaders($request->getHeaders()), + 'body' => $request->getBody() + ])->getBody(); } } diff --git a/lib/Nekland/BaseApi/Http/ClientInterface.php b/lib/Nekland/BaseApi/Http/ClientInterface.php index 46e5d7b..fe4c595 100644 --- a/lib/Nekland/BaseApi/Http/ClientInterface.php +++ b/lib/Nekland/BaseApi/Http/ClientInterface.php @@ -15,44 +15,19 @@ interface ClientInterface { /** - * Execute a GET request on the given path + * Generate a request object * - * @param string $path The path of the URL to get - * @param array $parameters The parameters of the request - * @param array $headers The headers of the request - * @return string + * @param string $method + * @param string $path + * @param array $parameters + * @param array $headers + * @return Request */ - public function get($path, array $parameters = [], array $headers = []); + public static function createRequest($method, $path, array $parameters = [], array $headers = []); /** - * Execute a POST request on the given path - * - * @param string $path The path of the URL to get - * @param array $parameters The parameters of the request - * @param array $headers The headers of the request - * @return string - */ - public function post($path, array $parameters = [], array $headers = []); - - /** - * Execute a PUT request on the given path - * - * @param string $path The path of the URL to get - * @param array $parameters The parameters of the request - * @param array $headers The headers of the request + * @param Request $request * @return string */ - public function put($path, array $parameters = [], array $headers = []); - - /** - * Execute a DELETE request on the given path - * - * @param string $path The path of the URL to get - * @param array $parameters The parameters of the request - * @param array $headers The headers of the request - * @return string - */ - public function delete($path, array $parameters = [], array $headers = []); - - public function authenticate($method, array $options); + public function send(Request $request); } diff --git a/lib/Nekland/BaseApi/Http/Event/Events.php b/lib/Nekland/BaseApi/Http/Event/Events.php new file mode 100644 index 0000000..f99d306 --- /dev/null +++ b/lib/Nekland/BaseApi/Http/Event/Events.php @@ -0,0 +1,18 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Http\Event; + + +class Events +{ + const ON_REQUEST_EVENT = 'nekland_api.on_http_request'; +} diff --git a/lib/Nekland/BaseApi/Http/Event/RequestEvent.php b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php new file mode 100644 index 0000000..19eb35f --- /dev/null +++ b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php @@ -0,0 +1,71 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Http\Event; + + +use Nekland\BaseApi\Http\Request; +use Symfony\Component\EventDispatcher\Event; + +class RequestEvent extends Event +{ + /** + * @var \Nekland\BaseApi\Http\Request + */ + private $request; + + /** + * @var string + */ + private $response; + + public function __construct(Request $request) + { + $this->request = $request; + } + + /** + * @return \Nekland\BaseApi\Http\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * @param string $response + * @throws \InvalidArgumentException + */ + public function setResponse($response) + { + if (!is_string($response)) { + throw new \InvalidArgumentException('The response should be send as string'); + } + + $this->response = $response; + } + + /** + * @return bool + */ + public function requestCompleted() + { + return isset($this->response); + } + + /** + * @return string + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/lib/Nekland/BaseApi/Http/HttpClientFactory.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php index 0e3ecb7..0f3bf64 100644 --- a/lib/Nekland/BaseApi/Http/HttpClientFactory.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -11,98 +11,111 @@ namespace Nekland\BaseApi\Http; -use Guzzle\Http\Exception\ServerErrorResponseException; +use Nekland\BaseApi\Cache\CacheStrategyInterface; use Nekland\BaseApi\Http\Auth\AuthFactory; use Nekland\BaseApi\Http\Auth\AuthListener; +use Nekland\BaseApi\Http\Auth\AuthStrategyInterface; +use Nekland\BaseApi\Http\ClientAdapter\GuzzleAdapter; +use Nekland\BaseApi\Http\Event\Events; +use Symfony\Component\EventDispatcher\EventDispatcher; class HttpClientFactory { - /** - * @var array + * @var AuthFactory */ - private $headers = []; + private $authFactory; /** - * @var AuthFactory + * @var \Symfony\Component\EventDispatcher\EventDispatcher */ - private $authFactory; + private $dispatcher; - public function __construct(array $options = []) - { - $this->options = array_merge_recursive($this->options, $options); - $this->client = $this->createGuzzleClient(); - $this->authFactory = new AuthFactory(); + /** + * @var string + */ + private $classes = [ + 'guzzle4' => 'Nekland\BaseApi\Api\Http\ClientAdapter\GuzzleAdapter' + ]; - $this->clearHeaders(); - } + private $options = []; - protected function clearHeaders() + public function __construct(array $options = [], EventDispatcher $eventDispatcher = null) { - $this->headers = [ - 'User-Agent' => $this->options['user_agent'] - ]; + $this->options = array_merge_recursive($this->options, $options); + $this->dispatcher = $eventDispatcher ?: new EventDispatcher(); + $this->authFactory = new AuthFactory(); } /** - * @param $path - * @param array $parameters - * @param array $headers - * @return \Guzzle\Http\Message\Response + * Generate an HttpClient object + * + * @param string $name + * @param null $client + * @return GuzzleAdapter|null + * @throws \InvalidArgumentException */ - public function get($path, array $parameters = [], array $headers = []) + public function createHttpClient($name = '', $client = null) { - return $this->request($path, null, 'GET', $headers, array('query' => $parameters))->getBody(); - } + if (empty($name)) { + return new GuzzleAdapter($this->options, $client); + } - public function request($path, $body = null, $httpMethod = 'GET', array $headers = array(), array $options = array()) - { - $request = $this->createRequest($httpMethod, $path, $body, $headers, $options); + if (!isset($this->classes[$name])) { + throw new \InvalidArgumentException(sprintf('The client "%s" is not registered.', $name)); + } - $response = $this->client->send($request); + $class = $this->classes[$name]; + $client = new $class($this->options, $client); - $this->lastRequest = $request; - $this->lastResponse = $response; + if ($client instanceof ClientInterface) { + return $client; + } - - return $response; + throw new \InvalidArgumentException('The client must be an implementation of ClientInterface.'); } - /*** - * @param string $method - * @param array $options + /** + * @param string $name + * @param string $class + * @throws \InvalidArgumentException */ - public function authenticate($method, array $options) + public function register($name, $class) { - $auth = $this->authFactory->get($method); - $auth->setOptions($options); + if (class_exists($class)) { + $this->classes[$name] = $class; + } - $this->addListener('request.before_send', array( - new AuthListener($auth), - 'onRequestBeforeSend' - )); + throw new \InvalidArgumentException(sprintf('%s is not a valid class name.', $name)); } /** - * Add an event on the http client + * Allow the user to add an authentication to the request * - * @param $eventName - * @param $listener + * @param string|AuthStrategyInterface $auth + * @param array $options */ - public function addListener($eventName, $listener) + public function authenticate($auth, array $options = []) { - $this->client->getEventDispatcher()->addListener($eventName, $listener); - } + if (!($auth instanceof AuthStrategyInterface)) { + $auth = $this->authFactory->get($auth); + $auth->setOptions($options); + } + $this->dispatcher->addListener(Events::ON_REQUEST_EVENT, array( + new AuthListener($auth), + 'onRequestBeforeSend' + )); + } - protected function createRequest($httpMethod, $path, $body = null, array $headers = array(), array $options = array()) + /** + * @param CacheStrategyInterface $cacheStrategy + */ + public function useCache(CacheStrategyInterface $cacheStrategy) { - return $this->client->createRequest( - $httpMethod, - $path, - array_merge($this->headers, $headers), - $body, - $options + $this->dispatcher->addListener( + Events::ON_REQUEST_EVENT, + [ $cacheStrategy, 'execute' ] ); } @@ -126,5 +139,4 @@ public function setOption($name, $value) return $this; } - } diff --git a/lib/Nekland/BaseApi/Http/Request.php b/lib/Nekland/BaseApi/Http/Request.php new file mode 100644 index 0000000..71c29f7 --- /dev/null +++ b/lib/Nekland/BaseApi/Http/Request.php @@ -0,0 +1,82 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Http; + + +class Request +{ + /** + * @var string + */ + private $method; + + /** + * @var string + */ + private $path; + + /** + * @var array + */ + private $body; + + /** + * @var array + */ + private $headers; + + /** + * @param string $method + * @param string $path + * @param array $body + * @param array $headers + */ + public function __construct($method, $path, array $body = [], array $headers = []) + { + $this->method = $method; + $this->path = $path; + $this->body = $body; + $this->headers = $headers; + } + + /** + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * @return string + */ + public function getMethod() + { + return strtolower($this->method); + } + + /** + * @return array + */ + public function getBody() + { + return $this->body; + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php b/spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php new file mode 100644 index 0000000..0e49099 --- /dev/null +++ b/spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php @@ -0,0 +1,14 @@ +shouldHaveType('Nekland\BaseApi\Http\HttpClientFactory'); + } +} diff --git a/spec/Nekland/BaseApi/Http/RequestSpec.php b/spec/Nekland/BaseApi/Http/RequestSpec.php new file mode 100644 index 0000000..b797013 --- /dev/null +++ b/spec/Nekland/BaseApi/Http/RequestSpec.php @@ -0,0 +1,31 @@ + 'hello']; + private $headers = ['User-Agent' => 'phpspec']; + + public function it_is_initializable() + { + $this->shouldHaveType('Nekland\BaseApi\Http\Request'); + } + + public function let() + { + $this->beConstructedWith('GET', $this->path, $this->body, $this->headers); + } + + public function it_should_return_values_setted_in_constructor() + { + $this->getHeaders()->shouldReturn($this->headers); + $this->getBody()->shouldReturn($this->body); + $this->getPath()->shouldReturn($this->path); + $this->getMethod()->shouldReturn('get'); + } +} From a2ccfdd2d42b6f32a9c0aed6d11183cc15fad19f Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 27 Aug 2014 15:55:12 +0200 Subject: [PATCH 08/34] Added tests and started documentation --- .travis.yml | 12 ++ CHANGELOG.md | 17 +++ Readme.md | 14 ++- doc/api_classes.md | 0 doc/api_factory.md | 18 +++ doc/index.md | 11 ++ lib/Nekland/BaseApi/Api/AbstractApi.php | 9 +- lib/Nekland/BaseApi/ApiFactory.php | 105 ++++++++++++++++-- .../Cache/CacheStrategyInterface.php | 0 ...ractAdapter.php => AbstractHttpClient.php} | 48 +++++++- lib/Nekland/BaseApi/Http/Auth/AuthFactory.php | 21 +++- .../BaseApi/Http/Auth/AuthListener.php | 6 +- .../Http/ClientAdapter/GuzzleAdapter.php | 25 +---- lib/Nekland/BaseApi/Http/ClientInterface.php | 33 ------ lib/Nekland/BaseApi/Http/Event/Events.php | 1 + .../BaseApi/Http/Event/RequestEvent.php | 2 +- lib/Nekland/BaseApi/Http/HttpClientAware.php | 28 +++++ .../BaseApi/Http/HttpClientFactory.php | 103 +++++++++-------- .../Http/ClientAdapter/GuzzleAdapterSpec.php | 62 +++++++++++ .../BaseApi/Http/HttpClientFactorySpec.php | 22 ++++ .../Transformer/JsonTransformerSpec.php | 21 ++++ spec/fixture/MyHttpClient.php | 15 +++ 22 files changed, 438 insertions(+), 135 deletions(-) create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 doc/api_classes.md create mode 100644 doc/api_factory.md create mode 100644 doc/index.md rename lib/Nekland/BaseApi/{Http => }/Cache/CacheStrategyInterface.php (100%) rename lib/Nekland/BaseApi/Http/{ClientAdapter/AbstractAdapter.php => AbstractHttpClient.php} (56%) delete mode 100644 lib/Nekland/BaseApi/Http/ClientInterface.php create mode 100644 lib/Nekland/BaseApi/Http/HttpClientAware.php create mode 100644 spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php create mode 100644 spec/Nekland/BaseApi/Transformer/JsonTransformerSpec.php create mode 100644 spec/fixture/MyHttpClient.php diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4e56fac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + +before_script: + - composer install --dev --prefer-source + +script: ./vendor/bin/phpspec run diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..119c5e3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +1.0.0 (2014-08-XX) +================== + +New awesome features +-------------------- + +* Drop Guzzle dependency +* Added dependency on Symfony Event Dispatcher Component +* Added specificity tests with phpspec +* Added cache strategy support +* Added transformer strategies +* Made the ApiFactory "IDE friendly" + +Compatibility breaks: +--------------------- + +Almost everything, sorry guys this is a new, very very major version :-). diff --git a/Readme.md b/Readme.md index 8113332..b191d68 100644 --- a/Readme.md +++ b/Readme.md @@ -8,9 +8,21 @@ I made this project because I created a lib for Youtube API and another for Soun Both libs have the same needs. To avoid duplicated code, I made this little project that can be used as a base for the API lib you want to create. +How +--- + +[x] [Semver](http://semver.org) compliant +[x] [HHVM](http://hhvm.com/) compatible +[x] [Composer](http://packagist) installable + +Documentation +------------- + +This project does not need so much documentation but I wrote some for interested people. Checkout the [doc](doc) folder. + Inspiration ----------- This project look like php-github-api, and it's not innocent. I used it as model. -Thanks to KnpLabs & Contributors on this project. \ No newline at end of file +Thanks to KnpLabs & Contributors on this project. diff --git a/doc/api_classes.md b/doc/api_classes.md new file mode 100644 index 0000000..e69de29 diff --git a/doc/api_factory.md b/doc/api_factory.md new file mode 100644 index 0000000..7868b6f --- /dev/null +++ b/doc/api_factory.md @@ -0,0 +1,18 @@ +The ApiFactory +============== + +This class instantiate "[Api classes](api_classes.md)" when you use it like that thanks to the magic method `call`: + +```php +getMyAwesomeApi(); +``` + +So the first thing you have to do when building your API is to extend it and implement the only needed method. + +You can also: + +* Redefine the `getTransformer` method to change the default one. + +Now, build your [API classes](api_classes.md). diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..761643c --- /dev/null +++ b/doc/index.md @@ -0,0 +1,11 @@ +Nekland base api +================ + +The idea is to made easy build of new PHP api libs. So this lib provide you some abstract class that will work together with other classes. + +You will learn more about things in the dedicated pages of doc: + +* [ApiFactory](api_factory.md) +* [Api classes](api_classes.md) +* [Auth and Cache strategies](auth_and_cache.md) +* [Transformers](transformers.md) diff --git a/lib/Nekland/BaseApi/Api/AbstractApi.php b/lib/Nekland/BaseApi/Api/AbstractApi.php index bed98a7..21e8a7d 100644 --- a/lib/Nekland/BaseApi/Api/AbstractApi.php +++ b/lib/Nekland/BaseApi/Api/AbstractApi.php @@ -12,15 +12,14 @@ namespace Nekland\BaseApi\Api; use Nekland\BaseApi\Api; -use Nekland\BaseApi\Cache\CacheStrategyInterface; -use Nekland\BaseApi\Http\ClientInterface; +use Nekland\BaseApi\Http\AbstractHttpClient; use Nekland\BaseApi\Transformer\JsonTransformer; use Nekland\BaseApi\Transformer\TransformerInterface; abstract class AbstractApi { /** - * @var ClientInterface + * @var AbstractHttpClient */ private $client; @@ -29,7 +28,7 @@ abstract class AbstractApi */ private $transformer; - public function __construct(ClientInterface $client, TransformerInterface $transformer = null) { + public function __construct(AbstractHttpClient $client, TransformerInterface $transformer = null) { $this->client = $client; $this->transformer = $transformer ?: new JsonTransformer(); } @@ -112,7 +111,7 @@ protected function delete($path, array $body = [], array $headers = []) } /** - * @return ClientInterface + * @return AbstractHttpClient */ protected function getClient() { diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 329e24b..f21019d 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -12,10 +12,18 @@ namespace Nekland\BaseApi; use Nekland\BaseApi\Api\AbstractApi; +use Nekland\BaseApi\Cache\CacheStrategyInterface; use Nekland\BaseApi\Exception\MissingApiException; +use Nekland\BaseApi\Http\Auth\AuthFactory; +use Nekland\BaseApi\Http\Auth\AuthListener; +use Nekland\BaseApi\Http\Auth\AuthStrategyInterface; use Nekland\BaseApi\Http\ClientInterface; +use Nekland\BaseApi\Http\Event\Events; use Nekland\BaseApi\Http\HttpClientFactory; +use Nekland\BaseApi\Transformer\JsonTransformer; +use Nekland\BaseApi\Transformer\TransformerInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; abstract class ApiFactory implements ApiInterface { @@ -25,22 +33,56 @@ abstract class ApiFactory implements ApiInterface private $clientFactory; /** - * @var ClientInterface + * @var Http\Auth\AuthFactory */ - private $client; + private $authFactory; - public function __construct(HttpClientFactory $httpClientFactory) + /** + * @var TransformerInterface + */ + private $transformer; + + public function __construct( + EventDispatcher $dispatcher = null, + TransformerInterface $transformer = null, + AuthFactory $authFactory = null, + HttpClientFactory $httpClientFactory = null + ) { + $this->dispatcher = $dispatcher ?: new EventDispatcher(); + $this->clientFactory = $httpClientFactory ?: new HttpClientFactory($this->dispatcher); + $this->authFactory = $authFactory; + $this->transformer = $transformer; + } + + + /** + * Allow the user to add an authentication to the request + * + * @param string|AuthStrategyInterface $auth + * @param array $options + */ + public function authenticate($auth, array $options = []) { - $this->clientFactory = $httpClientFactory; + if (!($auth instanceof AuthStrategyInterface)) { + $auth = $this->getAuthFactory()->get($auth); + $auth->setOptions($options); + } + + $this->dispatcher->addListener(Events::ON_REQUEST_EVENT, array( + new AuthListener($auth), + 'onRequestBeforeSend' + )); } - /*** - * @param string $method - * @param array $options + /** + * @param CacheStrategyInterface $cacheStrategy */ - public function authenticate($method, array $options) + public function useCache(CacheStrategyInterface $cacheStrategy) { - $this->clientFactory->authenticate($method, $options); + $this->dispatcher->addListener( + Events::ON_REQUEST_EVENT, + [ $cacheStrategy, 'execute' ] + ); } /** @@ -51,19 +93,44 @@ public function getClient() return $this->clientFactory->createHttpClient(); } + /** + * @return HttpClientFactory + */ + public function getHttpClientFactory() + { + return $this->clientFactory; + } + + /** + * @param HttpClientFactory $clientFactory + * @return self + */ + public function setHttpClientFactory(HttpClientFactory $clientFactory) + { + $this->clientFactory = $clientFactory; + + return $this; + } + + public function setTransformer(TransformerInterface $transformer) + { + $this->transformer = $transformer; + } + /** * @param string $name + * @param array $parameters * @return AbstractApi * @throws \RuntimeException|MissingApiException */ - public function __call($name) + public function __call($name, $parameters) { $apiName = str_replace(['get', 'Api'], '', str_replace('Api', '', $name)); foreach ($this->getApiNamespaces() as $namespace) { $class = $namespace . '\\' . $apiName; if (class_exists($class)) { - $api = new $class($this->getClient()); + $api = new $class($this->getClient(), $this->getTransformer()); if ($api instanceof AbstractApi) { return $api; @@ -78,6 +145,22 @@ public function __call($name) throw new MissingApiException($apiName); } + /** + * @return AuthFactory + */ + protected function getAuthFactory() + { + return $this->authFactory ?: new AuthFactory($this->getClient()); + } + + /** + * @return TransformerInterface + */ + protected function getTransformer() + { + return $this->transformer ?: new JsonTransformer(); + } + /** * Return array of namespaces where AbstractApi instance are localized * diff --git a/lib/Nekland/BaseApi/Http/Cache/CacheStrategyInterface.php b/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php similarity index 100% rename from lib/Nekland/BaseApi/Http/Cache/CacheStrategyInterface.php rename to lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php similarity index 56% rename from lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php rename to lib/Nekland/BaseApi/Http/AbstractHttpClient.php index b6b8760..e090a46 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/AbstractAdapter.php +++ b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php @@ -9,14 +9,15 @@ * on the root directory of this project */ -namespace Nekland\BaseApi\Http\ClientAdapter; +namespace Nekland\BaseApi\Http; -use Nekland\BaseApi\Http\ClientInterface; +use Nekland\BaseApi\Http\Event\Events; +use Nekland\BaseApi\Http\Event\RequestEvent; use Nekland\BaseApi\Http\Request; use Symfony\Component\EventDispatcher\EventDispatcher; -abstract class AbstractAdapter implements ClientInterface +abstract class AbstractHttpClient { /** * @var mixed[] @@ -34,9 +35,46 @@ abstract class AbstractAdapter implements ClientInterface public function __construct(EventDispatcher $eventDispatcher, array $options = []) { $this->dispatcher = $eventDispatcher; - $this->options = array_merge($this->options, $options); + $this->options = array_merge_recursive($this->options, $options); } + /** + * @param Request $request + * @return string + * @throws \BadMethodCallException + */ + public function send(Request $request) + { + $method = $request->getMethod(); + + if (!in_array($method, ['get', 'put', 'post', 'delete'])) { + throw new \BadMethodCallException(sprintf( + 'The http method "%s" does not exists or is not supported.', + $method + )); + } + + /** @var RequestEvent $event */ + $event = $this->getEventDispatcher()->dispatch(Events::ON_REQUEST_EVENT, new RequestEvent($request)); + + if (!$event->requestCompleted()) { + $res = $this->execute($request); + $event->setResponse($res); + } + + $this->getEventDispatcher()->dispatch(Events::AFTER_REQUEST_EVENT, $event); + + return $event->getResponse(); + } + + /** + * Execute a request + * + * @param Request $request + * @return string + */ + abstract protected function execute(Request $request); + /** * Complete headers using options * @@ -60,6 +98,8 @@ protected function getPath($path) } /** + * Generate a request object + * * @param string $method * @param string $path * @param array $parameters diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php index c394136..5bfe162 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php @@ -12,6 +12,9 @@ namespace Nekland\BaseApi\Http\Auth; +use Nekland\BaseApi\Http\AbstractHttpClient; +use Nekland\BaseApi\Http\HttpClientAware; + class AuthFactory { /** @@ -24,9 +27,15 @@ class AuthFactory */ private $namespaces; - public function __construct() + /** + * @var \Nekland\BaseApi\Http\AbstractHttpClient + */ + private $client; + + public function __construct(AbstractHttpClient $client) { $this->authentications = []; + $this->client = $client; $this->namespaces = [ 'Nekland\\BaseApi\\Http\\Auth\\' ]; @@ -47,11 +56,17 @@ public function get($authName) $authClass = $namespace . '\\' . $authName; if (class_exists($authClass)) { - return new $authClass(); + $auth = new $authClass(); + if ($auth instanceof HttpClientAware) { + $auth->setClient($this->client); + } + return $this->authentications[$authName] = $auth; } } - throw new \RuntimeException('The method "'.$authName.'" is not supported for authentication.'); + throw new \RuntimeException( + sprintf('The method "%s" is not supported for authentication.', $authName) + ); } /** diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php b/lib/Nekland/BaseApi/Http/Auth/AuthListener.php index 18ec3df..3001da1 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthListener.php @@ -11,7 +11,7 @@ namespace Nekland\BaseApi\Http\Auth; -use Guzzle\Common\Event; +use Nekland\BaseApi\Http\Event\RequestEvent; class AuthListener { @@ -25,12 +25,12 @@ public function __construct(AuthInterface $auth) $this->auth = $auth; } - public function onRequestBeforeSend(Event $event) + public function onRequestBeforeSend(RequestEvent $event) { if (null === $this->auth) { return; } - $this->auth->auth($event['request']); + $this->auth->auth($event->getRequest()); } } diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 83602a0..f33f5e6 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -13,44 +13,27 @@ use GuzzleHttp\Client; -use Nekland\BaseApi\Http\Event\Events; -use Nekland\BaseApi\Http\Event\RequestEvent; +use Nekland\BaseApi\Http\AbstractHttpClient; use Nekland\BaseApi\Http\Request; use Symfony\Component\EventDispatcher\EventDispatcher; -class GuzzleAdapter extends AbstractAdapter +class GuzzleAdapter extends AbstractHttpClient { /** * @var \GuzzleHttp\Client */ private $guzzle; - public function __construct(array $options = [], EventDispatcher $dispatcher = null, Client $client = null) + public function __construct(EventDispatcher $dispatcher, array $options = [], Client $client = null) { parent::__construct($dispatcher, $options); $this->guzzle = $client ?: new Client(); } - public function send(Request $request) + protected function execute(Request $request) { $method = $request->getMethod(); - if (!in_array($method, ['get', 'put', 'post', 'delete'])) { - throw new \BadMethodCallException(sprintf( - 'The http method "%s" does not exists or is not supported.', - $method - )); - } - - $event = new RequestEvent($request); - - $this->getEventDispatcher()->dispatch(Events::ON_REQUEST_EVENT, $event); - - if ($event->requestCompleted()) { - return $event->getResponse(); - } - - return $this->guzzle->$method($this->getPath($request->getPath()), [ 'headers' => $this->getHeaders($request->getHeaders()), 'body' => $request->getBody() diff --git a/lib/Nekland/BaseApi/Http/ClientInterface.php b/lib/Nekland/BaseApi/Http/ClientInterface.php deleted file mode 100644 index fe4c595..0000000 --- a/lib/Nekland/BaseApi/Http/ClientInterface.php +++ /dev/null @@ -1,33 +0,0 @@ - - * - * For the full license, take a look to the LICENSE file - * on the root directory of this project - */ - -namespace Nekland\BaseApi\Http; - - -interface ClientInterface -{ - /** - * Generate a request object - * - * @param string $method - * @param string $path - * @param array $parameters - * @param array $headers - * @return Request - */ - public static function createRequest($method, $path, array $parameters = [], array $headers = []); - - /** - * @param Request $request - * @return string - */ - public function send(Request $request); -} diff --git a/lib/Nekland/BaseApi/Http/Event/Events.php b/lib/Nekland/BaseApi/Http/Event/Events.php index f99d306..1a5a856 100644 --- a/lib/Nekland/BaseApi/Http/Event/Events.php +++ b/lib/Nekland/BaseApi/Http/Event/Events.php @@ -15,4 +15,5 @@ class Events { const ON_REQUEST_EVENT = 'nekland_api.on_http_request'; + const AFTER_REQUEST_EVENT = 'nekland_api.after_http_request'; } diff --git a/lib/Nekland/BaseApi/Http/Event/RequestEvent.php b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php index 19eb35f..c37c6cd 100644 --- a/lib/Nekland/BaseApi/Http/Event/RequestEvent.php +++ b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php @@ -41,7 +41,7 @@ public function getRequest() } /** - * @param string $response + * @param mixed $response * @throws \InvalidArgumentException */ public function setResponse($response) diff --git a/lib/Nekland/BaseApi/Http/HttpClientAware.php b/lib/Nekland/BaseApi/Http/HttpClientAware.php new file mode 100644 index 0000000..f3c8800 --- /dev/null +++ b/lib/Nekland/BaseApi/Http/HttpClientAware.php @@ -0,0 +1,28 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Http; + +abstract class HttpClientAware +{ + /** + * @var AbstractHttpClient + */ + protected $httpClient; + + /** + * @param AbstractHttpClient $client + */ + public function setClient(AbstractHttpClient $client) + { + $this->httpClient = $client; + } +} diff --git a/lib/Nekland/BaseApi/Http/HttpClientFactory.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php index 0f3bf64..87d5f50 100644 --- a/lib/Nekland/BaseApi/Http/HttpClientFactory.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -11,64 +11,61 @@ namespace Nekland\BaseApi\Http; -use Nekland\BaseApi\Cache\CacheStrategyInterface; use Nekland\BaseApi\Http\Auth\AuthFactory; use Nekland\BaseApi\Http\Auth\AuthListener; -use Nekland\BaseApi\Http\Auth\AuthStrategyInterface; use Nekland\BaseApi\Http\ClientAdapter\GuzzleAdapter; -use Nekland\BaseApi\Http\Event\Events; use Symfony\Component\EventDispatcher\EventDispatcher; class HttpClientFactory { - /** - * @var AuthFactory - */ - private $authFactory; - /** * @var \Symfony\Component\EventDispatcher\EventDispatcher */ private $dispatcher; /** - * @var string + * @var array */ private $classes = [ - 'guzzle4' => 'Nekland\BaseApi\Api\Http\ClientAdapter\GuzzleAdapter' + 'guzzle4' => [ + 'class' => 'Nekland\BaseApi\Http\ClientAdapter\GuzzleAdapter', + 'requirement' => 'GuzzleHttp\Client' + ] ]; + /** + * @var array + */ private $options = []; public function __construct(array $options = [], EventDispatcher $eventDispatcher = null) { $this->options = array_merge_recursive($this->options, $options); $this->dispatcher = $eventDispatcher ?: new EventDispatcher(); - $this->authFactory = new AuthFactory(); } /** - * Generate an HttpClient object + * Generate the best http client according to the detected configuration * * @param string $name * @param null $client - * @return GuzzleAdapter|null + * @return GuzzleAdapter|AbstractHttpClient * @throws \InvalidArgumentException */ public function createHttpClient($name = '', $client = null) { if (empty($name)) { - return new GuzzleAdapter($this->options, $client); + return $this->createBestClient(); } if (!isset($this->classes[$name])) { throw new \InvalidArgumentException(sprintf('The client "%s" is not registered.', $name)); } - $class = $this->classes[$name]; - $client = new $class($this->options, $client); + $class = $this->classes[$name]['class']; + $client = new $class($this->dispatcher, $this->options, $client); - if ($client instanceof ClientInterface) { + if ($client instanceof AbstractHttpClient) { return $client; } @@ -76,55 +73,55 @@ public function createHttpClient($name = '', $client = null) } /** - * @param string $name - * @param string $class - * @throws \InvalidArgumentException + * @return AbstractHttpClient + * @throws \RuntimeException */ - public function register($name, $class) + public function createBestClient() { - if (class_exists($class)) { - $this->classes[$name] = $class; + foreach ($this->classes as $name => $definition) { + + if (is_callable($definition['requirement'])) { + if (!call_user_func($definition['requirement'])) { + continue; + } + } + if (!empty($definition['requirement'])) { + if (!class_exists($definition['requirement'])) { + continue; + } + } + $className = $definition['class']; + return new $className($this->dispatcher, $this->options); } - throw new \InvalidArgumentException(sprintf('%s is not a valid class name.', $name)); + throw new \RuntimeException('Impossible to find a Client class.'); } /** - * Allow the user to add an authentication to the request + * Register a new client that will be able to be use by the ApiFactory * - * @param string|AuthStrategyInterface $auth - * @param array $options + * @param string $name + * @param string $class + * @param string|callable $requirement + * @param bool $priority + * @throws \InvalidArgumentException */ - public function authenticate($auth, array $options = []) + public function register($name, $class, $requirement = '', $priority = false) { - if (!($auth instanceof AuthStrategyInterface)) { - $auth = $this->authFactory->get($auth); - $auth->setOptions($options); + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('%s is not a valid class name.', $class)); } - $this->dispatcher->addListener(Events::ON_REQUEST_EVENT, array( - new AuthListener($auth), - 'onRequestBeforeSend' - )); - } + $definition = [ + 'class' => $class, + 'requirement' => $requirement + ]; - /** - * @param CacheStrategyInterface $cacheStrategy - */ - public function useCache(CacheStrategyInterface $cacheStrategy) - { - $this->dispatcher->addListener( - Events::ON_REQUEST_EVENT, - [ $cacheStrategy, 'execute' ] - ); - } - - /** - * @return AuthFactory - */ - public function getAuthFactory() - { - return $this->authFactory; + if ($priority) { + $this->classes = array_merge_recursive([$name => $definition], $this->classes); + } else { + $this->classes[$name] = $definition; + } } /** diff --git a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php new file mode 100644 index 0000000..72a43c0 --- /dev/null +++ b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php @@ -0,0 +1,62 @@ +shouldHaveType('Nekland\BaseApi\Http\ClientAdapter\GuzzleAdapter'); + $this->shouldHaveType('Nekland\BaseApi\Http\AbstractHttpClient'); + } + + public function let(EventDispatcher $dispatcher, Client $guzzle) + { + $this->beConstructedWith($dispatcher, [], $guzzle); + } + + public function it_should_not_send_real_request_if_the_event_completed_request( + Request $request, + EventDispatcher $dispatcher, + RequestEvent $requestEvent, + Client $guzzle + ) { + $requestEvent->requestCompleted()->willReturn(true); + $requestEvent->getResponse()->willReturn('the response'); + $request->getMethod()->willReturn('get'); + $guzzle->get()->shouldNotBeCalled(); + + $dispatcher->dispatch(Argument::any(), Argument::type('Nekland\BaseApi\Http\Event\RequestEvent'))->willReturn($requestEvent); + + $this->send($request); + } + + public function it_should_send_real_request_when_event_request_not_completed( + Request $request, + Client $guzzle, + EventDispatcher $dispatcher, + RequestEvent $requestEvent, + MessageInterface $result + ) { + $guzzle->get('api.com', Argument::any())->shouldBeCalled(); + $guzzle->get('api.com', Argument::any())->willReturn($result); + + $requestEvent->requestCompleted()->willReturn(false); + $dispatcher->dispatch(Argument::any(), Argument::type('Nekland\BaseApi\Http\Event\RequestEvent'))->willReturn($requestEvent); + + $request->getMethod()->willReturn('get'); + $request->getPath()->willReturn('api.com'); + $request->getHeaders()->willReturn([]); + $request->getBody()->willReturn([]); + + $this->send($request); + } +} diff --git a/spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php b/spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php index 0e49099..21ace95 100644 --- a/spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php +++ b/spec/Nekland/BaseApi/Http/HttpClientFactorySpec.php @@ -2,6 +2,8 @@ namespace spec\Nekland\BaseApi\Http; +require __DIR__ . '/../../../fixture/MyHttpClient.php'; + use PhpSpec\ObjectBehavior; use Prophecy\Argument; @@ -11,4 +13,24 @@ function it_is_initializable() { $this->shouldHaveType('Nekland\BaseApi\Http\HttpClientFactory'); } + + public function it_should_return_a_guzzle_http_client_by_default() + { + $this->createHttpClient()->shouldHaveType('Nekland\BaseApi\Http\ClientAdapter\GuzzleAdapter'); + } + + public function it_should_be_able_to_create_user_http_client() + { + $this->register('MyHttpClient', 'spec\fixture\MyHttpClient'); + $this->createHttpClient('MyHttpClient')->shouldHaveType('spec\fixture\MyHttpClient'); + } + + public function it_should_not_register_class_that_does_not_exists() + { + + $this + ->shouldThrow('\InvalidArgumentException') + ->duringRegister('ClassThatDoesNotExists', 'this\namespace\does\not\Exists') + ; + } } diff --git a/spec/Nekland/BaseApi/Transformer/JsonTransformerSpec.php b/spec/Nekland/BaseApi/Transformer/JsonTransformerSpec.php new file mode 100644 index 0000000..cd8f31b --- /dev/null +++ b/spec/Nekland/BaseApi/Transformer/JsonTransformerSpec.php @@ -0,0 +1,21 @@ +shouldHaveType('Nekland\BaseApi\Transformer\JsonTransformer'); + $this->shouldHaveType('Nekland\BaseApi\Transformer\TransformerInterface'); + } + + public function it_should_transform_json_string_to_array() + { + $a = ['hello' => 'world']; + $this->transform('{"hello":"world"}')->shouldReturn($a); + } +} diff --git a/spec/fixture/MyHttpClient.php b/spec/fixture/MyHttpClient.php new file mode 100644 index 0000000..948cb9a --- /dev/null +++ b/spec/fixture/MyHttpClient.php @@ -0,0 +1,15 @@ + Date: Wed, 27 Aug 2014 16:11:28 +0200 Subject: [PATCH 09/34] Added api_classes doc --- doc/api_classes.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/api_classes.md b/doc/api_classes.md index e69de29..e36eb63 100644 --- a/doc/api_classes.md +++ b/doc/api_classes.md @@ -0,0 +1,10 @@ +Api classes +=========== + + +They should simply extends the `AbstractApi` class. + +She provide you useful methods (get/put/post/delete rest methods) and as the class do some job, you just have to create methods like: + +* getResourceById +* deleteResource From a6223d1abefcd45dce30036595204bc3ee9015a8 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sun, 31 Aug 2014 23:19:40 +0200 Subject: [PATCH 10/34] Added documentation --- Readme.md | 7 +------ composer.json | 19 +++++++++++++++---- doc/api_classes.md | 3 +++ doc/auth_and_cache.md | 21 +++++++++++++++++++++ doc/transformers.md | 8 ++++++++ lib/Nekland/BaseApi/ApiFactory.php | 5 ++--- 6 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 doc/auth_and_cache.md create mode 100644 doc/transformers.md diff --git a/Readme.md b/Readme.md index b191d68..10dc17e 100644 --- a/Readme.md +++ b/Readme.md @@ -20,9 +20,4 @@ Documentation This project does not need so much documentation but I wrote some for interested people. Checkout the [doc](doc) folder. -Inspiration ------------ - -This project look like php-github-api, and it's not innocent. I used it as model. - -Thanks to KnpLabs & Contributors on this project. +> This project is inspirated by KNPLabs api libs. diff --git a/composer.json b/composer.json index b6dce68..ebe782f 100644 --- a/composer.json +++ b/composer.json @@ -9,22 +9,33 @@ "name": "Maxime Veber", "email": "nekland@gmail.com", "homepage": "http://nekland.fr" + }, + { + "name": "Nekland Team", + "email": "nekland.fr@gmail.com", + "homepage": "http://team.nekland.fr" } ], "require": { "php": ">=5.4", "symfony/event-dispatcher": "~2.3" }, + "require-dev": { + "phpspec/phpspec": "dev-master", + "guzzlehttp/guzzle": "~4.2" + }, "suggest": { "nekland/youtube-api": "Youtube API made easy !", "nekland/soundcloud-api": "Soundcloud API made easy !", - "guzzle/guzzle": "The default HttpClient will not work without this library." + "guzzlehttp/guzzle": "The only http adapter available for now is for this library !" }, "autoload": { "psr-0": { "Nekland\\": "lib/" } }, - "require-dev": { - "phpspec/phpspec": "dev-master", - "guzzlehttp/guzzle": "~4.2" + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } } } diff --git a/doc/api_classes.md b/doc/api_classes.md index e36eb63..91ece8e 100644 --- a/doc/api_classes.md +++ b/doc/api_classes.md @@ -8,3 +8,6 @@ She provide you useful methods (get/put/post/delete rest methods) and as the cla * getResourceById * deleteResource + + +> You have to register a namespace to your api objects in the api factory diff --git a/doc/auth_and_cache.md b/doc/auth_and_cache.md new file mode 100644 index 0000000..433b458 --- /dev/null +++ b/doc/auth_and_cache.md @@ -0,0 +1,21 @@ +Auth & Cache strategies +======================= + +In order to help you manage authentication or cache systems, this lib provides you useful strategies managements. + +> Since theses classes are register as event listener (it use the event dispatcher of Symfony), you can add multiple strategies of each feature. + +The cache strategy +================== + +This are classes that implements the `CacheStrategyInterface`. + +You register them using the method `useCache` of the ApiFactory. + + +The authentication strategy +=========================== + +This are classes implementing the `AuthenticationStrategyInterface`. + +You register them using the method `useAuthentication` of the ApiFactory diff --git a/doc/transformers.md b/doc/transformers.md new file mode 100644 index 0000000..3c3d2b9 --- /dev/null +++ b/doc/transformers.md @@ -0,0 +1,8 @@ +Transformers +============ + +Transformers are classes implementing the `` interface. + +You can set them by using the `setTransformer` of the `ApiFactory` class. + +> You can only set one transformer because you can't retrieve the same data in many forms in the same request. diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index f21019d..674cb61 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -18,7 +18,6 @@ use Nekland\BaseApi\Http\Auth\AuthFactory; use Nekland\BaseApi\Http\Auth\AuthListener; use Nekland\BaseApi\Http\Auth\AuthStrategyInterface; -use Nekland\BaseApi\Http\ClientInterface; use Nekland\BaseApi\Http\Event\Events; use Nekland\BaseApi\Http\HttpClientFactory; use Nekland\BaseApi\Transformer\JsonTransformer; @@ -61,7 +60,7 @@ public function __construct( * @param string|AuthStrategyInterface $auth * @param array $options */ - public function authenticate($auth, array $options = []) + public function useAuthentication($auth, array $options = []) { if (!($auth instanceof AuthStrategyInterface)) { $auth = $this->getAuthFactory()->get($auth); @@ -86,7 +85,7 @@ public function useCache(CacheStrategyInterface $cacheStrategy) } /** - * @return ClientInterface + * @return \Nekland\BaseApi\Http\AbstractHttpClient */ public function getClient() { From b8b842edd6b547c59a5a5814f7428f006a40ca63 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Mon, 1 Sep 2014 00:02:53 +0200 Subject: [PATCH 11/34] Fixed test --- lib/Nekland/BaseApi/Http/Auth/AuthListener.php | 4 ++-- lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php | 5 +++-- .../Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php | 5 ++++- spec/fixture/MyHttpClient.php | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php b/lib/Nekland/BaseApi/Http/Auth/AuthListener.php index 3001da1..1815238 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthListener.php @@ -16,11 +16,11 @@ class AuthListener { /** - * @var AuthInterface + * @var AuthStrategyInterface */ private $auth; - public function __construct(AuthInterface $auth) + public function __construct(AuthStrategyInterface $auth) { $this->auth = $auth; } diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php b/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php index 38a88f3..e7874b4 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php @@ -11,6 +11,7 @@ namespace Nekland\BaseApi\Http\Auth; +use Nekland\BaseApi\Http\Request; interface AuthStrategyInterface { @@ -21,7 +22,7 @@ interface AuthStrategyInterface public function setOptions(array $options); /** - * @param \Guzzle\Http\Message\Request $request + * @param Request $request */ - public function auth(\Guzzle\Http\Message\Request $request); + public function auth(Request $request); } diff --git a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php index 72a43c0..8b38a28 100644 --- a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php +++ b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php @@ -34,7 +34,10 @@ public function it_should_not_send_real_request_if_the_event_completed_request( $request->getMethod()->willReturn('get'); $guzzle->get()->shouldNotBeCalled(); - $dispatcher->dispatch(Argument::any(), Argument::type('Nekland\BaseApi\Http\Event\RequestEvent'))->willReturn($requestEvent); + $dispatcher + ->dispatch(Argument::any(), Argument::type('Nekland\BaseApi\Http\Event\RequestEvent')) + ->willReturn($requestEvent) + ; $this->send($request); } diff --git a/spec/fixture/MyHttpClient.php b/spec/fixture/MyHttpClient.php index 948cb9a..9e23f4b 100644 --- a/spec/fixture/MyHttpClient.php +++ b/spec/fixture/MyHttpClient.php @@ -8,7 +8,7 @@ class MyHttpClient extends \Nekland\BaseApi\Http\AbstractHttpClient * @param \Nekland\BaseApi\Http\Request $request * @return string */ - public function send(\Nekland\BaseApi\Http\Request $request) + public function execute(\Nekland\BaseApi\Http\Request $request) { return ''; } From 12cb47a94e8de205c2993ebe412c0bbc293370f1 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 2 Sep 2014 20:32:45 +0200 Subject: [PATCH 12/34] Added hability to get the client from the event --- lib/Nekland/BaseApi/Http/AbstractHttpClient.php | 2 +- lib/Nekland/BaseApi/Http/Auth/AuthListener.php | 2 +- .../BaseApi/Http/Auth/AuthStrategyInterface.php | 6 +++--- lib/Nekland/BaseApi/Http/Event/RequestEvent.php | 17 ++++++++++++++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php index e090a46..c211204 100644 --- a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php +++ b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php @@ -55,7 +55,7 @@ public function send(Request $request) } /** @var RequestEvent $event */ - $event = $this->getEventDispatcher()->dispatch(Events::ON_REQUEST_EVENT, new RequestEvent($request)); + $event = $this->getEventDispatcher()->dispatch(Events::ON_REQUEST_EVENT, new RequestEvent($request, $this)); if (!$event->requestCompleted()) { $res = $this->execute($request); diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php b/lib/Nekland/BaseApi/Http/Auth/AuthListener.php index 1815238..5940e1e 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthListener.php @@ -31,6 +31,6 @@ public function onRequestBeforeSend(RequestEvent $event) return; } - $this->auth->auth($event->getRequest()); + $this->auth->auth($event); } } diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php b/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php index e7874b4..bbadc64 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthStrategyInterface.php @@ -11,7 +11,7 @@ namespace Nekland\BaseApi\Http\Auth; -use Nekland\BaseApi\Http\Request; +use Nekland\BaseApi\Http\Event\RequestEvent; interface AuthStrategyInterface { @@ -22,7 +22,7 @@ interface AuthStrategyInterface public function setOptions(array $options); /** - * @param Request $request + * @param RequestEvent $request */ - public function auth(Request $request); + public function auth(RequestEvent $request); } diff --git a/lib/Nekland/BaseApi/Http/Event/RequestEvent.php b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php index c37c6cd..da22beb 100644 --- a/lib/Nekland/BaseApi/Http/Event/RequestEvent.php +++ b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php @@ -12,6 +12,7 @@ namespace Nekland\BaseApi\Http\Event; +use Nekland\BaseApi\Http\AbstractHttpClient; use Nekland\BaseApi\Http\Request; use Symfony\Component\EventDispatcher\Event; @@ -27,9 +28,15 @@ class RequestEvent extends Event */ private $response; - public function __construct(Request $request) + /** + * @var \Nekland\BaseApi\Http\AbstractHttpClient + */ + private $client; + + public function __construct(Request $request, AbstractHttpClient $client) { $this->request = $request; + $this->client = $client; } /** @@ -68,4 +75,12 @@ public function getResponse() { return $this->response; } + + /** + * @return AbstractHttpClient + */ + public function getClient() + { + return $this->client; + } } From 761b2d214b51c0c9bf57768cced469437741f24a Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 2 Sep 2014 21:40:17 +0200 Subject: [PATCH 13/34] Fixed tests --- lib/Nekland/BaseApi/Http/AbstractHttpClient.php | 4 ++-- .../BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php index c211204..89303ee 100644 --- a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php +++ b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php @@ -28,7 +28,7 @@ abstract class AbstractHttpClient ]; /** - * @var \Symfony\Component\EventDispatcher\EventDispatcher + * @var EventDispatcher */ private $dispatcher; @@ -114,7 +114,7 @@ public static function createRequest($method, $path, array $parameters = [], arr /** * @return EventDispatcher */ - public function getEventDispatcher() + protected function getEventDispatcher() { return $this->dispatcher; } diff --git a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php index 8b38a28..61d69d7 100644 --- a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php +++ b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php @@ -43,9 +43,9 @@ public function it_should_not_send_real_request_if_the_event_completed_request( } public function it_should_send_real_request_when_event_request_not_completed( - Request $request, Client $guzzle, EventDispatcher $dispatcher, + Request $request, RequestEvent $requestEvent, MessageInterface $result ) { @@ -53,13 +53,19 @@ public function it_should_send_real_request_when_event_request_not_completed( $guzzle->get('api.com', Argument::any())->willReturn($result); $requestEvent->requestCompleted()->willReturn(false); - $dispatcher->dispatch(Argument::any(), Argument::type('Nekland\BaseApi\Http\Event\RequestEvent'))->willReturn($requestEvent); + $dispatcher + ->dispatch(Argument::any(), Argument::type('Nekland\BaseApi\Http\Event\RequestEvent')) + ->willReturn($requestEvent) + ; $request->getMethod()->willReturn('get'); $request->getPath()->willReturn('api.com'); $request->getHeaders()->willReturn([]); $request->getBody()->willReturn([]); + $requestEvent->setResponse(Argument::any())->shouldBeCalled(); + $requestEvent->getResponse()->shouldBeCalled(); + $this->send($request); } } From 6bd7bcabf406c2f7356f5596ce4e5f1b53988510 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 3 Sep 2014 21:19:43 +0200 Subject: [PATCH 14/34] Added tests --- lib/Nekland/BaseApi/ApiFactory.php | 13 ++++--- lib/Nekland/BaseApi/Http/Auth/AuthFactory.php | 12 +------ .../BaseApi/Http/Auth/AuthFactorySpec.php | 34 +++++++++++++++++++ spec/fixture/MyAuthStrategy.php | 26 ++++++++++++++ 4 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 spec/Nekland/BaseApi/Http/Auth/AuthFactorySpec.php create mode 100644 spec/fixture/MyAuthStrategy.php diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 674cb61..b9c3d5c 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -24,7 +24,7 @@ use Nekland\BaseApi\Transformer\TransformerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; -abstract class ApiFactory implements ApiInterface +abstract class ApiFactory { /** * @var HttpClientFactory @@ -67,10 +67,10 @@ public function useAuthentication($auth, array $options = []) $auth->setOptions($options); } - $this->dispatcher->addListener(Events::ON_REQUEST_EVENT, array( - new AuthListener($auth), - 'onRequestBeforeSend' - )); + $this->dispatcher->addListener(Events::ON_REQUEST_EVENT, [ + $auth, + 'auth' + ]); } /** @@ -111,6 +111,9 @@ public function setHttpClientFactory(HttpClientFactory $clientFactory) return $this; } + /** + * @param TransformerInterface $transformer + */ public function setTransformer(TransformerInterface $transformer) { $this->transformer = $transformer; diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php index 5bfe162..b7e8466 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php @@ -13,7 +13,6 @@ use Nekland\BaseApi\Http\AbstractHttpClient; -use Nekland\BaseApi\Http\HttpClientAware; class AuthFactory { @@ -27,15 +26,9 @@ class AuthFactory */ private $namespaces; - /** - * @var \Nekland\BaseApi\Http\AbstractHttpClient - */ - private $client; - - public function __construct(AbstractHttpClient $client) + public function __construct() { $this->authentications = []; - $this->client = $client; $this->namespaces = [ 'Nekland\\BaseApi\\Http\\Auth\\' ]; @@ -57,9 +50,6 @@ public function get($authName) if (class_exists($authClass)) { $auth = new $authClass(); - if ($auth instanceof HttpClientAware) { - $auth->setClient($this->client); - } return $this->authentications[$authName] = $auth; } } diff --git a/spec/Nekland/BaseApi/Http/Auth/AuthFactorySpec.php b/spec/Nekland/BaseApi/Http/Auth/AuthFactorySpec.php new file mode 100644 index 0000000..afca6ed --- /dev/null +++ b/spec/Nekland/BaseApi/Http/Auth/AuthFactorySpec.php @@ -0,0 +1,34 @@ +shouldHaveType('Nekland\BaseApi\Http\Auth\AuthFactory'); + } + + public function let() + { + $this->beConstructedWith(); + } + + public function it_should_return_an_auth_strategy_after_registered_namespace() + { + $this->addNamespace('spec\fixture'); + $this->get('MyAuthStrategy')->shouldHaveType('spec\fixture\MyAuthStrategy'); + } + + public function it_should_return_an_auth_strategy_after_added_it_to_classes() + { + $this->addAuth('my_auth', 'spec\fixture\MyAuthStrategy'); + $this->get('my_auth')->shouldHaveType('spec\fixture\MyAuthStrategy'); + } +} diff --git a/spec/fixture/MyAuthStrategy.php b/spec/fixture/MyAuthStrategy.php new file mode 100644 index 0000000..af8bdc8 --- /dev/null +++ b/spec/fixture/MyAuthStrategy.php @@ -0,0 +1,26 @@ + Date: Wed, 3 Sep 2014 21:24:05 +0200 Subject: [PATCH 15/34] Removed old and useless classes --- lib/Nekland/BaseApi/ApiInterface.php | 24 ------------- .../BaseApi/Http/Auth/AuthListener.php | 36 ------------------- 2 files changed, 60 deletions(-) delete mode 100644 lib/Nekland/BaseApi/ApiInterface.php delete mode 100644 lib/Nekland/BaseApi/Http/Auth/AuthListener.php diff --git a/lib/Nekland/BaseApi/ApiInterface.php b/lib/Nekland/BaseApi/ApiInterface.php deleted file mode 100644 index 2bc5916..0000000 --- a/lib/Nekland/BaseApi/ApiInterface.php +++ /dev/null @@ -1,24 +0,0 @@ - - * - * For the full license, take a look to the LICENSE file - * on the root directory of this project - */ - -namespace Nekland\BaseApi; - - -interface ApiInterface -{ - /** - * Return an api object - * - * @param string $name - * @return mixed - */ - public function api($name); -} \ No newline at end of file diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php b/lib/Nekland/BaseApi/Http/Auth/AuthListener.php deleted file mode 100644 index 5940e1e..0000000 --- a/lib/Nekland/BaseApi/Http/Auth/AuthListener.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full license, take a look to the LICENSE file - * on the root directory of this project - */ - -namespace Nekland\BaseApi\Http\Auth; - -use Nekland\BaseApi\Http\Event\RequestEvent; - -class AuthListener -{ - /** - * @var AuthStrategyInterface - */ - private $auth; - - public function __construct(AuthStrategyInterface $auth) - { - $this->auth = $auth; - } - - public function onRequestBeforeSend(RequestEvent $event) - { - if (null === $this->auth) { - return; - } - - $this->auth->auth($event); - } -} From b95dfc7ec62cc29c948943a3343af4c579e5d6ef Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 3 Sep 2014 23:59:00 +0200 Subject: [PATCH 16/34] Fixes --- composer.json | 4 +-- lib/Nekland/BaseApi/Api/AbstractApi.php | 8 ++--- lib/Nekland/BaseApi/Http/Request.php | 40 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index ebe782f..401d48f 100644 --- a/composer.json +++ b/composer.json @@ -7,12 +7,12 @@ "authors": [ { "name": "Maxime Veber", - "email": "nekland@gmail.com", + "email": "nek.dev@gmail.com", "homepage": "http://nekland.fr" }, { "name": "Nekland Team", - "email": "nekland.fr@gmail.com", + "email": "team@nekland.fr", "homepage": "http://team.nekland.fr" } ], diff --git a/lib/Nekland/BaseApi/Api/AbstractApi.php b/lib/Nekland/BaseApi/Api/AbstractApi.php index 21e8a7d..c68666a 100644 --- a/lib/Nekland/BaseApi/Api/AbstractApi.php +++ b/lib/Nekland/BaseApi/Api/AbstractApi.php @@ -56,7 +56,7 @@ public function setTransformer(TransformerInterface $transformer) */ protected function get($path, array $body = [], array $headers = []) { - $client = $this->getClient(); + $client = $this->getClient(); $request = $client::createRequest('GET', $path, $body, $headers); return $this->transformer->transform($client->send($request)); @@ -72,7 +72,7 @@ protected function get($path, array $body = [], array $headers = []) */ protected function put($path, array $body = [], array $headers = []) { - $client = $this->getClient(); + $client = $this->getClient(); $request = $client::createRequest('PUT', $path, $body, $headers); return $this->transformer->transform($client->send($request)); @@ -88,7 +88,7 @@ protected function put($path, array $body = [], array $headers = []) */ protected function post($path, array $body = [], array $headers = []) { - $client = $this->getClient(); + $client = $this->getClient(); $request = $client::createRequest('POST', $path, $body, $headers); return $this->transformer->transform($client->send($request)); @@ -104,7 +104,7 @@ protected function post($path, array $body = [], array $headers = []) */ protected function delete($path, array $body = [], array $headers = []) { - $client = $this->getClient(); + $client = $this->getClient(); $request = $client::createRequest('DELETE', $path, $body, $headers); return $this->transformer->transform($client->send($request)); diff --git a/lib/Nekland/BaseApi/Http/Request.php b/lib/Nekland/BaseApi/Http/Request.php index 71c29f7..6366e4c 100644 --- a/lib/Nekland/BaseApi/Http/Request.php +++ b/lib/Nekland/BaseApi/Http/Request.php @@ -79,4 +79,44 @@ public function getPath() { return $this->path; } + + /** + * @param array $body + * @return self + */ + public function setBody($body) + { + $this->body = $body; + return $this; + } + + /** + * @param array $headers + * @return self + */ + public function setHeaders($headers) + { + $this->headers = $headers; + return $this; + } + + /** + * @param string $method + * @return self + */ + public function setMethod($method) + { + $this->method = $method; + return $this; + } + + /** + * @param string $path + * @return self + */ + public function setPath($path) + { + $this->path = $path; + return $this; + } } From ae0af0b3bb4524ec6dcada5a0a5ef02bd3b665f4 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sat, 6 Sep 2014 09:34:22 +0200 Subject: [PATCH 17/34] Removed useless class --- lib/Nekland/BaseApi/ApiFactory.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index b9c3d5c..08fd76c 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -16,7 +16,6 @@ use Nekland\BaseApi\Exception\MissingApiException; use Nekland\BaseApi\Http\Auth\AuthFactory; -use Nekland\BaseApi\Http\Auth\AuthListener; use Nekland\BaseApi\Http\Auth\AuthStrategyInterface; use Nekland\BaseApi\Http\Event\Events; use Nekland\BaseApi\Http\HttpClientFactory; From 22a3d3198317603fb3581dc0c1624754e098626b Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sat, 6 Sep 2014 10:25:11 +0200 Subject: [PATCH 18/34] some fixes --- composer.json | 2 +- lib/Nekland/BaseApi/ApiFactory.php | 6 +++--- lib/Nekland/BaseApi/Http/AbstractHttpClient.php | 2 +- lib/Nekland/BaseApi/Http/Auth/AuthFactory.php | 3 +-- lib/Nekland/BaseApi/Http/HttpClientFactory.php | 3 ++- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 401d48f..5057cec 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,7 @@ "suggest": { "nekland/youtube-api": "Youtube API made easy !", "nekland/soundcloud-api": "Soundcloud API made easy !", - "guzzlehttp/guzzle": "The only http adapter available for now is for this library !" + "guzzlehttp/guzzle": "The only http adapter available for now is for this library." }, "autoload": { "psr-0": { "Nekland\\": "lib/" } diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 08fd76c..fe61b06 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -41,10 +41,10 @@ abstract class ApiFactory private $transformer; public function __construct( + HttpClientFactory $httpClientFactory = null, EventDispatcher $dispatcher = null, TransformerInterface $transformer = null, - AuthFactory $authFactory = null, - HttpClientFactory $httpClientFactory = null + AuthFactory $authFactory = null ) { $this->dispatcher = $dispatcher ?: new EventDispatcher(); $this->clientFactory = $httpClientFactory ?: new HttpClientFactory($this->dispatcher); @@ -151,7 +151,7 @@ public function __call($name, $parameters) */ protected function getAuthFactory() { - return $this->authFactory ?: new AuthFactory($this->getClient()); + return $this->authFactory ?: $this->authFactory = new AuthFactory($this->getClient()); } /** diff --git a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php index 89303ee..4ad0f0a 100644 --- a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php +++ b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php @@ -35,7 +35,7 @@ abstract class AbstractHttpClient public function __construct(EventDispatcher $eventDispatcher, array $options = []) { $this->dispatcher = $eventDispatcher; - $this->options = array_merge_recursive($this->options, $options); + $this->options = array_merge($this->options, $options); } /** diff --git a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php index b7e8466..9f1ea01 100644 --- a/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php +++ b/lib/Nekland/BaseApi/Http/Auth/AuthFactory.php @@ -11,7 +11,6 @@ namespace Nekland\BaseApi\Http\Auth; - use Nekland\BaseApi\Http\AbstractHttpClient; class AuthFactory @@ -30,7 +29,7 @@ public function __construct() { $this->authentications = []; $this->namespaces = [ - 'Nekland\\BaseApi\\Http\\Auth\\' + 'Nekland\\BaseApi\\Http\\Auth' ]; } diff --git a/lib/Nekland/BaseApi/Http/HttpClientFactory.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php index 87d5f50..5e8e521 100644 --- a/lib/Nekland/BaseApi/Http/HttpClientFactory.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -40,7 +40,7 @@ class HttpClientFactory public function __construct(array $options = [], EventDispatcher $eventDispatcher = null) { - $this->options = array_merge_recursive($this->options, $options); + $this->options = array_merge($this->options, $options); $this->dispatcher = $eventDispatcher ?: new EventDispatcher(); } @@ -91,6 +91,7 @@ public function createBestClient() } } $className = $definition['class']; + return new $className($this->dispatcher, $this->options); } From 3d6447f0bc218921a5d363a193ec8d3467754a6d Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sat, 6 Sep 2014 11:08:06 +0200 Subject: [PATCH 19/34] Added hasHeader to request --- CHANGELOG.md | 2 +- lib/Nekland/BaseApi/Http/Request.php | 9 +++++++++ spec/Nekland/BaseApi/Http/RequestSpec.php | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 119c5e3..6412e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -1.0.0 (2014-08-XX) +1.0.0 (2014-09-XX) ================== New awesome features diff --git a/lib/Nekland/BaseApi/Http/Request.php b/lib/Nekland/BaseApi/Http/Request.php index 6366e4c..b124dcb 100644 --- a/lib/Nekland/BaseApi/Http/Request.php +++ b/lib/Nekland/BaseApi/Http/Request.php @@ -56,6 +56,15 @@ public function getHeaders() return $this->headers; } + /** + * @param string $name + * @return bool + */ + public function hasHeader($name) + { + return isset($this->headers[$name]); + } + /** * @return string */ diff --git a/spec/Nekland/BaseApi/Http/RequestSpec.php b/spec/Nekland/BaseApi/Http/RequestSpec.php index b797013..73ad9ab 100644 --- a/spec/Nekland/BaseApi/Http/RequestSpec.php +++ b/spec/Nekland/BaseApi/Http/RequestSpec.php @@ -27,5 +27,8 @@ public function it_should_return_values_setted_in_constructor() $this->getBody()->shouldReturn($this->body); $this->getPath()->shouldReturn($this->path); $this->getMethod()->shouldReturn('get'); + + $this->hasHeader('User-Agent')->shouldReturn(true); + $this->hasHeader('Something-Else')->shouldReturn(false); } } From 7b5bc01200a4d0f50b3d1b478ff90bb518f00aef Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 10 Sep 2014 20:55:23 +0200 Subject: [PATCH 20/34] Added things to request object --- lib/Nekland/BaseApi/Http/Request.php | 74 ++++++++++++++++++++++- spec/Nekland/BaseApi/Http/RequestSpec.php | 26 ++++++-- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/Request.php b/lib/Nekland/BaseApi/Http/Request.php index b124dcb..c94aa09 100644 --- a/lib/Nekland/BaseApi/Http/Request.php +++ b/lib/Nekland/BaseApi/Http/Request.php @@ -25,6 +25,9 @@ class Request private $path; /** + * Body parameters + * used in PUT, POST and DELETE methods + * * @var array */ private $body; @@ -34,18 +37,31 @@ class Request */ private $headers; + /** + * URI parameters + * basically for GET requests + * + * @var array + */ + private $parameters; + /** * @param string $method * @param string $path - * @param array $body + * @param array $body if the method is GET it's taken as parameters * @param array $headers */ public function __construct($method, $path, array $body = [], array $headers = []) { $this->method = $method; $this->path = $path; - $this->body = $body; $this->headers = $headers; + + if ($method === 'GET') { + $this->parameters = $body; + } else { + $this->body = $body; + } } /** @@ -89,6 +105,24 @@ public function getPath() return $this->path; } + /** + * @return string + */ + public function getUrl() + { + $parameters = ''; + + if (!empty($this->parameters)) { + $parameters = false === strpos($this->path, '?') ? '?' : '&'; + + foreach ($this->parameters as $name => $value) { + $parameters .= $name . '=' . $value; + } + } + + return $this->path . $parameters; + } + /** * @param array $body * @return self @@ -128,4 +162,40 @@ public function setPath($path) $this->path = $path; return $this; } + + /** + * @param mixed[] $parameters + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * @param string $name + * @param mixed $parameter + * @return self + */ + public function setParameter($name, $parameter) + { + $this->parameters[$name] = $parameter; + return $this; + } + + /** + * @param string $name + * @return mixed + */ + public function getParameter($name) + { + return $this->parameters[$name]; + } } diff --git a/spec/Nekland/BaseApi/Http/RequestSpec.php b/spec/Nekland/BaseApi/Http/RequestSpec.php index 73ad9ab..2f83112 100644 --- a/spec/Nekland/BaseApi/Http/RequestSpec.php +++ b/spec/Nekland/BaseApi/Http/RequestSpec.php @@ -7,9 +7,9 @@ class RequestSpec extends ObjectBehavior { - private $path = 'http://site.com/api/v1/test'; - private $body = ['param1' => 'hello']; - private $headers = ['User-Agent' => 'phpspec']; + private $url = 'http://site.com/api/v1/test'; + private $body = ['foo' => 'bar']; + private $headers = ['User-Agent' => 'phpspec']; public function it_is_initializable() { @@ -18,17 +18,31 @@ public function it_is_initializable() public function let() { - $this->beConstructedWith('GET', $this->path, $this->body, $this->headers); + $this->beConstructedWith('GET', $this->url, $this->body, $this->headers); } public function it_should_return_values_setted_in_constructor() { $this->getHeaders()->shouldReturn($this->headers); - $this->getBody()->shouldReturn($this->body); - $this->getPath()->shouldReturn($this->path); + $this->getParameters()->shouldReturn($this->body); + $this->getPath()->shouldReturn($this->url); + $this->getUrl()->shouldReturn($this->url . '?foo=bar'); $this->getMethod()->shouldReturn('get'); + } + public function headers_should_be_editable() + { $this->hasHeader('User-Agent')->shouldReturn(true); $this->hasHeader('Something-Else')->shouldReturn(false); + $this->addHeader('Hello', 'Content'); + $this->hasHeader('Hello')->shouldReturn('true'); + } + + public function parameters_should_be_editable() + { + $this->hasParameter('foo')->shouldReturn(true); + $this->hasParameter('key')->shouldReturn(false); + $this->setParameter('key', 'some_key'); + $this->getParameter('key')->shouldReturn('some_key'); } } From 9b39c2a244a75569c3c936c6a4b9e34ccdbdaa18 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 10 Sep 2014 21:08:20 +0200 Subject: [PATCH 21/34] added check for api methods --- lib/Nekland/BaseApi/ApiFactory.php | 41 ++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index fe61b06..4ea47d3 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -120,30 +120,43 @@ public function setTransformer(TransformerInterface $transformer) /** * @param string $name - * @param array $parameters + * @param array $parameters * @return AbstractApi - * @throws \RuntimeException|MissingApiException + * @throws \RuntimeException|MissingApiException|\BadMethodCallException */ public function __call($name, $parameters) { - $apiName = str_replace(['get', 'Api'], '', str_replace('Api', '', $name)); + if ($this->isApiMethod($name)) { + $apiName = str_replace(['get', 'Api'], '', str_replace('Api', '', $name)); - foreach ($this->getApiNamespaces() as $namespace) { - $class = $namespace . '\\' . $apiName; - if (class_exists($class)) { - $api = new $class($this->getClient(), $this->getTransformer()); + foreach ($this->getApiNamespaces() as $namespace) { + $class = $namespace . '\\' . $apiName; + if (class_exists($class)) { + $api = new $class($this->getClient(), $this->getTransformer()); - if ($api instanceof AbstractApi) { - return $api; - } + if ($api instanceof AbstractApi) { + return $api; + } - throw new \RuntimeException( - sprintf('The API %s is found but does not implements AbstractApi.', $apiName) - ); + throw new \RuntimeException( + sprintf('The API %s is found but does not implements AbstractApi.', $apiName) + ); + } } + + throw new MissingApiException($apiName); } - throw new MissingApiException($apiName); + throw new \BadMethodCallException(sprintf('The method %s does not exists.', $name)); + } + + /** + * @param string $name + * @return bool + */ + protected function isApiMethod($name) + { + return (bool) preg_match('/^get[A-Z][a-zA-Z]*Api$/', $name); } /** From fc91f604d06c41b2322eb6c2a0f50a03cb1e09a7 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 10 Sep 2014 21:53:21 +0200 Subject: [PATCH 22/34] Fixes --- lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php | 2 +- lib/Nekland/BaseApi/Http/HttpClientFactory.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index f33f5e6..650c196 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -34,7 +34,7 @@ protected function execute(Request $request) { $method = $request->getMethod(); - return $this->guzzle->$method($this->getPath($request->getPath()), [ + return $this->guzzle->$method($this->getPath($request->getUrl()), [ 'headers' => $this->getHeaders($request->getHeaders()), 'body' => $request->getBody() ])->getBody(); diff --git a/lib/Nekland/BaseApi/Http/HttpClientFactory.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php index 5e8e521..3c84c3c 100644 --- a/lib/Nekland/BaseApi/Http/HttpClientFactory.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -19,7 +19,7 @@ class HttpClientFactory { /** - * @var \Symfony\Component\EventDispatcher\EventDispatcher + * @var EventDispatcher */ private $dispatcher; @@ -129,7 +129,7 @@ public function register($name, $class, $requirement = '', $priority = false) * @param string $name * @param mixed $value * - * @return HttpClient + * @return self */ public function setOption($name, $value) { From 5f9e0082a945b3df2309710d7ce205d74284cac6 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sat, 13 Sep 2014 08:37:27 +0200 Subject: [PATCH 23/34] Fixed dispatcher injection --- lib/Nekland/BaseApi/ApiFactory.php | 10 ++++++++-- .../BaseApi/Http/ClientAdapter/GuzzleAdapter.php | 2 ++ lib/Nekland/BaseApi/Http/HttpClientFactory.php | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 4ea47d3..3b5a096 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -46,8 +46,14 @@ public function __construct( TransformerInterface $transformer = null, AuthFactory $authFactory = null ) { - $this->dispatcher = $dispatcher ?: new EventDispatcher(); - $this->clientFactory = $httpClientFactory ?: new HttpClientFactory($this->dispatcher); + if ($httpClientFactory !== null) { + $this->clientFactory = $httpClientFactory; + $this->dispatcher = $dispatcher ?: $httpClientFactory->getEventDispatcher(); + } else { + $this->dispatcher = $dispatcher ?: new EventDispatcher(); + $this->clientFactory = new HttpClientFactory($this->dispatcher); + } + $this->authFactory = $authFactory; $this->transformer = $transformer; } diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 650c196..350e680 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -34,6 +34,8 @@ protected function execute(Request $request) { $method = $request->getMethod(); + var_dump($request->getUrl()); + return $this->guzzle->$method($this->getPath($request->getUrl()), [ 'headers' => $this->getHeaders($request->getHeaders()), 'body' => $request->getBody() diff --git a/lib/Nekland/BaseApi/Http/HttpClientFactory.php b/lib/Nekland/BaseApi/Http/HttpClientFactory.php index 3c84c3c..e434646 100644 --- a/lib/Nekland/BaseApi/Http/HttpClientFactory.php +++ b/lib/Nekland/BaseApi/Http/HttpClientFactory.php @@ -137,4 +137,12 @@ public function setOption($name, $value) return $this; } + + /** + * @return EventDispatcher + */ + public function getEventDispatcher() + { + return $this->dispatcher; + } } From 7c691a5c37f79a0724f4f117bc5fd96bfe019c24 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sat, 13 Sep 2014 08:56:08 +0200 Subject: [PATCH 24/34] Fixed mistakes in guzzle adapter --- .../BaseApi/Http/ClientAdapter/GuzzleAdapter.php | 4 +--- lib/Nekland/BaseApi/Http/Request.php | 13 ++++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 350e680..7efa220 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -34,9 +34,7 @@ protected function execute(Request $request) { $method = $request->getMethod(); - var_dump($request->getUrl()); - - return $this->guzzle->$method($this->getPath($request->getUrl()), [ + return (string) $this->guzzle->$method($this->getPath($request->getUrl()), [ 'headers' => $this->getHeaders($request->getHeaders()), 'body' => $request->getBody() ])->getBody(); diff --git a/lib/Nekland/BaseApi/Http/Request.php b/lib/Nekland/BaseApi/Http/Request.php index c94aa09..9dede14 100644 --- a/lib/Nekland/BaseApi/Http/Request.php +++ b/lib/Nekland/BaseApi/Http/Request.php @@ -110,17 +110,16 @@ public function getPath() */ public function getUrl() { - $parameters = ''; + $url = $this->path; if (!empty($this->parameters)) { - $parameters = false === strpos($this->path, '?') ? '?' : '&'; - foreach ($this->parameters as $name => $value) { - $parameters .= $name . '=' . $value; + $start = false === strpos($url, '?') ? '?' : '&'; + $url .= $start . $name . '=' . $value; } } - return $this->path . $parameters; + return $url; } /** @@ -181,7 +180,7 @@ public function getParameters() /** * @param string $name - * @param mixed $parameter + * @param string $parameter * @return self */ public function setParameter($name, $parameter) @@ -192,7 +191,7 @@ public function setParameter($name, $parameter) /** * @param string $name - * @return mixed + * @return string */ public function getParameter($name) { From da6b635261a90df0b99b5aaaa8afbde3c5d4469b Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sat, 13 Sep 2014 13:12:52 +0200 Subject: [PATCH 25/34] Added hability to not dispatch event on request and type attribute on request --- .../BaseApi/Http/AbstractHttpClient.php | 20 +++++++--- .../Http/ClientAdapter/GuzzleAdapter.php | 1 - lib/Nekland/BaseApi/Http/Request.php | 38 ++++++++++++++++++- .../Transformer/TransformerInterface.php | 2 +- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php index 4ad0f0a..58df069 100644 --- a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php +++ b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php @@ -40,10 +40,11 @@ public function __construct(EventDispatcher $eventDispatcher, array $options = [ /** * @param Request $request + * @param bool $withEvent if an event send a request, in order to avoid infinite while * @return string * @throws \BadMethodCallException */ - public function send(Request $request) + public function send(Request $request, $withEvent = true) { $method = $request->getMethod(); @@ -54,15 +55,19 @@ public function send(Request $request) )); } - /** @var RequestEvent $event */ - $event = $this->getEventDispatcher()->dispatch(Events::ON_REQUEST_EVENT, new RequestEvent($request, $this)); + $event = new RequestEvent($request, $this); + if ($withEvent) { + $event = $this->getEventDispatcher()->dispatch(Events::ON_REQUEST_EVENT, $event); + } if (!$event->requestCompleted()) { $res = $this->execute($request); $event->setResponse($res); } - $this->getEventDispatcher()->dispatch(Events::AFTER_REQUEST_EVENT, $event); + if ($withEvent) { + $this->getEventDispatcher()->dispatch(Events::AFTER_REQUEST_EVENT, $event); + } return $event->getResponse(); } @@ -94,7 +99,12 @@ protected function getHeaders(array $headers = []) */ protected function getPath($path) { - return $this->options['base_url'] . $path; + $hasHttp = strpos($path, 'http'); + if (false === $hasHttp || $hasHttp !== 0) { + return $this->options['base_url'] . $path; + } + + return $path; } /** diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 7efa220..0673b54 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -11,7 +11,6 @@ namespace Nekland\BaseApi\Http\ClientAdapter; - use GuzzleHttp\Client; use Nekland\BaseApi\Http\AbstractHttpClient; use Nekland\BaseApi\Http\Request; diff --git a/lib/Nekland/BaseApi/Http/Request.php b/lib/Nekland/BaseApi/Http/Request.php index 9dede14..7e7ba93 100644 --- a/lib/Nekland/BaseApi/Http/Request.php +++ b/lib/Nekland/BaseApi/Http/Request.php @@ -45,17 +45,26 @@ class Request */ private $parameters; + /** + * Type of request, nothing to do with http but useful for transformers + * + * @var null|string + */ + private $type; + /** * @param string $method * @param string $path * @param array $body if the method is GET it's taken as parameters * @param array $headers + * @param string $type */ - public function __construct($method, $path, array $body = [], array $headers = []) + public function __construct($method, $path, array $body = [], array $headers = [], $type = null) { $this->method = $method; $this->path = $path; $this->headers = $headers; + $this->type = $type; if ($method === 'GET') { $this->parameters = $body; @@ -142,6 +151,15 @@ public function setHeaders($headers) return $this; } + /** + * @param string $name + * @param string $content + */ + public function setHeader($name, $content) + { + $this->headers[$name] = $content; + } + /** * @param string $method * @return self @@ -197,4 +215,22 @@ public function getParameter($name) { return $this->parameters[$name]; } + + /** + * @param null|string $type + * @return self + */ + public function setType($type) + { + $this->type = $type; + return $this; + } + + /** + * @return null|string + */ + public function getType() + { + return $this->type; + } } diff --git a/lib/Nekland/BaseApi/Transformer/TransformerInterface.php b/lib/Nekland/BaseApi/Transformer/TransformerInterface.php index e103688..c1093fd 100644 --- a/lib/Nekland/BaseApi/Transformer/TransformerInterface.php +++ b/lib/Nekland/BaseApi/Transformer/TransformerInterface.php @@ -23,4 +23,4 @@ interface TransformerInterface * @return mixed */ public function transform($data, $type = self::UNKNOWN); -} \ No newline at end of file +} From f32f1bc80112b0c4ce56a167ec46b0ac626c0a64 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Sat, 13 Sep 2014 14:19:21 +0200 Subject: [PATCH 26/34] cs --- lib/Nekland/BaseApi/ApiFactory.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 3b5a096..686d83a 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -58,7 +58,6 @@ public function __construct( $this->transformer = $transformer; } - /** * Allow the user to add an authentication to the request * From c9212aa7e172c7704ae27cf2fdd307ac32ce40c6 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Mon, 15 Sep 2014 23:50:50 +0200 Subject: [PATCH 27/34] Fixed missing private arg --- lib/Nekland/BaseApi/ApiFactory.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 686d83a..805716c 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -40,6 +40,11 @@ abstract class ApiFactory */ private $transformer; + /** + * @var EventDispatcher + */ + private $dispatcher; + public function __construct( HttpClientFactory $httpClientFactory = null, EventDispatcher $dispatcher = null, From 60d9a30ce9c8f5bd716c9403728c4be81f0ab2c3 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Wed, 17 Sep 2014 00:26:10 +0200 Subject: [PATCH 28/34] Added Cache provider --- lib/Nekland/BaseApi/ApiFactory.php | 29 ++++-- lib/Nekland/BaseApi/Cache/CacheFactory.php | 90 +++++++++++++++++++ .../BaseApi/Cache/CacheStrategyInterface.php | 6 ++ .../Cache/Provider/CacheProviderInterface.php | 42 +++++++++ .../BaseApi/Cache/Provider/FileProvider.php | 70 +++++++++++++++ .../Cache/Provider/FileProviderSpec.php | 46 ++++++++++ .../Http/ClientAdapter/GuzzleAdapterSpec.php | 2 +- spec/fixture/cache_file | 1 + spec/fixture/cache_file_comp | 1 + 9 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 lib/Nekland/BaseApi/Cache/CacheFactory.php create mode 100644 lib/Nekland/BaseApi/Cache/Provider/CacheProviderInterface.php create mode 100644 lib/Nekland/BaseApi/Cache/Provider/FileProvider.php create mode 100644 spec/Nekland/BaseApi/Cache/Provider/FileProviderSpec.php create mode 100644 spec/fixture/cache_file create mode 100644 spec/fixture/cache_file_comp diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 805716c..84ecf57 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -12,6 +12,7 @@ namespace Nekland\BaseApi; use Nekland\BaseApi\Api\AbstractApi; +use Nekland\BaseApi\Cache\CacheFactory; use Nekland\BaseApi\Cache\CacheStrategyInterface; use Nekland\BaseApi\Exception\MissingApiException; @@ -45,11 +46,17 @@ abstract class ApiFactory */ private $dispatcher; + /** + * @var CacheFactory + */ + private $cacheFactory; + public function __construct( HttpClientFactory $httpClientFactory = null, EventDispatcher $dispatcher = null, TransformerInterface $transformer = null, - AuthFactory $authFactory = null + AuthFactory $authFactory = null, + CacheFactory $cacheFactory = null ) { if ($httpClientFactory !== null) { $this->clientFactory = $httpClientFactory; @@ -60,6 +67,7 @@ public function __construct( } $this->authFactory = $authFactory; + $this->cacheFactory = $cacheFactory ?: new CacheFactory(); $this->transformer = $transformer; } @@ -83,13 +91,16 @@ public function useAuthentication($auth, array $options = []) } /** - * @param CacheStrategyInterface $cacheStrategy + * @param CacheStrategyInterface|string $cacheStrategy + * @param \Nekland\BaseApi\Cache\Provider\CacheProviderInterface|string $cacheProvider + * @param array $options */ - public function useCache(CacheStrategyInterface $cacheStrategy) + public function useCache($cacheStrategy, $cacheProvider = null, array $options = null) { + $cache = $this->getCacheFactory()->createCacheStrategy($cacheStrategy, $cacheProvider, $options); $this->dispatcher->addListener( Events::ON_REQUEST_EVENT, - [ $cacheStrategy, 'execute' ] + [ $cache, 'execute' ] ); } @@ -172,11 +183,19 @@ protected function isApiMethod($name) /** * @return AuthFactory */ - protected function getAuthFactory() + public function getAuthFactory() { return $this->authFactory ?: $this->authFactory = new AuthFactory($this->getClient()); } + /** + * @return CacheFactory + */ + public function getCacheFactory() + { + return $this->cacheFactory; + } + /** * @return TransformerInterface */ diff --git a/lib/Nekland/BaseApi/Cache/CacheFactory.php b/lib/Nekland/BaseApi/Cache/CacheFactory.php new file mode 100644 index 0000000..749f715 --- /dev/null +++ b/lib/Nekland/BaseApi/Cache/CacheFactory.php @@ -0,0 +1,90 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Cache; + + +use Nekland\BaseApi\Cache\Provider\CacheProviderInterface; +use Nekland\BaseApi\Cache\Provider\FileProvider; + +class CacheFactory +{ + /** + * @var array + */ + private $namespaces; + + public function __construct($namespaces = null) + { + $this->namespaces = $namespaces ?: []; + } + + /** + * @param string[CacheStrategyInterface $cacheStrategy + * @param string[CacheProviderInterface $cacheProvider + * @param array $options + * @return CacheStrategyInterface + * @throws \RuntimeException + */ + public function createCacheStrategy($cacheStrategy, $cacheProvider = null, array $options = []) + { + $provider = $this->createProvider($cacheProvider, $options); + + foreach ($this->namespaces as $namespace) { + $class = $namespace . '\\' . $cacheStrategy; + if (class_exists($class)) { + $class = new $class(); + $class->setProvider($provider); + + return $class; + } + } + + throw new \RuntimeException(sprintf('Impossible to find a cache strategy named %s', $cacheStrategy)); + } + + /** + * @param string|CacheProviderInterface $provider + * @param array $options + * @return CacheProviderInterface + * @throws \RuntimeException + */ + public function createProvider($provider, array $options = null) + { + if ($provider === null) { + return (new FileProvider())->setOptions($options); + } + + if (is_object($provider)) { + if (!($provider instanceof CacheProviderInterface)) { + throw new \RuntimeException( + 'The cache provider must implements Nekland\BaseApi\Cache\Provider\CacheProviderInterface' + ); + } + $provider->setOptions($options); + + return $provider; + } + + foreach ($this->namespaces as $namespace) { + if ( + class_exists($class = $namespace . '\\' . $provider) || + class_exists($class = $namespace . '\\Provider\\' . $provider) + ) { + $provider = new $class; + + return $provider; + } + } + + throw new \RuntimeException(sprintf('Impossible to find the provider %s', $provider)); + } +} diff --git a/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php b/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php index 37271ee..455cc96 100644 --- a/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php +++ b/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php @@ -12,6 +12,7 @@ namespace Nekland\BaseApi\Cache; +use Nekland\BaseApi\Cache\Provider\CacheProviderInterface; use Nekland\BaseApi\Http\Event\RequestEvent; interface CacheStrategyInterface @@ -20,4 +21,9 @@ interface CacheStrategyInterface * @param RequestEvent $event */ public function execute(RequestEvent $event); + + /** + * @param CacheProviderInterface $provider + */ + public function setProvider(CacheProviderInterface $provider); } diff --git a/lib/Nekland/BaseApi/Cache/Provider/CacheProviderInterface.php b/lib/Nekland/BaseApi/Cache/Provider/CacheProviderInterface.php new file mode 100644 index 0000000..09f8e16 --- /dev/null +++ b/lib/Nekland/BaseApi/Cache/Provider/CacheProviderInterface.php @@ -0,0 +1,42 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Cache\Provider; + + +interface CacheProviderInterface +{ + /** + * Loads the cache + */ + public function load(); + + /** + * Saves the cache + */ + public function save(); + + /** + * @param string $key + */ + public function get($key); + + /** + * @param string $key + * @param mixed $value + */ + public function set($key, $value); + + /** + * @param array $options + */ + public function setOptions(array $options); +} diff --git a/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php b/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php new file mode 100644 index 0000000..8607877 --- /dev/null +++ b/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php @@ -0,0 +1,70 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Cache\Provider; + + +class FileProvider implements CacheProviderInterface +{ + /** + * @var array + */ + private $options; + + /** + * @var array + */ + private $cache; + + public function setOptions(array $options) + { + $this->options = array_merge($this->getOptions(), $options); + return $this; + } + + public function load() + { + $path = $this->getPath(); + if (!is_file($path)) { + @file_put_contents($path, serialize([])); + } + + $this->cache = unserialize(file_get_contents($this->getPath())); + } + + public function save() + { + file_put_contents($this->getPath(), serialize($this->cache)); + } + + public function get($key) + { + return $this->cache[$key]; + } + + public function set($key, $value) + { + $this->cache[$key] = $value; + return $this; + } + + protected function getPath() + { + return $this->options['path']; + } + + protected function getOptions() + { + return $this->options ?: [ + 'path' => sys_get_temp_dir() . '/nekland_api_cache_file' + ]; + } +} diff --git a/spec/Nekland/BaseApi/Cache/Provider/FileProviderSpec.php b/spec/Nekland/BaseApi/Cache/Provider/FileProviderSpec.php new file mode 100644 index 0000000..816dbec --- /dev/null +++ b/spec/Nekland/BaseApi/Cache/Provider/FileProviderSpec.php @@ -0,0 +1,46 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace spec\Nekland\BaseApi\Cache\Provider; + +use PhpSpec\ObjectBehavior; + +class FileProviderSpec extends ObjectBehavior +{ + public function it_is_initializable() + { + $this->shouldHaveType('Nekland\BaseApi\Cache\Provider\FileProvider'); + $this->shouldHaveType('Nekland\BaseApi\Cache\Provider\CacheProviderInterface'); + } + + public function it_should_return_an_array_stored_in_a_file() + { + $this->setOptions(['path' => __DIR__ . '/../../../../fixture/cache_file']); + $this->load(); + $this->get('something')->shouldReturn(['foz', 'baz']); + } + + public function it_should_save_the_cache() + { + $final = sys_get_temp_dir() . '/nekland_test_cache_file'; + $this->setOptions(['path' => __DIR__ . '/../../../../fixture/cache_file']); + $this->load(); + $this->set('element', ['foo' => 'bar']); + $this->setOptions(['path' => $final]); + $this->save(); + + if ( + trim(file_get_contents($final)) !== + trim(file_get_contents(__DIR__ . '/../../../../fixture/cache_file_comp')) + ) { + throw new \Exception('The cache file is not as expected'); + } + } +} diff --git a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php index 61d69d7..56961f4 100644 --- a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php +++ b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php @@ -59,7 +59,7 @@ public function it_should_send_real_request_when_event_request_not_completed( ; $request->getMethod()->willReturn('get'); - $request->getPath()->willReturn('api.com'); + $request->getUrl()->willReturn('api.com'); $request->getHeaders()->willReturn([]); $request->getBody()->willReturn([]); diff --git a/spec/fixture/cache_file b/spec/fixture/cache_file new file mode 100644 index 0000000..2173310 --- /dev/null +++ b/spec/fixture/cache_file @@ -0,0 +1 @@ +a:1:{s:9:"something";a:2:{i:0;s:3:"foz";i:1;s:3:"baz";}} diff --git a/spec/fixture/cache_file_comp b/spec/fixture/cache_file_comp new file mode 100644 index 0000000..826e73b --- /dev/null +++ b/spec/fixture/cache_file_comp @@ -0,0 +1 @@ +a:2:{s:9:"something";a:2:{i:0;s:3:"foz";i:1;s:3:"baz";}s:7:"element";a:1:{s:3:"foo";s:3:"bar";}} From 7a86fbf2274774504830e2fb0e519039d562ae4b Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Thu, 18 Sep 2014 00:39:37 +0200 Subject: [PATCH 29/34] Added Response object --- lib/Nekland/BaseApi/ApiFactory.php | 4 + .../BaseApi/Cache/CacheStrategyInterface.php | 5 ++ .../BaseApi/Cache/Provider/FileProvider.php | 9 +++ .../BaseApi/Http/AbstractHttpClient.php | 14 +++- .../Http/ClientAdapter/GuzzleAdapter.php | 6 +- .../BaseApi/Http/Event/RequestEvent.php | 14 ++-- lib/Nekland/BaseApi/Http/Request.php | 10 +++ lib/Nekland/BaseApi/Http/Response.php | 80 +++++++++++++++++++ 8 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 lib/Nekland/BaseApi/Http/Response.php diff --git a/lib/Nekland/BaseApi/ApiFactory.php b/lib/Nekland/BaseApi/ApiFactory.php index 84ecf57..7629845 100644 --- a/lib/Nekland/BaseApi/ApiFactory.php +++ b/lib/Nekland/BaseApi/ApiFactory.php @@ -102,6 +102,10 @@ public function useCache($cacheStrategy, $cacheProvider = null, array $options = Events::ON_REQUEST_EVENT, [ $cache, 'execute' ] ); + $this->dispatcher->addListener( + Events::AFTER_REQUEST_EVENT, + [ $cache, 'cache' ] + ); } /** diff --git a/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php b/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php index 455cc96..5b6d9a3 100644 --- a/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php +++ b/lib/Nekland/BaseApi/Cache/CacheStrategyInterface.php @@ -22,6 +22,11 @@ interface CacheStrategyInterface */ public function execute(RequestEvent $event); + /** + * @param RequestEvent $event + */ + public function cache(RequestEvent $event); + /** * @param CacheProviderInterface $provider */ diff --git a/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php b/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php index 8607877..77d008f 100644 --- a/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php +++ b/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php @@ -47,6 +47,10 @@ public function save() public function get($key) { + if (!isset($this->cache[$key])) { + return null; + } + return $this->cache[$key]; } @@ -67,4 +71,9 @@ protected function getOptions() 'path' => sys_get_temp_dir() . '/nekland_api_cache_file' ]; } + + public function __destruct() + { + $this->save(); + } } diff --git a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php index 58df069..e7accc5 100644 --- a/lib/Nekland/BaseApi/Http/AbstractHttpClient.php +++ b/lib/Nekland/BaseApi/Http/AbstractHttpClient.php @@ -69,14 +69,14 @@ public function send(Request $request, $withEvent = true) $this->getEventDispatcher()->dispatch(Events::AFTER_REQUEST_EVENT, $event); } - return $event->getResponse(); + return (string) $event->getResponse(); } /** * Execute a request * * @param Request $request - * @return string + * @return Response */ abstract protected function execute(Request $request); @@ -121,6 +121,16 @@ public static function createRequest($method, $path, array $parameters = [], arr return new Request($method, $path, $parameters, $headers); } + /** + * @param string $body + * @param array $headers + * @return Response + */ + public static function createResponse($body, array $headers) + { + return new Response($body, $headers); + } + /** * @return EventDispatcher */ diff --git a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php index 0673b54..9c5b2c6 100644 --- a/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php +++ b/lib/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapter.php @@ -33,9 +33,11 @@ protected function execute(Request $request) { $method = $request->getMethod(); - return (string) $this->guzzle->$method($this->getPath($request->getUrl()), [ + $response = $this->guzzle->$method($this->getPath($request->getUrl()), [ 'headers' => $this->getHeaders($request->getHeaders()), 'body' => $request->getBody() - ])->getBody(); + ]); + + return self::createResponse($response->getBody(), $response->getHeaders()); } } diff --git a/lib/Nekland/BaseApi/Http/Event/RequestEvent.php b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php index da22beb..5ec95db 100644 --- a/lib/Nekland/BaseApi/Http/Event/RequestEvent.php +++ b/lib/Nekland/BaseApi/Http/Event/RequestEvent.php @@ -14,6 +14,7 @@ use Nekland\BaseApi\Http\AbstractHttpClient; use Nekland\BaseApi\Http\Request; +use Nekland\BaseApi\Http\Response; use Symfony\Component\EventDispatcher\Event; class RequestEvent extends Event @@ -24,7 +25,7 @@ class RequestEvent extends Event private $request; /** - * @var string + * @var Response */ private $response; @@ -48,15 +49,10 @@ public function getRequest() } /** - * @param mixed $response - * @throws \InvalidArgumentException + * @param Response $response */ - public function setResponse($response) + public function setResponse(Response $response) { - if (!is_string($response)) { - throw new \InvalidArgumentException('The response should be send as string'); - } - $this->response = $response; } @@ -69,7 +65,7 @@ public function requestCompleted() } /** - * @return string + * @return Response */ public function getResponse() { diff --git a/lib/Nekland/BaseApi/Http/Request.php b/lib/Nekland/BaseApi/Http/Request.php index 7e7ba93..d7dbe97 100644 --- a/lib/Nekland/BaseApi/Http/Request.php +++ b/lib/Nekland/BaseApi/Http/Request.php @@ -226,6 +226,16 @@ public function setType($type) return $this; } + /** + * Return an id uniq for this request with identicals parameters + * + * @return string + */ + public function getId() + { + return base64_encode($this->path . implode('', $this->parameters) . implode('', $this->body) . $this->method); + } + /** * @return null|string */ diff --git a/lib/Nekland/BaseApi/Http/Response.php b/lib/Nekland/BaseApi/Http/Response.php new file mode 100644 index 0000000..7a73f19 --- /dev/null +++ b/lib/Nekland/BaseApi/Http/Response.php @@ -0,0 +1,80 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Http; + + +class Response +{ + /** + * @var string + */ + private $body; + + /** + * @var array + */ + private $headers; + + /** + * @param string $body + * @param array $headers + */ + public function __construct($body, array $headers = []) + { + $this->body = $body; + $this->headers = $headers; + } + + /** + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * @param string $body + * @return self + */ + public function setBody($body) + { + $this->body = $body; + return $this; + } + + /** + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * @param array $headers + * @return self + */ + public function setHeaders(array $headers) + { + $this->headers = $headers; + return $this; + } + + /** + * @return string + */ + public function __toString() + { + return $this->body; + } +} From a72db1d67a4c657b2762f77dc1a3b3be796facef Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 27 Jan 2015 15:11:29 +0100 Subject: [PATCH 30/34] Added missing option exception --- .../Exception/MissingOptionException.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/Nekland/BaseApi/Exception/MissingOptionException.php diff --git a/lib/Nekland/BaseApi/Exception/MissingOptionException.php b/lib/Nekland/BaseApi/Exception/MissingOptionException.php new file mode 100644 index 0000000..3d035f9 --- /dev/null +++ b/lib/Nekland/BaseApi/Exception/MissingOptionException.php @@ -0,0 +1,18 @@ + + * + * For the full license, take a look to the LICENSE file + * on the root directory of this project + */ + +namespace Nekland\BaseApi\Exception; + + +class MissingOptionException extends \Exception +{ + +} From 76e12f881800a41972a4d15101948cdc37c9bf19 Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 10 Mar 2015 00:19:56 +0100 Subject: [PATCH 31/34] Fixed response tostring method --- lib/Nekland/BaseApi/Http/Response.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Nekland/BaseApi/Http/Response.php b/lib/Nekland/BaseApi/Http/Response.php index 7a73f19..4898694 100644 --- a/lib/Nekland/BaseApi/Http/Response.php +++ b/lib/Nekland/BaseApi/Http/Response.php @@ -75,6 +75,6 @@ public function setHeaders(array $headers) */ public function __toString() { - return $this->body; + return (string) $this->body; } } From 3871f5b6ecf7152308352164cdce90d3eae2988b Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 10 Mar 2015 07:59:30 +0100 Subject: [PATCH 32/34] updated doc --- doc/transformers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/transformers.md b/doc/transformers.md index 3c3d2b9..812c46d 100644 --- a/doc/transformers.md +++ b/doc/transformers.md @@ -1,7 +1,7 @@ Transformers ============ -Transformers are classes implementing the `` interface. +Transformers are classes implementing the `TransformerInterface` interface. You can set them by using the `setTransformer` of the `ApiFactory` class. From 094c2afc204413b3e6976e097d90609f1332658b Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 10 Mar 2015 08:13:38 +0100 Subject: [PATCH 33/34] Added travis to readme --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index 10dc17e..a1bbb3e 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,8 @@ Nekland Base API ================ +[![Build Status](https://travis-ci.org/Nekland/BaseApi.svg)](https://travis-ci.org/Nekland/BaseApi) + Why --- From a8d8413126ff819215b1e6477625af6436ed30ca Mon Sep 17 00:00:00 2001 From: "Nek (Maxime Veber)" Date: Tue, 10 Mar 2015 10:13:06 +0100 Subject: [PATCH 34/34] Fixed tests --- lib/Nekland/BaseApi/Cache/Provider/FileProvider.php | 9 ++++++++- .../BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php | 5 ++++- spec/fixture/cache_file | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php b/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php index 77d008f..7031a1e 100644 --- a/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php +++ b/lib/Nekland/BaseApi/Cache/Provider/FileProvider.php @@ -27,6 +27,7 @@ class FileProvider implements CacheProviderInterface public function setOptions(array $options) { $this->options = array_merge($this->getOptions(), $options); + return $this; } @@ -57,9 +58,13 @@ public function get($key) public function set($key, $value) { $this->cache[$key] = $value; + return $this; } + /** + * @return string + */ protected function getPath() { return $this->options['path']; @@ -74,6 +79,8 @@ protected function getOptions() public function __destruct() { - $this->save(); + if (null !== $this->getPath()) { + $this->save(); + } } } diff --git a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php index 56961f4..1a6b558 100644 --- a/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php +++ b/spec/Nekland/BaseApi/Http/ClientAdapter/GuzzleAdapterSpec.php @@ -4,6 +4,7 @@ use GuzzleHttp\Client; use GuzzleHttp\Message\MessageInterface; +use GuzzleHttp\Message\ResponseInterface; use Nekland\BaseApi\Http\Event\RequestEvent; use Nekland\BaseApi\Http\Request; use PhpSpec\ObjectBehavior; @@ -47,10 +48,12 @@ public function it_should_send_real_request_when_event_request_not_completed( EventDispatcher $dispatcher, Request $request, RequestEvent $requestEvent, - MessageInterface $result + ResponseInterface $result ) { $guzzle->get('api.com', Argument::any())->shouldBeCalled(); $guzzle->get('api.com', Argument::any())->willReturn($result); + $result->getHeaders()->willReturn([]); + $result->getBody()->willReturn(''); $requestEvent->requestCompleted()->willReturn(false); $dispatcher diff --git a/spec/fixture/cache_file b/spec/fixture/cache_file index 2173310..f3dd466 100644 --- a/spec/fixture/cache_file +++ b/spec/fixture/cache_file @@ -1 +1 @@ -a:1:{s:9:"something";a:2:{i:0;s:3:"foz";i:1;s:3:"baz";}} +a:1:{s:9:"something";a:2:{i:0;s:3:"foz";i:1;s:3:"baz";}} \ No newline at end of file