From ee1d48989e8e3270c5f3f7b21f24ef3028e86d86 Mon Sep 17 00:00:00 2001 From: Manuel Reinhard Date: Thu, 21 Sep 2023 13:59:34 +0200 Subject: [PATCH] [wip] Refactor to use Guzzle and custom Response class --- composer.json | 2 +- lib/ApiClient/Http/Client.php | 83 ++++++++++++ lib/ApiClient/Http/HttpRequestException.php | 9 ++ lib/ApiClient/Http/Response.php | 36 +++++ lib/ApiClient/TicketparkApiClient.php | 143 ++++++++++---------- 5 files changed, 204 insertions(+), 69 deletions(-) create mode 100644 lib/ApiClient/Http/Client.php create mode 100644 lib/ApiClient/Http/HttpRequestException.php create mode 100644 lib/ApiClient/Http/Response.php diff --git a/composer.json b/composer.json index eb654b9..b71fcce 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "type": "library", "require": { "php": ">=8.1", - "kriswallsmith/buzz": "^0.15.0" + "guzzlehttp/guzzle": "^7.8" }, "license": "MIT", "authors": [ diff --git a/lib/ApiClient/Http/Client.php b/lib/ApiClient/Http/Client.php new file mode 100644 index 0000000..33a8158 --- /dev/null +++ b/lib/ApiClient/Http/Client.php @@ -0,0 +1,83 @@ +guzzle = new GuzzleClient(); + } + + public function head(string $url, array $headers): Response + { + return $this->execute('head', $url, $headers); + } + + public function get(string $url, array $headers): Response + { + return $this->execute('get', $url, $headers); + } + + public function post(string $url, string $content, array $headers): Response + { + return $this->execute('post', $url, $headers, $content); + } + + public function postForm(string $url, array $formData, array $headers): Response + { + return $this->execute('post', $url, $headers, null, $formData); + } + + public function patch(string $url, string $content, array $headers): Response + { + return $this->execute('patch', $url, $headers, $content); + } + + public function delete(string $url, array $headers): Response + { + return $this->execute('delete', $url, $headers); + } + + private function execute( + string $method, + string $url, + array $headers = [], + string $content = null, + array $formData = [] + ): Response { + try { + /** @var GuzzleResponse $response */ + $guzzleResponse = $this->guzzle->request( + $method, + $url, + [ + 'headers' => $headers, + 'body' => $content, + 'form_params' => $formData + ] + ); + } catch (\Exception $e) { + if (!$e instanceof ClientException) { + throw new HttpRequestException($e->getMessage()); + } + + /** @var GuzzleResponse $response */ + $guzzleResponse = $e->getResponse(); + } + + return new Response( + $guzzleResponse->getStatusCode(), + (string) $guzzleResponse->getBody(), + $guzzleResponse->getHeaders() + ); + } +} \ No newline at end of file diff --git a/lib/ApiClient/Http/HttpRequestException.php b/lib/ApiClient/Http/HttpRequestException.php new file mode 100644 index 0000000..f71c484 --- /dev/null +++ b/lib/ApiClient/Http/HttpRequestException.php @@ -0,0 +1,9 @@ +statusCode; + } + + public function getContent(): array + { + return json_decode($this->content, true); + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function isSuccessful(): bool + { + return ($this->statusCode >= 200 && $this->statusCode <= 204); + } +} \ No newline at end of file diff --git a/lib/ApiClient/TicketparkApiClient.php b/lib/ApiClient/TicketparkApiClient.php index 5435655..9e2cbc7 100644 --- a/lib/ApiClient/TicketparkApiClient.php +++ b/lib/ApiClient/TicketparkApiClient.php @@ -2,9 +2,8 @@ namespace Ticketpark\ApiClient; -use Buzz\Browser; -use Buzz\Client\Curl; -use Buzz\Message\Response; +use Ticketpark\ApiClient\Http\Client; +use Ticketpark\ApiClient\Http\Response; use Ticketpark\ApiClient\Exception\TokenGenerationException; use Ticketpark\ApiClient\Token\AccessToken; use Ticketpark\ApiClient\Token\RefreshToken; @@ -14,9 +13,9 @@ class TicketparkApiClient private const ROOT_URL = 'https://api.ticketpark.ch'; private const REFRESH_TOKEN_LIFETIME = 30 * 86400; + private ?Client $client = null; private ?string $username = null; private ?string $password = null; - private ?Browser $browser = null; private ?RefreshToken $refreshToken = null; private ?AccessToken $accessToken = null; @@ -26,20 +25,6 @@ public function __construct( ) { } - public function setBrowser(Browser $browser = null): void - { - $this->browser = $browser; - } - - public function getBrowser(): Browser - { - if (null === $this->browser) { - $this->browser = new Browser(new Curl()); - } - - return $this->browser; - } - public function setUserCredentials(string $username, string $password): void { $this->username = $username; @@ -51,11 +36,6 @@ public function getAccessToken(): ?AccessToken return $this->accessToken; } - public function setAccessTokenInstance(AccessToken $accessToken): void - { - $this->accessToken = $accessToken; - } - public function setAccessToken(string $accessToken): void { $this->accessToken = new AccessToken($accessToken); @@ -66,49 +46,51 @@ public function getRefreshToken(): ?RefreshToken return $this->refreshToken; } - public function setRefreshTokenInstance(RefreshToken $refreshToken): void - { - $this->refreshToken = $refreshToken; - } - public function setRefreshToken(string $refreshToken): void { $this->refreshToken = new RefreshToken($refreshToken); } - public function get(string $path, array $parameters = [], array $headers = []): Response + public function head(string $path, array $parameters = []): Response { - $params = ''; - if (count($parameters)) { - $params = '?' . http_build_query($parameters); - } - - return $this->getBrowser()->get(self::ROOT_URL . $path . $params, $this->getDefaultHeaders($headers)); + return $this->getClient()->head( + $this->getUrl($path, $parameters), + $this->getHeaders() + ); } - public function post(string $path, mixed $content = '', array $headers = []): Response + public function get(string $path, array $parameters = []): Response { - return $this->getBrowser()->post(self::ROOT_URL . $path, $this->getDefaultHeaders($headers), json_encode($content, JSON_THROW_ON_ERROR)); + return $this->getClient()->get( + $this->getUrl($path, $parameters), + $this->getHeaders() + ); } - public function head($path, array $parameters = [], array $headers = []): Response + public function post(string $path, array $data = []): Response { - $params = ''; - if (count($parameters)) { - $params = '?' . http_build_query($parameters); - } - - return $this->getBrowser()->head(self::ROOT_URL . $path . $params, $this->getDefaultHeaders($headers)); + return $this->getClient()->post( + $this->getUrl($path), + json_encode($data, JSON_THROW_ON_ERROR), + $this->getHeaders() + ); } - public function patch(string $path, mixed $content = '', array $headers = []): Response + public function patch(string $path, array $data = []): Response { - return $this->getBrowser()->patch(self::ROOT_URL . $path, $this->getDefaultHeaders($headers), json_encode($content, JSON_THROW_ON_ERROR)); + return $this->getClient()->patch( + $this->getUrl($path), + json_encode($data, JSON_THROW_ON_ERROR), + $this->getHeaders() + ); } - public function delete(string $path, array $headers = []): Response + public function delete(string $path): Response { - return $this->getBrowser()->delete(self::ROOT_URL . $path, $this->getDefaultHeaders($headers)); + return $this->getClient()->delete( + $this->getUrl($path), + $this->getHeaders() + ); } public function generateTokens(): void @@ -127,7 +109,7 @@ public function generateTokens(): void } // Try with user credentials - if (!isset($data) && $this->username) { + if ($this->username) { $data = [ 'username' => $this->username, 'password' => $this->password, @@ -142,18 +124,30 @@ public function generateTokens(): void throw new TokenGenerationException('Failed to generate a access tokens. Make sure to provide a valid refresh token or user credentials.'); } - private function getDefaultHeaders(array $customHeaders = []): array + private function getClient(): Client { - $headers = ['Content-Type' => 'application/json', 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $this->getValidAccessToken()]; + if (null === $this->client) { + $this->client = new Client(); + } - return array_merge($customHeaders, $headers); + return $this->client; + } + + private function getUrl(string $path, array $parameters = []): string + { + $params = ''; + if (count($parameters)) { + $params = '?' . http_build_query($parameters); + } + + return self::ROOT_URL . $path . $params; } private function getValidAccessToken(): string { $accessToken = $this->getAccessToken(); - if (!$accessToken || $accessToken->hasExpired()) { + if (null === $accessToken || $accessToken->hasExpired()) { $this->generateTokens(); $accessToken = $this->getAccessToken(); } @@ -161,7 +155,7 @@ private function getValidAccessToken(): string return $accessToken->getToken(); } - protected function doGenerateTokens(array $data): bool + private function doGenerateTokens(array $data): bool { $headers = [ 'Content-Type' => 'application/x-www-form-urlencoded', @@ -169,24 +163,37 @@ protected function doGenerateTokens(array $data): bool 'Authorization' => 'Basic '.base64_encode($this->apiKey . ':' . $this->apiSecret) ]; - $response = $this->getBrowser()->post(self::ROOT_URL . '/oauth/v2/token', $headers, $data); + $response = $this->getClient()->postForm( + $this->getUrl('/oauth/v2/token'), + $data, + $headers, + ); - if (200 == $response->getStatusCode()) { - $response = json_decode((string) $response->getContent(), true, 512, JSON_THROW_ON_ERROR); + if (!$response->isSuccessful()) { + return false; + } - $this->accessToken = new AccessToken( - $response['access_token'], - (new \DateTime())->setTimestamp(time() + $response['expires_in']) - ); + $content = $response->getContent(); - $this->refreshToken = new RefreshToken( - $response['refresh_token'], - (new \DateTime())->setTimestamp(time() + self::REFRESH_TOKEN_LIFETIME) - ); + $this->accessToken = new AccessToken( + $content['access_token'], + (new \DateTime())->setTimestamp(time() + $content['expires_in']) + ); - return true; - } + $this->refreshToken = new RefreshToken( + $content['refresh_token'], + (new \DateTime())->setTimestamp(time() + self::REFRESH_TOKEN_LIFETIME) + ); - return false; + return true; + } + + private function getHeaders(): array + { + return [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $this->getValidAccessToken() + ]; } }