Passed
Push — master ( 10118a...a1d344 )
by Jhao
03:15
created

ApiClient   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 157
Duplicated Lines 0 %

Test Coverage

Coverage 7.25%

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 62
c 8
b 0
f 0
dl 0
loc 157
ccs 5
cts 69
cp 0.0725
rs 10
wmc 22

12 Methods

Rating   Name   Duplication   Size   Complexity  
B send() 0 30 7
A post() 0 3 1
A put() 0 3 1
A delete() 0 3 1
A get() 0 3 1
A __construct() 0 5 1
A handleClientException() 0 11 2
A handleServerException() 0 3 1
A getResponseContent() 0 3 1
A handleAuthenticationException() 0 7 2
A buildHttpRequest() 0 23 3
A buildFile() 0 12 1
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 GuzzleHttp\ClientInterface;
20
use GuzzleHttp\Psr7\UploadedFile;
21
use Psr\Log\LoggerAwareInterface;
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 3
    public function __construct(Authentication $authentication, ClientInterface $httpClient, LoggerInterface $logger)
49
    {
50 3
        $this->authentication = $authentication;
51 3
        $this->httpClient = $httpClient;
52 3
        $this->logger = $logger;
53 3
    }
54
55
    public function get(string $path, ?Arrayable $request = null, $type = null)
56
    {
57
        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
    public function post(string $path, Arrayable $request, $type = null)
61
    {
62
        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
    public function put(string $path, Arrayable $request, $type = null)
66
    {
67
        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
    public function delete(string $path, Arrayable $request, $type = null)
71
    {
72
        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
    private function send(string $method, string $path, ?Arrayable $request = null, $responseType = null)
89
    {
90
        $method = \strtoupper($method);
91
92
        try {
93
            $response = $this->httpClient->send($this->buildHttpRequest($method, $path, $request));
94
95
            $contenType = $response->getHeaderLine('Content-Type');
96
97
            if (\preg_match('~^application/(pdf|zip)$~', $contenType, $matches)) {
98
                return $this->buildFile($response, $matches[1]);
99
            }
100
101
            if (\preg_match('~^application/json~', $contenType)) {
102
                $content = $this->getResponseContent($response);
103
104
                $this->logger->info('pochta.ru Dispatching response:.', $content);
105
106
                return $responseType === null
107
                    ? $content
108
                    : Instantiator::instantiate($responseType, $content);
109
            }
110
111
            throw new BadRequest();
112
        } catch (ClientException $e) {
113
            throw $this->handleClientException($e);
114
        } catch (ServerException $e) {
115
            throw $this->handleServerException($e);
116
        } catch (\Exception $e) {
117
            throw $e;
118
        }
119
    }
120
121
    private function buildHttpRequest(string $method, string $path, ?Arrayable $payload): RequestInterface
122
    {
123
        $request = $this->authentication->authenticate(
124
            new Request($method, self::API_URL.$path, ['Accept' => 'application/json;charset=UTF-8'])
125
        );
126
127
        if ($payload === null) {
128
            return $request;
129
        }
130
131
        $data = \array_filter($payload->toArray());
132
133
        $this->logger->info("pochta.ru Dispatching request: {$path}", $data);
134
135
        if ($method === 'GET') {
136
            $query = guzzle_build_query($data);
137
138
            return guzzle_modify_request($request, \compact('query'));
139
        }
140
141
        return $request
142
            ->withHeader('Content-Type', 'application/json;charset=UTF-8')
143
            ->withBody(guzzle_stream_for(guzzle_json_encode($data)));
144
    }
145
146
    private function buildFile(ResponseInterface $response, string $type): UploadedFile
147
    {
148
        \preg_match('~=(.+)$~', $response->getHeaderLine('Content-Disposition'), $matches);
149
150
        $this->logger->info("pochta.ru Dispatching response: file {$matches[1]}.{$type} ({$response->getBody()->getSize()} butes).");
151
152
        return new UploadedFile(
153
            $response->getBody(),
154
            $response->getBody()->getSize(),
155
            \UPLOAD_ERR_OK,
156
            "{$matches[1]}.{$type}",
157
            $response->getHeaderLine('Content-Type')
158
        );
159
    }
160
161
    private function handleClientException(ClientException $exception): DispatchingException
162
    {
163
        if (\in_array($exception->getCode(), [401, 403])) {
164
            throw $this->handleAuthenticationException($exception);
165
        }
166
167
        $content = $this->getResponseContent($exception->getResponse());
168
169
        return new BadRequest(
170
            $content['message'] ?? $content['error'] ?? $content['desc'],
171
            (int) ($content['status'] ?? $content['code'] ?? $exception->getCode())
172
        );
173
    }
174
175
    private function handleAuthenticationException(ClientException $exception)
176
    {
177
        $content = $this->getResponseContent($exception->getResponse());
178
179
        return new BadRequest(
180
            $content['message'] ?? $content['desc'] ?? '',
181
            $content['code'] ? (int) $content['code'] : $exception->getCode()
182
        );
183
    }
184
185
    private function handleServerException(ServerException $e): DispatchingException
186
    {
187
        throw $e;
188
    }
189
190
    private function getResponseContent(ResponseInterface $response): array
191
    {
192
        return guzzle_json_decode((string) $response->getBody(), true);
193
    }
194
}
195