Client::updateApiToken()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.2888
c 0
b 0
f 0
cc 5
nc 4
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Yproximite\Api\Client;
5
6
use Http\Client\HttpClient;
7
use Http\Message\MessageFactory;
8
use Psr\Http\Message\RequestInterface;
9
use Psr\Http\Message\StreamInterface;
10
use Http\Client\Exception\HttpException;
11
use Http\Discovery\MessageFactoryDiscovery;
12
use Http\Client\Exception\TransferException as HttpTransferException;
13
14
use Yproximite\Api\Exception\LogicException;
15
use Yproximite\Api\Exception\RequestException;
16
use Yproximite\Api\Exception\TransferException;
17
use Yproximite\Api\Exception\AuthenficationException;
18
use Yproximite\Api\Exception\InvalidResponseException;
19
20
/**
21
 * Class Client
22
 */
23
class Client
24
{
25
    const BASE_URL = 'https://api.yproximite.fr';
26
27
    /**
28
     * @var string|null
29
     */
30
    private $baseUrl;
31
32
    /**
33
     * @var string|null
34
     */
35
    private $apiKey;
36
37
    /**
38
     * @var HttpClient
39
     */
40
    private $httpClient;
41
42
    /**
43
     * @var MessageFactory|null
44
     */
45
    private $messageFactory;
46
47
    /**
48
     * @var string
49
     */
50
    private $apiToken;
51
52
    /**
53
     * Used to determine if token was used. In some cases the token could be invalidated during the usage of the API.
54
     *
55
     * @var bool
56
     */
57
    private $apiTokenFresh = true;
58
59
    /**
60
     * Client constructor.
61
     *
62
     * @param HttpClient          $httpClient
63
     * @param string|null         $apiKey
64
     * @param string|null         $baseUrl
65
     * @param MessageFactory|null $messageFactory
66
     */
67
    public function __construct(
68
        HttpClient $httpClient,
69
        string $apiKey = null,
70
        string $baseUrl = null,
71
        MessageFactory $messageFactory = null
72
    ) {
73
        $this->httpClient     = $httpClient;
74
        $this->messageFactory = $messageFactory;
75
        $this->apiKey         = $apiKey;
76
        $this->baseUrl        = $baseUrl;
77
    }
78
79
    /**
80
     * @param null|string $baseUrl
81
     */
82
    public function setBaseUrl(string $baseUrl = null)
83
    {
84
        $this->baseUrl = $baseUrl;
85
    }
86
87
    /**
88
     * @param null|string $apiKey
89
     */
90
    public function setApiKey(string $apiKey = null)
91
    {
92
        $this->apiKey = $apiKey;
93
94
        $this->resetApiToken();
95
    }
96
97
    /**
98
     * Sends a request
99
     *
100
     * @param string                                     $method
101
     * @param string                                     $path
102
     * @param array|resource|string|StreamInterface|null $body
103
     * @param array                                      $headers
104
     *
105
     * @return mixed
106
     * @throws TransferException
107
     * @throws InvalidResponseException
108
     * @throws AuthenficationException
109
     */
110
    public function sendRequest(string $method, string $path, $body = null, array $headers = [])
111
    {
112
        $request = $this->createRequest($method, $path, $body, $headers);
113
114
        try {
115
            $content = $this->doSendRequest($request);
116
        } catch (InvalidResponseException $e) {
117
            if ($e->getResponse()->getStatusCode() === 401 && !$this->apiTokenFresh) {
118
                $this->resetApiToken();
119
120
                $request = $this->createRequest($method, $path, $body, $headers);
121
                $content = $this->doSendRequest($request);
122
            } else {
123
                throw $e;
124
            }
125
        }
126
127
        return $content;
128
    }
129
130
    /**
131
     * @param string $method
132
     * @param string $path
133
     * @param null   $body
134
     * @param array  $headers
135
     * @param bool   $withAuthorization
136
     *
137
     * @return RequestInterface
138
     */
139
    private function createRequest(
140
        string $method,
141
        string $path,
142
        $body = null,
143
        array $headers = [],
144
        bool $withAuthorization = true
145
    ): RequestInterface {
146
        $uri  = $this->getSafeBaseUrl();
147
        $uri .= '/'.$path;
148
149
        $rawData = is_array($body) ? http_build_query($body) : $body;
150
        $body    = null;
151
152
        if (in_array($method, $this->getQueryMethods())) {
153
            if (is_string($rawData) && $rawData !== '') {
154
                $uri .= '?'.$rawData;
155
            }
156
        } else {
157
            $body = $rawData;
158
        }
159
160
        $baseHeaders = ['Content-Type' => 'application/x-www-form-urlencoded'];
161
162
        if ($withAuthorization) {
163
            $baseHeaders['Authorization'] = $this->getAuthorizationHeader();
164
        }
165
166
        return $this->getMessageFactory()->createRequest($method, $uri, $headers + $baseHeaders, $body);
167
    }
168
169
    /**
170
     * @return string
171
     */
172
    private function getSafeBaseUrl(): string
173
    {
174
        return !is_null($this->baseUrl) ? $this->baseUrl : self::BASE_URL;
175
    }
176
177
    /**
178
     * @param RequestInterface $request
179
     *
180
     * @return mixed
181
     */
182
    private function doSendRequest(RequestInterface $request)
183
    {
184
        try {
185
            $response = $this->getHttpClient()->sendRequest($request);
186
        } catch (HttpTransferException $e) {
187
            throw new TransferException(
188
                $e->getMessage(),
189
                $request,
190
                $e instanceof HttpException ? $e->getResponse() : null
191
            );
192
        }
193
194
        if ($response->getStatusCode() >= 400) {
195
            throw new InvalidResponseException('Bad response status code.', $request, $response);
196
        }
197
198
        $rawData = (string) $response->getBody();
199
200
        if (empty($rawData)) {
201
            return null;
202
        }
203
204
        $data = json_decode($rawData, true);
205
206
        if (json_last_error() !== JSON_ERROR_NONE) {
207
            throw new InvalidResponseException('Could not decode the response.', $request, $response);
208
        }
209
210
        return $data;
211
    }
212
213
    /**
214
     * @return HttpClient
215
     */
216
    private function getHttpClient(): HttpClient
217
    {
218
        return $this->httpClient;
219
    }
220
221
    /**
222
     * @return MessageFactory
223
     */
224
    private function getMessageFactory(): MessageFactory
225
    {
226
        if ($this->messageFactory === null) {
227
            $this->messageFactory = MessageFactoryDiscovery::find();
228
        }
229
230
        return $this->messageFactory;
231
    }
232
233
    /**
234
     * Returns all methods that uses query string to transfer a request data
235
     *
236
     * @return array
237
     */
238
    private function getQueryMethods(): array
239
    {
240
        return ['GET', 'HEAD', 'DELETE'];
241
    }
242
243
    /**
244
     * @return string
245
     */
246
    private function getApiToken(): string
247
    {
248
        if (!is_null($this->apiToken)) {
249
            $this->apiTokenFresh = false;
250
        } else {
251
            $this->updateApiToken();
252
        }
253
254
        return $this->apiToken;
255
    }
256
257
    private function updateApiToken()
258
    {
259
        if (is_null($this->apiKey)) {
260
            throw new LogicException('The api key cannot be empty.');
261
        }
262
263
        $request = $this->createRequest('POST', 'login_check', ['api_key' => $this->apiKey], [], false);
264
265
        try {
266
            $data = $this->doSendRequest($request);
267
        } catch (RequestException $e) {
268
            throw new AuthenficationException('Could not request a token.');
269
        }
270
271
        if (!is_array($data) || !array_key_exists('token', $data)) {
272
            throw new AuthenficationException('Could not retreive a token.');
273
        }
274
275
        $this->apiToken = (string) $data['token'];
276
    }
277
278
    private function resetApiToken()
279
    {
280
        $this->apiToken      = null;
281
        $this->apiTokenFresh = true;
282
    }
283
284
    /**
285
     * @return string
286
     */
287
    private function getAuthorizationHeader(): string
288
    {
289
        return sprintf('Bearer %s', $this->getApiToken());
290
    }
291
}
292