Completed
Push — master ( 3dc30b...6a0dc0 )
by Jhao
04:34 queued 02:01
created

ApiClient::post()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Appwilio\RussianPostSDK\Dispatching\Http;
6
7
use Appwilio\RussianPostSDK\Dispatching\Contracts\Arrayable;
8
use GuzzleHttp\Client as HttpClient;
9
use GuzzleHttp\Psr7\Response;
10
use GuzzleHttp\Psr7\UploadedFile;
11
use JMS\Serializer\Handler\HandlerRegistryInterface;
12
use JMS\Serializer\Serializer;
13
use JMS\Serializer\SerializerBuilder;
14
15
final class ApiClient
16
{
17
    private const API_URL = 'https://otpravka-api.pochta.ru';
18
19
    private const COMMON_HEADERS = [
20
        'Accept' => 'application/json;charset=UTF-8',
21
    ];
22
23
    private const FILE_SIGNATURES = [
24
        'zip' => 'PK',
25
        'pdf' => '%PDF-',
26
    ];
27
28
    /** @var Authorization */
29
    private $auth;
30
31
    /** @var HttpClient */
32
    private $httpClient;
33
34
    /** @var Serializer */
35
    private $serializer;
36
37
    /** @var array */
38
    private $httpOptions;
39
40
    /** @var array */
41
    private $customDeserializators = [];
42
43
    public function __construct(Authorization $authorization, array $httpOptions)
44
    {
45
        $this->auth = $authorization;
46
        $this->httpOptions = $httpOptions;
47
    }
48
49
    public function get(string $path, ?Arrayable $request = null, $type = null)
50
    {
51
        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

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

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

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

66
        return $this->send('DELETE', /** @scrutinizer ignore-type */ ...\func_get_args());
Loading history...
67
    }
68
69
    public function addCustomDeserializator(string $type, string $class): void
70
    {
71
        $this->customDeserializators[$type] = $class;
72
    }
73
74
    private function createDesrializer(): Serializer
75
    {
76
        if (null === $this->serializer) {
77
            $this->serializer = SerializerBuilder::create()
78
                ->configureHandlers(function (HandlerRegistryInterface $registry) {
79
                    foreach ($this->customDeserializators as $class => $handler) {
80
                        $registry->registerHandler('deserialization', $class, 'json', new $handler);
0 ignored issues
show
Bug introduced by
'deserialization' of type string is incompatible with the type integer expected by parameter $direction of JMS\Serializer\Handler\H...face::registerHandler(). ( Ignorable by Annotation )

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

80
                        $registry->registerHandler(/** @scrutinizer ignore-type */ 'deserialization', $class, 'json', new $handler);
Loading history...
Bug introduced by
new $handler() of type object is incompatible with the type callable expected by parameter $handler of JMS\Serializer\Handler\H...face::registerHandler(). ( Ignorable by Annotation )

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

80
                        $registry->registerHandler('deserialization', $class, 'json', /** @scrutinizer ignore-type */ new $handler);
Loading history...
81
                    }
82
                })
83
                ->build();
84
        }
85
86
        return $this->serializer;
87
    }
88
89
    private function send(string $method, string $path, ?Arrayable $request = null, $type = null)
90
    {
91
        $response = $this->getHttpClient()->request(
92
            $method, $path, $request ? $this->buildRequestOptions($method, $request) : []
93
        );
94
95
        $fileType = $this->guessFileType($response);
96
97
        if ($fileType) {
98
            return $this->buildFile($response, $fileType);
99
        }
100
101
        $content = (string) $response->getBody();
102
103
        if (null === $type) {
104
            return \json_decode($content, false);
105
        }
106
107
        if ($type instanceof ArrayOf) {
108
            $type = "array<{$type->getType()}>";
109
        }
110
111
        return $this->createDesrializer()->deserialize($content, $type, 'json');
112
    }
113
114
    private function buildRequestOptions(string $method, Arrayable $request): array
115
    {
116
        $data = \array_filter($request->toArray());
117
118
        if ($method === 'GET') {
119
            return [
120
                'query' => $data,
121
            ];
122
        }
123
124
        return [
125
            'body'    => \json_encode($data),
126
            'headers' => [
127
                'Content-Type' => 'application/json;charset=UTF-8',
128
            ],
129
        ];
130
    }
131
132
    private function getHttpClient(): HttpClient
133
    {
134
        if ($this->httpClient === null) {
135
            $this->httpClient = new HttpClient(\array_merge(
136
                $this->httpOptions,
137
                [
138
                    'base_uri' => self::API_URL,
139
                    'headers'  => \array_merge(self::COMMON_HEADERS, $this->auth->toArray()),
140
                ]
141
            ));
142
        }
143
144
        return $this->httpClient;
145
    }
146
147
    private function guessFileType(Response $response): ?string
148
    {
149
        $chunk = $response->getBody()->read(10);
150
151
        foreach (self::FILE_SIGNATURES as $type => $signature) {
152
            if (0 === \stripos($chunk, $signature)) {
153
                $response->getBody()->rewind();
154
155
                return $type;
156
            }
157
        }
158
159
        return null;
160
    }
161
162
    public function buildFile(Response $response, string $type): UploadedFile
163
    {
164
        $name = \explode('=', $response->getHeaderLine('Content-Disposition') ?? '')[1] ?? '';
165
166
        return new UploadedFile(
167
            $response->getBody(),
168
            $response->getBody()->getSize(),
169
            \UPLOAD_ERR_OK,
170
            "{$name}.{$type}",
171
            $response->getHeaderLine('Content-Type')
172
        );
173
    }
174
}
175