Passed
Push — master ( de53d8...b05c35 )
by Jhao
02:13
created

ApiClient::getHttpClient()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 13
ccs 0
cts 12
cp 0
rs 10
cc 2
nc 2
nop 0
crap 6
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 GuzzleHttp\ClientInterface;
18
use GuzzleHttp\Psr7\UploadedFile;
19
use GuzzleHttp\Exception\ClientException;
20
use GuzzleHttp\Exception\ServerException;
21
use Psr\Http\Message\RequestInterface;
22
use Psr\Http\Message\ResponseInterface;
23
use Appwilio\RussianPostSDK\Dispatching\Instantiator;
24
use Appwilio\RussianPostSDK\Dispatching\Contracts\Arrayable;
25
use Appwilio\RussianPostSDK\Dispatching\Exceptions\BadRequest;
26
use Appwilio\RussianPostSDK\Dispatching\Contracts\DispatchingException;
27
use function GuzzleHttp\Psr7\stream_for;
28
use function GuzzleHttp\Psr7\build_query;
29
30
final class ApiClient
31
{
32
    private const API_URL = 'https://otpravka-api.pochta.ru';
33
34
    /** @var Authentication */
35
    private $authentication;
36
37
    /** @var ClientInterface */
38
    private $httpClient;
39
40 3
    public function __construct(Authentication $authentication, ClientInterface $httpClient)
41
    {
42 3
        $this->authentication = $authentication;
43 3
        $this->httpClient = $httpClient;
44 3
    }
45
46
    public function get(string $path, ?Arrayable $request = null, $type = null)
47
    {
48
        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

48
        return $this->send('GET', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
49
    }
50
51
    public function post(string $path, Arrayable $request, $type = null)
52
    {
53
        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

53
        return $this->send('POST', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
54
    }
55
56
    public function put(string $path, Arrayable $request, $type = null)
57
    {
58
        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

58
        return $this->send('PUT', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
59
    }
60
61
    public function delete(string $path, Arrayable $request, $type = null)
62
    {
63
        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

63
        return $this->send('DELETE', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
64
    }
65
66
    /**
67
     * Выполнение запроса.
68
     *
69
     * @param  string          $method
70
     * @param  string          $path
71
     * @param  Arrayable|null  $request
72
     * @param  mixed           $responseType
73
     *
74
     * @throws DispatchingException
75
     * @throws \GuzzleHttp\Exception\GuzzleException
76
     *
77
     * @return mixed
78
     */
79
    private function send(string $method, string $path, ?Arrayable $request = null, $responseType = null)
80
    {
81
        try {
82
            $response = $this->httpClient->send($this->buildHttpRequest($method, $path, $request));
83
84
            $contenType = $response->getHeaderLine('Content-Type');
85
86
            if (\preg_match('~^application/(pdf|zip)$~', $contenType, $matches)) {
87
                return $this->buildFile($response, $matches[1]);
88
            }
89
90
            if (\preg_match('~^application/json~', $contenType)) {
91
                $content = $this->getResponseContent($response);
92
93
                return $responseType === null
94
                    ? $content
95
                    : Instantiator::instantiate($responseType, $content);
96
            }
97
98
            throw new BadRequest();
99
        } catch (ClientException $e) {
100
            throw $this->handleClientException($e);
101
        } catch (ServerException $e) {
102
            throw $this->handleServerException($e);
103
        } catch (\Exception $e) {
104
            throw $e;
105
        }
106
    }
107
108
    private function buildHttpRequest(string $method, string $path, ?Arrayable $payload): RequestInterface
109
    {
110
        $request = $this->authentication->authenticate(
111
            new Request($method, self::API_URL.$path, ['Accept' => 'application/json;charset=UTF-8'])
112
        );
113
114
        if ($payload === null) {
115
            return $request;
116
        }
117
118
        $data = \array_filter($payload->toArray());
119
120
        if (\strtoupper($method) === 'GET') {
121
            return $request->withBody(stream_for(build_query($data)));
122
        }
123
124
        /** @noinspection PhpIncompatibleReturnTypeInspection */
125
        return $request
126
            ->withHeader('Content-Type', 'application/json;charset=UTF-8')
127
            ->withBody(stream_for(\json_encode($data)));
128
    }
129
130
    private function buildFile(ResponseInterface $response, string $type): UploadedFile
131
    {
132
        \preg_match('~=(.+)$~', $response->getHeaderLine('Content-Disposition'), $matches);
133
134
        return new UploadedFile(
135
            $response->getBody(),
136
            $response->getBody()->getSize(),
137
            \UPLOAD_ERR_OK,
138
            "{$matches[1]}.{$type}",
139
            $response->getHeaderLine('Content-Type')
140
        );
141
    }
142
143
    private function handleClientException(ClientException $exception): DispatchingException
144
    {
145
        if (\in_array($exception->getCode(), [401, 403])) {
146
            throw $this->handleAuthenticationException($exception);
147
        }
148
149
        $content = $this->getResponseContent($exception->getResponse());
150
151
        return new BadRequest(
152
            $content['message'] ?? $content['error'] ?? $content['desc'],
153
            (int) ($content['status'] ?? $content['code'] ?? $exception->getCode())
154
        );
155
    }
156
157
    private function handleAuthenticationException(ClientException $exception)
158
    {
159
        $content = $this->getResponseContent($exception->getResponse());
160
161
        return new BadRequest(
162
            $content['message'] ?? $content['desc'] ?? '',
163
            $content['code'] ? (int) $content['code'] : $exception->getCode()
164
        );
165
    }
166
167
    private function handleServerException(ServerException $e): DispatchingException
168
    {
169
        throw $e;
170
    }
171
172
    private function getResponseContent(ResponseInterface $response): array
173
    {
174
        return \json_decode((string) $response->getBody(), true);
175
    }
176
}
177