diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7c1677c..74a4ba9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ jobs: - uses: docker://composer with: args: install - - uses: docker://php:7.3-cli-alpine + - uses: docker://php:7.4-cli-alpine with: entrypoint: vendor/bin/php-cs-fixer args: fix -v --dry-run @@ -23,7 +23,7 @@ jobs: - uses: docker://composer with: args: install - - uses: docker://php:7.3-cli-alpine + - uses: docker://php:7.4-cli-alpine with: entrypoint: vendor/bin/phpstan args: analyse src -c phpstan.neon -l max @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - version: ['7.2', '7.3'] + version: ['7.3', '7.4'] steps: - uses: actions/checkout@master - uses: baptouuuu/setup-php@1.0.2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e54f9c3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ -language: php - -php: - - 7.2 - - 7.3 - - 7.4snapshot - - nightly - -matrix: - fast_finish: true - include: - - php: 7.2 - env: COMPOSER_FLAGS="--prefer-lowest" - - php: 7.2 - env: COMPOSER_FLAGS="--prefer-stable" - allow_failures: - - php: 7.4snapshot - - php: nightly - -install: - - composer update --prefer-dist --no-interaction $COMPOSER_FLAGS - -script: - - vendor/bin/phpunit --coverage-clover=coverage.clover - - vendor/bin/phpstan analyse src -c phpstan.neon -l max - -after_script: - - if [ "$TRAVIS_PHP_VERSION" != "nightly" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi - - if [ "$TRAVIS_PHP_VERSION" != "nightly" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi diff --git a/src/Client.php b/src/Client.php index 163be98..f4eafc6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,122 +10,76 @@ Exception\BadRequest, Exception\Forbidden, Exception\NotFound, - Exception\Unauthorized + Exception\Unauthorized, }; use Psr\Http\Message\ResponseInterface; -use RuntimeException; class Client { private $http; - private $baseUri; - private $apiKey; - public function __construct(HttpMethodsClient $http, string $baseUri, string $apiKey) + public function __construct(HttpMethodsClient $http) { - if (false === filter_var($baseUri, FILTER_VALIDATE_URL)) { - throw new RuntimeException('Invalid Clockify endpoint.'); - } - $this->http = $http; - $this->baseUri = $baseUri; - $this->apiKey = $apiKey; } public function get(string $uri, array $params = []): array { - $response = $this->http->get( - $this->endpoint($uri, $params), - ['X-Api-Key' => $this->apiKey] - ); + if (!empty($params)) { + $uri .= '?'.http_build_query($params); + } + + $response = $this->http->get($uri); if (200 !== $response->getStatusCode()) { throw $this->createExceptionFromResponse($response); } - return json_decode($response->getBody()->getContents(), true); + return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); } public function post(string $uri, array $data): array { - $response = $this->http->post( - $this->endpoint($uri), - [ - 'Content-Type' => 'application/json', - 'X-Api-Key' => $this->apiKey, - ], - json_encode($data) - ); + $response = $this->http->post($uri, [], json_encode($data, JSON_THROW_ON_ERROR)); if (201 !== $response->getStatusCode()) { throw $this->createExceptionFromResponse($response); } - return json_decode($response->getBody()->getContents(), true); + return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); } public function put(string $uri, array $data): array { - $response = $this->http->put( - $this->endpoint($uri), - [ - 'Content-Type' => 'application/json', - 'X-Api-Key' => $this->apiKey, - ], - json_encode($data) - ); + $response = $this->http->put($uri, [], json_encode($data, JSON_THROW_ON_ERROR)); if (!in_array($response->getStatusCode(), [200, 201], true)) { throw $this->createExceptionFromResponse($response); } - return json_decode($response->getBody()->getContents(), true); + return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); } public function patch(string $uri, array $data): array { - $response = $this->http->patch( - $this->endpoint($uri), - [ - 'Content-Type' => 'application/json', - 'X-Api-Key' => $this->apiKey, - ], - json_encode($data) - ); + $response = $this->http->patch($uri, [], json_encode($data, JSON_THROW_ON_ERROR)); if (!in_array($response->getStatusCode(), [200, 204], true)) { throw $this->createExceptionFromResponse($response); } - return json_decode($response->getBody()->getContents(), true); + return json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); } public function delete(string $uri): void { - $response = $this->http->delete( - $this->endpoint($uri) - ); + $response = $this->http->delete($uri); if (204 !== $response->getStatusCode()) { throw $this->createExceptionFromResponse($response); } } - private function endpoint(string $uri, array $params = []): string - { - $endpoint = sprintf( - '%s/%s', - rtrim($this->baseUri, '/'), - ltrim($uri, '/') - ); - - if (!empty($params)) { - $endpoint .= http_build_query($params); - } - - return $endpoint; - } - private function createExceptionFromResponse(ResponseInterface $response): ClockifyException { $data = json_decode((string) $response->getBody(), true); diff --git a/src/ClientBuilder.php b/src/ClientBuilder.php index 299f7d4..0bba80d 100644 --- a/src/ClientBuilder.php +++ b/src/ClientBuilder.php @@ -6,14 +6,23 @@ use Http\Client\{ Common\HttpMethodsClient, + Common\Plugin\AddHostPlugin, + Common\Plugin\AddPathPlugin, + Common\Plugin\AuthenticationPlugin, + Common\Plugin\HeaderSetPlugin, + Common\PluginClient, HttpClient, }; -use Http\Message\MessageFactory; -use Psr\Http\Message\RequestFactoryInterface; +use Http\Message\Authentication\Header; +use Psr\Http\Message\{ + RequestFactoryInterface, + UriFactoryInterface, +}; use Http\Discovery\{ Psr17FactoryDiscovery, Psr18ClientDiscovery, }; +use RuntimeException; class ClientBuilder { @@ -21,11 +30,16 @@ class ClientBuilder private $httpClient; private $requestFactory; + private $uriFactory; - public function __construct(?HttpClient $httpClient = null, ?RequestFactoryInterface $requestFactory = null) - { + public function __construct( + ?HttpClient $httpClient = null, + ?RequestFactoryInterface $requestFactory = null, + ?UriFactoryInterface $uriFactory = null + ) { $this->httpClient = $httpClient ?? Psr18ClientDiscovery::find(); $this->requestFactory = $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(); + $this->uriFactory = $uriFactory ?? Psr17FactoryDiscovery::findUriFactory(); } public function createClientV1(string $apiKey): Client @@ -35,8 +49,35 @@ public function createClientV1(string $apiKey): Client public function create(string $endpoint, string $apiKey): Client { - $http = new HttpMethodsClient($this->httpClient, $this->requestFactory); + if (false === filter_var($endpoint, FILTER_VALIDATE_URL)) { + throw new RuntimeException('Invalid Clockify endpoint.'); + } + + if ('' === trim($apiKey)) { + throw new RuntimeException('API token is required.'); + } + + $plugins = [ + new AuthenticationPlugin( + new Header('X-Api-Key', $apiKey), + ), + new AddHostPlugin( + $this->uriFactory->createUri($endpoint), + ), + new AddPathPlugin( + $this->uriFactory->createUri($endpoint), + ), + new HeaderSetPlugin([ + 'User-Agent' => 'github.com/jdecool/clockify-api', + 'Content-Type' => 'application/json', + ]), + ]; + + $http = new HttpMethodsClient( + new PluginClient($this->httpClient, $plugins), + $this->requestFactory, + ); - return new Client($http, $endpoint, $apiKey); + return new Client($http); } }