Passed
Push — master ( 48b223...68e72c )
by Jhao
02:21
created

ApiClient::handleAuthenticationException()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
1
<?php
2
3
/**
4
 * This file is part of RussianPost SDK package.
5
 *
6
 * © Appwilio (http://appwilio.com), JhaoDa (https://github.com/jhaoda)
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Appwilio\RussianPostSDK\Dispatching\Http;
15
16
use GuzzleHttp\Psr7\Request;
17
use Psr\Log\LoggerInterface;
18
use Psr\Log\LoggerAwareTrait;
19
use Psr\Log\LoggerAwareInterface;
20
use GuzzleHttp\ClientInterface;
21
use GuzzleHttp\Psr7\UploadedFile;
22
use GuzzleHttp\Exception\ClientException;
23
use GuzzleHttp\Exception\ServerException;
24
use Psr\Http\Message\RequestInterface;
25
use Psr\Http\Message\ResponseInterface;
26
use Appwilio\RussianPostSDK\Dispatching\Instantiator;
27
use Appwilio\RussianPostSDK\Dispatching\Contracts\Arrayable;
28
use Appwilio\RussianPostSDK\Dispatching\Exceptions\BadRequest;
29
use Appwilio\RussianPostSDK\Dispatching\Contracts\DispatchingException;
30
use function GuzzleHttp\json_encode as guzzle_json_encode;
31
use function GuzzleHttp\json_decode as guzzle_json_decode;
32
use function GuzzleHttp\Psr7\stream_for as guzzle_stream_for;
33
use function GuzzleHttp\Psr7\build_query as guzzle_build_query;
34
use function GuzzleHttp\Psr7\modify_request as guzzle_modify_request;
35
36
final class ApiClient implements LoggerAwareInterface
37
{
38
    use LoggerAwareTrait;
39
40
    private const API_URL = 'https://otpravka-api.pochta.ru';
41
42
    /** @var Authentication */
43
    private $authentication;
44
45
    /** @var ClientInterface */
46
    private $httpClient;
47
48 28
    public function __construct(Authentication $authentication, ClientInterface $httpClient, LoggerInterface $logger)
49
    {
50 28
        $this->authentication = $authentication;
51 28
        $this->httpClient = $httpClient;
52 28
        $this->logger = $logger;
53 28
    }
54
55 22
    public function get(string $path, ?Arrayable $request = null, $type = null)
56
    {
57 22
        return $this->send('GET', ...\func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $path of Appwilio\RussianPostSDK\...\Http\ApiClient::send() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

57
        return $this->send('GET', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
58
    }
59
60 1
    public function post(string $path, Arrayable $request, $type = null)
61
    {
62 1
        return $this->send('POST', ...\func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $path of Appwilio\RussianPostSDK\...\Http\ApiClient::send() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

62
        return $this->send('POST', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
63
    }
64
65 1
    public function put(string $path, Arrayable $request, $type = null)
66
    {
67 1
        return $this->send('PUT', ...\func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $path of Appwilio\RussianPostSDK\...\Http\ApiClient::send() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
        return $this->send('PUT', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
68
    }
69
70 1
    public function delete(string $path, Arrayable $request, $type = null)
71
    {
72 1
        return $this->send('DELETE', ...\func_get_args());
0 ignored issues
show
Bug introduced by
func_get_args() is expanded, but the parameter $path of Appwilio\RussianPostSDK\...\Http\ApiClient::send() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

72
        return $this->send('DELETE', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
73
    }
74
75
    /**
76
     * Выполнение запроса.
77
     *
78
     * @param  string          $method
79
     * @param  string          $path
80
     * @param  Arrayable|null  $request
81
     * @param  mixed           $responseType
82
     *
83
     * @throws DispatchingException
84
     * @throws \GuzzleHttp\Exception\GuzzleException
85
     *
86
     * @return mixed
87
     */
88 25
    private function send(string $method, string $path, ?Arrayable $request = null, $responseType = null)
89
    {
90 25
        $method = \strtoupper($method);
91
92
        try {
93 25
            $response = $this->httpClient->send($this->buildHttpRequest($method, $path, $request));
94
95 6
            $contenType = $response->getHeaderLine('Content-Type');
96
97 6
            if (\preg_match('~^application/(pdf|zip)$~', $contenType, $matches)) {
98 1
                return $this->buildFile($response, $matches[1]);
99
            }
100
101 5
            if (\preg_match('~^application/json~', $contenType)) {
102 4
                $content = $this->getResponseContent($response);
103
104 4
                $this->logger->info('pochta.ru Dispatching response:.', $content);
105
106 4
                return $responseType === null
107 4
                    ? $content
108 4
                    : Instantiator::instantiate($responseType, $content);
109
            }
110
111 1
            throw new BadRequest();
112 20
        } catch (ClientException $e) {
113 18
            throw $this->handleClientException($e);
114 2
        } catch (ServerException $e) {
115 1
            throw $this->handleServerException($e);
116 1
        } catch (\Exception $e) {
117 1
            throw $e;
118
        }
119
    }
120
121 25
    private function buildHttpRequest(string $method, string $path, ?Arrayable $payload): RequestInterface
122
    {
123 25
        $request = $this->authentication->authenticate(
124 25
            new Request($method, self::API_URL.$path, ['Accept' => 'application/json;charset=UTF-8'])
125
        );
126
127 25
        if ($payload === null) {
128 22
            return $request;
129
        }
130
131 4
        $data = \array_filter($payload->toArray());
132
133 4
        $this->logger->info("pochta.ru Dispatching request: {$path}", $data);
134
135 4
        if ($method === 'GET') {
136 1
            return guzzle_modify_request($request, ['query' => guzzle_build_query($data)]);
137
        }
138
139
        return $request
140 3
            ->withHeader('Content-Type', 'application/json;charset=UTF-8')
141 3
            ->withBody(guzzle_stream_for(guzzle_json_encode($data)));
142
    }
143
144 1
    private function buildFile(ResponseInterface $response, string $type): UploadedFile
145
    {
146 1
        \preg_match('~=(.+)$~', $response->getHeaderLine('Content-Disposition'), $matches);
147
148 1
        $this->logger->info("pochta.ru Dispatching response: file {$matches[1]}.{$type} ({$response->getBody()->getSize()} butes).");
149
150 1
        return new UploadedFile(
151 1
            $response->getBody(),
152 1
            $response->getBody()->getSize(),
153 1
            \UPLOAD_ERR_OK,
154 1
            "{$matches[1]}.{$type}",
155 1
            $response->getHeaderLine('Content-Type')
156
        );
157
    }
158
159 18
    private function handleClientException(ClientException $exception): DispatchingException
160
    {
161 18
        if (\in_array($exception->getCode(), [401, 403])) {
162 12
            throw $this->handleAuthenticationException($exception);
163
        }
164
165 6
        $content = $this->getResponseContent($exception->getResponse());
0 ignored issues
show
Bug introduced by
It seems like $exception->getResponse() can also be of type null; however, parameter $response of Appwilio\RussianPostSDK\...t::getResponseContent() does only seem to accept Psr\Http\Message\ResponseInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

165
        $content = $this->getResponseContent(/** @scrutinizer ignore-type */ $exception->getResponse());
Loading history...
166
167 6
        return new BadRequest(
168 6
            $content['message'] ?? $content['error'] ?? $content['desc'],
169 6
            (int) ($content['status'] ?? $content['code'] ?? $exception->getCode())
170
        );
171
    }
172
173 12
    private function handleAuthenticationException(ClientException $exception)
174
    {
175 12
        $content = $this->getResponseContent($exception->getResponse());
0 ignored issues
show
Bug introduced by
It seems like $exception->getResponse() can also be of type null; however, parameter $response of Appwilio\RussianPostSDK\...t::getResponseContent() does only seem to accept Psr\Http\Message\ResponseInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

175
        $content = $this->getResponseContent(/** @scrutinizer ignore-type */ $exception->getResponse());
Loading history...
176
177 12
        return new BadRequest(
178 12
            $content['message'] ?? $content['desc'] ?? '',
179 12
            isset($content['code']) ? (int) $content['code'] : $exception->getCode()
180
        );
181
    }
182
183 1
    private function handleServerException(ServerException $e): DispatchingException
184
    {
185 1
        throw $e;
186
    }
187
188 22
    private function getResponseContent(ResponseInterface $response): array
189
    {
190 22
        return guzzle_json_decode((string) $response->getBody(), true);
191
    }
192
}
193