Completed
Push — master ( 37d52e...ca4941 )
by Jhao
11:04
created

ApiClient   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 27
eloc 74
c 2
b 0
f 0
dl 0
loc 189
rs 10

14 Methods

Rating   Name   Duplication   Size   Complexity  
A getHttpClient() 0 13 2
A guessFileType() 0 13 3
A buildRequestOptions() 0 14 2
B send() 0 37 9
A handleClientException() 0 7 1
A post() 0 3 1
A put() 0 3 1
A __construct() 0 4 1
A handleServerException() 0 3 1
A delete() 0 3 1
A getResponseContent() 0 3 1
A handleAuthenticationException() 0 7 2
A buildFile() 0 10 1
A get() 0 3 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 Appwilio\RussianPostSDK\Dispatching\Instantiator;
17
use Appwilio\RussianPostSDK\Dispatching\Contracts\Arrayable;
18
use Appwilio\RussianPostSDK\Dispatching\Exceptions\BadRequest;
19
use Appwilio\RussianPostSDK\Dispatching\Contracts\DispatchingException;
20
use GuzzleHttp\Client as HttpClient;
21
use GuzzleHttp\Exception\ClientException;
22
use GuzzleHttp\Exception\ServerException;
23
use GuzzleHttp\Psr7\Response;
24
use GuzzleHttp\Psr7\UploadedFile;
25
use Psr\Http\Message\ResponseInterface;
26
27
final class ApiClient
28
{
29
    private const API_URL = 'https://otpravka-api.pochta.ru';
30
31
    private const COMMON_HEADERS = [
32
        'Accept' => 'application/json;charset=UTF-8',
33
    ];
34
35
    private const FILE_SIGNATURES = [
36
        'zip' => 'PK',
37
        'pdf' => '%PDF-',
38
    ];
39
40
    /** @var Authorization */
41
    private $auth;
42
43
    /** @var HttpClient */
44
    private $httpClient;
45
46
    /** @var array */
47
    private $httpOptions;
48
49
    public function __construct(Authorization $authorization, array $httpOptions)
50
    {
51
        $this->auth = $authorization;
52
        $this->httpOptions = $httpOptions;
53
    }
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  null            $type
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $type is correct as it would always require null to be passed?
Loading history...
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, $type = null)
89
    {
90
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
91
92
        try {
93
            $response = $this->getHttpClient()->request(
94
                $method, $path, $request ? $this->buildRequestOptions($method, $request) : []
95
            );
96
        } catch (ClientException $e) {
97
            if ($e->getCode() === 401) {
98
                throw $this->handleAuthenticationException($e);
99
            }
100
101
            throw $this->handleClientException($e);
102
        } catch (ServerException $e) {
103
            throw $this->handleServerException($e);
104
        } catch (\Exception $e) {
105
            throw $e;
106
        }
107
108
        if (null === $response) {
109
            return null;
110
        }
111
112
        $fileType = $this->guessFileType($response);
113
114
        if ($fileType) {
115
            return $this->buildFile($response, $fileType);
116
        }
117
118
        $content = $this->getResponseContent($response);
119
120
        if (null === $type) {
0 ignored issues
show
introduced by
The condition null === $type is always true.
Loading history...
121
            return $content;
122
        }
123
124
        return Instantiator::instantiate($type, $content);
125
    }
126
127
    private function buildRequestOptions(string $method, Arrayable $request): array
128
    {
129
        $data = \array_filter($request->toArray());
130
131
        if ($method === 'GET') {
132
            return [
133
                'query' => $data,
134
            ];
135
        }
136
137
        return [
138
            'body'    => \json_encode($data),
139
            'headers' => [
140
                'Content-Type' => 'application/json;charset=UTF-8',
141
            ],
142
        ];
143
    }
144
145
    private function getHttpClient(): HttpClient
146
    {
147
        if ($this->httpClient === null) {
148
            $this->httpClient = new HttpClient(\array_merge(
149
                $this->httpOptions,
150
                [
151
                    'base_uri' => self::API_URL,
152
                    'headers'  => \array_merge(self::COMMON_HEADERS, $this->auth->toArray()),
153
                ]
154
            ));
155
        }
156
157
        return $this->httpClient;
158
    }
159
160
    private function guessFileType(Response $response): ?string
161
    {
162
        $chunk = $response->getBody()->read(10);
163
164
        foreach (self::FILE_SIGNATURES as $type => $signature) {
165
            if (0 === \stripos($chunk, $signature)) {
166
                $response->getBody()->rewind();
167
168
                return $type;
169
            }
170
        }
171
172
        return null;
173
    }
174
175
    private function buildFile(Response $response, string $type): UploadedFile
176
    {
177
        $name = \explode('=', $response->getHeaderLine('Content-Disposition') ?? '')[1] ?? '';
178
179
        return new UploadedFile(
180
            $response->getBody(),
181
            $response->getBody()->getSize(),
182
            \UPLOAD_ERR_OK,
183
            "{$name}.{$type}",
184
            $response->getHeaderLine('Content-Type')
185
        );
186
    }
187
188
    private function handleClientException(ClientException $exception): DispatchingException
189
    {
190
        $content = $this->getResponseContent($exception->getResponse());
191
192
        return new BadRequest(
193
            $content['message'] ?? $content['error'] ?? $content['desc'],
194
            (int) ($content['status'] ?? $content['code'] ?? $exception->getCode())
195
        );
196
    }
197
198
    private function handleAuthenticationException(ClientException $exception)
199
    {
200
        $content = $this->getResponseContent($exception->getResponse());
201
202
        return new BadRequest(
203
            $content['message'] ?? $content['desc'] ?? '',
204
            $content['code'] ? (int) $content['code'] : $exception->getCode()
205
        );
206
    }
207
208
    private function handleServerException(ServerException $e): DispatchingException
209
    {
210
        throw $e;
211
    }
212
213
    private function getResponseContent(ResponseInterface $response): array
214
    {
215
        return \json_decode((string) $response->getBody(), true);
216
    }
217
}
218