From b8b31884cd10f2bcfa0924943bd185ed9ee4bc7d Mon Sep 17 00:00:00 2001 From: Dmitry Gladyshev Date: Sun, 16 Jan 2022 11:02:23 +0300 Subject: [PATCH] Follow 307 redirect, psalm, code-style --- psalm.xml | 4 -- src/AbstractService.php | 20 ++++-- src/Appendix/RegionIDs.php | 44 +++++++++---- src/Credentials.php | 18 +---- src/Exception/ErrorResponse.php | 31 ++++----- src/Exception/MalformedResponse.php | 9 +-- src/Exception/TooManyJumpsException.php | 88 +++++++++++++++++++++++++ src/OAuth2Client.php | 7 +- src/RequestSenderTrait.php | 55 ++++++++++++++++ src/ServiceFactoryInterface.php | 4 +- src/ServiceInterface.php | 4 +- 11 files changed, 217 insertions(+), 67 deletions(-) create mode 100644 src/Exception/TooManyJumpsException.php create mode 100644 src/RequestSenderTrait.php diff --git a/psalm.xml b/psalm.xml index 879faa9..d5d44ee 100644 --- a/psalm.xml +++ b/psalm.xml @@ -9,10 +9,6 @@ > - - - - diff --git a/src/AbstractService.php b/src/AbstractService.php index 8df9301..5ba5091 100644 --- a/src/AbstractService.php +++ b/src/AbstractService.php @@ -5,10 +5,16 @@ namespace Promopult\TikTokMarketingApi; use GuzzleHttp\Psr7\Request; +use Promopult\TikTokMarketingApi\Exception\ErrorResponse; +use Promopult\TikTokMarketingApi\Exception\MalformedResponse; use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; abstract class AbstractService implements ServiceInterface { + use RequestSenderTrait; + protected CredentialsInterface $credentials; protected ClientInterface $httpClient; @@ -55,7 +61,7 @@ public function requestApi( $body ); - $response = $this->httpClient->sendRequest($request); + $response = $this->sendRequest($request); return $this->handleResponse($response, $request); } @@ -64,8 +70,8 @@ public function requestApi( * @throws \JsonException */ protected function handleResponse( - \Psr\Http\Message\ResponseInterface $response, - \Psr\Http\Message\RequestInterface $request + ResponseInterface $response, + RequestInterface $request ): array { /** @var array $decodedJson */ $decodedJson = \json_decode( @@ -76,14 +82,14 @@ protected function handleResponse( ); if (!isset($decodedJson['code'])) { - throw new \Promopult\TikTokMarketingApi\Exception\MalformedResponse( + throw new MalformedResponse( $request, $response ); } if ($decodedJson['code'] != 0) { - throw new \Promopult\TikTokMarketingApi\Exception\ErrorResponse( + throw new ErrorResponse( (int) $decodedJson['code'], (string) ($decodedJson['message'] ?? 'Unknown error.'), $request, @@ -98,7 +104,9 @@ protected function prepareGetParams(array $args): string { $formedArgs = []; - /** @var mixed $value */ + /** + * @psalm-suppress MixedAssignment + */ foreach ($args as $arg => $value) { if (is_scalar($value)) { $formedArgs[$arg] = $value; diff --git a/src/Appendix/RegionIDs.php b/src/Appendix/RegionIDs.php index 436b2e6..7eedceb 100644 --- a/src/Appendix/RegionIDs.php +++ b/src/Appendix/RegionIDs.php @@ -6,10 +6,10 @@ final class RegionIDs { - public const TYPE_DMA = "DMA"; - public const TYPE_CITIES = "cities"; - public const TYPE_COUNTRIES = "countries"; - public const TYPE_PROVINCES = "provinces"; + public const TYPE_DMA = 'DMA'; + public const TYPE_CITIES = 'cities'; + public const TYPE_COUNTRIES = 'countries'; + public const TYPE_PROVINCES = 'provinces'; public const JSON = [ 'DMA' => [ @@ -1480,16 +1480,12 @@ final class RegionIDs ]; /** - * Кешированный списко регионов. Ключ Id - * - * @var array + * @var array */ - private static $region_cache = []; + private static array $region_cache = []; /** - * Возвращает список всех регионов - * - * @return array + * @return array */ public static function getRegionsList(): array { @@ -1521,7 +1517,11 @@ public static function getRegionsByType(string $type): array $ids = array_column($items, 'id'); $items = array_combine($ids, $items); - return $items ?? []; + + return $items === false + ? [] + : $items + ; } /** @@ -1536,8 +1536,13 @@ public static function getRegionsByLevel(int $level): array if ($level < 1 || $level > 3) { return []; } + $list = self::getRegionsList(); + return array_filter($list, function ($region) use ($level) { + /** + * @psalm-suppress MixedArrayAccess + */ return ($region['level'] ?? null) == $level; }); } @@ -1553,6 +1558,9 @@ public static function getRegionsByParentId(int $parent_id): array { $list = self::getRegionsList(); return array_filter($list, function ($region) use ($parent_id) { + /** + * @psalm-suppress MixedArrayAccess + */ return ($region['parent_id'] ?? null) == $parent_id; }); } @@ -1562,11 +1570,12 @@ public static function getRegionsByParentId(int $parent_id): array * * @param int $id Id региона * - * @return array|null + * @return ?array */ public static function getRegionById(int $id): ?array { self::getRegionsList(); + return self::$region_cache[$id] ?? null; } @@ -1605,13 +1614,20 @@ public static function getRegionTree(int $id): array $region = self::getRegionById($id); $type = self::getRegionType($id); + if ($region === null || $type === null) { + throw new \InvalidArgumentException('Invalid region ID.'); + } + $tree = [$type => $region]; if (isset($region['parent_id'])) { + /** + * @psalm-suppress MixedArgument + */ $tree = array_merge($tree, self::getRegionTree($region['parent_id'])); } - uasort($tree, function ($a, $b) { + uasort($tree, function (array $a, array $b) { if ($a['level'] == $b['level']) { return 0; } diff --git a/src/Credentials.php b/src/Credentials.php index 0f36adf..c33cc80 100644 --- a/src/Credentials.php +++ b/src/Credentials.php @@ -6,16 +6,8 @@ final class Credentials implements CredentialsInterface { - /** - * @var string - */ - private $accessToken; - - /** - * @var string - */ - private $apiBaseUrl; - + private string $accessToken; + private string $apiBaseUrl; /** * Credentials constructor. @@ -47,17 +39,11 @@ public static function fromAccessTokenSandbox(string $accessToken): CredentialsI ); } - /** - * @return string - */ public function getAccessToken(): string { return $this->accessToken; } - /** - * @return string - */ public function getApiBaseUrl(): string { return $this->apiBaseUrl; diff --git a/src/Exception/ErrorResponse.php b/src/Exception/ErrorResponse.php index 00b6eef..4cb15ec 100644 --- a/src/Exception/ErrorResponse.php +++ b/src/Exception/ErrorResponse.php @@ -2,23 +2,20 @@ namespace Promopult\TikTokMarketingApi\Exception; +use GuzzleHttp\Psr7\Message; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + class ErrorResponse extends \RuntimeException { - /** - * @var \Psr\Http\Message\RequestInterface - */ - private $request; - - /** - * @var \Psr\Http\Message\ResponseInterface - */ - private $response; + private RequestInterface $request; + private ResponseInterface $response; public function __construct( int $code, string $message, - \Psr\Http\Message\RequestInterface $request, - \Psr\Http\Message\ResponseInterface $response, + RequestInterface $request, + ResponseInterface $response, \Throwable $previous = null ) { parent::__construct($message, $code, $previous); @@ -26,32 +23,32 @@ public function __construct( $this->response = $response; } - public function getRequest(): \Psr\Http\Message\RequestInterface + public function getRequest(): RequestInterface { return $this->request; } - public function getResponse(): \Psr\Http\Message\ResponseInterface + public function getResponse(): ResponseInterface { return $this->response; } public function getResponseAsString(): string { - return \GuzzleHttp\Psr7\Message::toString($this->response); + return Message::toString($this->response); } public function getRequestAsString(): string { - return \GuzzleHttp\Psr7\Message::toString($this->request); + return Message::toString($this->request); } public function __toString(): string { return parent::__toString() . PHP_EOL . 'Http log:' . PHP_EOL - . '>>>' . $this->getRequestAsString() . PHP_EOL - . '<<<' . $this->getResponseAsString() + . '>>>' . PHP_EOL . $this->getRequestAsString() . PHP_EOL + . '<<<' . PHP_EOL . $this->getResponseAsString() ; } } diff --git a/src/Exception/MalformedResponse.php b/src/Exception/MalformedResponse.php index e2ebdaf..d4a98f8 100644 --- a/src/Exception/MalformedResponse.php +++ b/src/Exception/MalformedResponse.php @@ -2,6 +2,7 @@ namespace Promopult\TikTokMarketingApi\Exception; +use GuzzleHttp\Psr7\Message; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -34,20 +35,20 @@ public function getResponse(): ResponseInterface public function getResponseAsString(): string { - return \GuzzleHttp\Psr7\Message::toString($this->response); + return Message::toString($this->response); } public function getRequestAsString(): string { - return \GuzzleHttp\Psr7\Message::toString($this->request); + return Message::toString($this->request); } public function __toString(): string { return parent::__toString() . PHP_EOL . 'Http log:' . PHP_EOL - . '>>>' . $this->getRequestAsString() . PHP_EOL - . '<<<' . $this->getResponseAsString() + . '>>>' . PHP_EOL . $this->getRequestAsString() . PHP_EOL + . '<<<' . PHP_EOL . $this->getResponseAsString() ; } } diff --git a/src/Exception/TooManyJumpsException.php b/src/Exception/TooManyJumpsException.php new file mode 100644 index 0000000..227ff36 --- /dev/null +++ b/src/Exception/TooManyJumpsException.php @@ -0,0 +1,88 @@ + $jumpResponses + * @param string $message + * @param int $code + * @param \Throwable|null $previous + */ + public function __construct( + RequestInterface $request, + array $jumpResponses, + string $message = 'Too many redirect jumps.', + int $code = 0, + \Throwable $previous = null + ) { + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->jumpResponses = $jumpResponses; + } + + /** + * @return RequestInterface + */ + public function getRequest(): RequestInterface + { + return $this->request; + } + + public function getRequestAsString(): string + { + return Message::toString($this->request); + } + + public function getResponseJumpsAsString(): string + { + $jumpsStr = ''; + + foreach ($this->jumpResponses as $response) { + $jumpsStr .= Message::toString($response) . PHP_EOL . '---' . PHP_EOL; + } + + return $jumpsStr; + } + + /** + * @return ResponseInterface[] + */ + public function getJumpResponses(): array + { + return $this->jumpResponses; + } + + public function getLastResponse(): ResponseInterface + { + return end($this->jumpResponses); + } + + public function getLastResponseAsString(): string + { + return Message::toString($this->getLastResponse()); + } + + public function __toString(): string + { + return parent::__toString() . PHP_EOL + . 'Http log:' . PHP_EOL + . '>>>' . PHP_EOL . $this->getRequestAsString() . PHP_EOL + . '<<<' . PHP_EOL . $this->getLastResponseAsString() + ; + } +} diff --git a/src/OAuth2Client.php b/src/OAuth2Client.php index 7988521..ada5956 100644 --- a/src/OAuth2Client.php +++ b/src/OAuth2Client.php @@ -10,6 +10,8 @@ final class OAuth2Client { + use RequestSenderTrait; + private ClientInterface $httpClient; /** @@ -57,7 +59,7 @@ public function advertiserGet( ] ); - $response = $this->httpClient->sendRequest($request); + $response = $this->sendRequest($request); /** @var array $parsedBody */ $parsedBody = json_decode( @@ -104,7 +106,7 @@ public function getAccessToken( ]) ); - $response = $this->httpClient->sendRequest($request); + $response = $this->sendRequest($request); /** @var array $accessToken */ $accessToken = json_decode($response->getBody()->getContents(), true, JSON_THROW_ON_ERROR); @@ -131,7 +133,6 @@ public function getAccessToken( * @return string * * @see https://ads.tiktok.com/marketing_api/docs?id=100648 - * @see */ public static function createAuthorizationUrl( string $appId, diff --git a/src/RequestSenderTrait.php b/src/RequestSenderTrait.php new file mode 100644 index 0000000..5e1d129 --- /dev/null +++ b/src/RequestSenderTrait.php @@ -0,0 +1,55 @@ +httpClient->sendRequest($request); + + $redirectCodes = [307]; + + if (in_array($response->getStatusCode(), $redirectCodes)) { + $responses[] = $response; + + if (count($responses) >= $maxRedirectJumps) { + throw new TooManyJumpsException($request, $responses); + } + + $uri = current($response->getHeader('Location')); + + /** + * @psalm-suppress UnusedVariable + */ + $request = $request->withUri(new Uri($uri)); + + goto jump; + } + + return $response; + } +} diff --git a/src/ServiceFactoryInterface.php b/src/ServiceFactoryInterface.php index bba1a49..70ba0ca 100644 --- a/src/ServiceFactoryInterface.php +++ b/src/ServiceFactoryInterface.php @@ -9,7 +9,7 @@ interface ServiceFactoryInterface /** * @param string $serviceName * - * @return \Promopult\TikTokMarketingApi\ServiceInterface + * @return ServiceInterface */ - public function createService(string $serviceName): \Promopult\TikTokMarketingApi\ServiceInterface; + public function createService(string $serviceName): ServiceInterface; } diff --git a/src/ServiceInterface.php b/src/ServiceInterface.php index 06091f7..ca07244 100644 --- a/src/ServiceInterface.php +++ b/src/ServiceInterface.php @@ -4,6 +4,8 @@ namespace Promopult\TikTokMarketingApi; +use Throwable; + interface ServiceInterface { /** @@ -13,7 +15,7 @@ interface ServiceInterface * * @return array * - * @throws \Throwable + * @throws Throwable */ public function requestApi(string $httpMethod, string $endpoint, array $args = []): array; }