Passed
Pull Request — master (#190)
by Arman
04:27
created

HttpClient::url()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.9.5
13
 */
14
15
namespace Quantum\Libraries\HttpClient;
16
17
use Quantum\Libraries\HttpClient\Exceptions\HttpClientException;
18
use Quantum\Exceptions\BaseException;
19
use Curl\CaseInsensitiveArray;
20
use ErrorException;
21
use Curl\MultiCurl;
22
use Curl\Curl;
23
24
/**
25
 * HttpClient Class
26
 * @package Quantum\Libraries\HttpClient
27
 * @uses php-curl-class/php-curl-class
28
 * @method object addGet(string $url, array $data = [])
29
 * @method object addPost(string $url, string $data = '', bool $follow_303_with_post = false)
30
 * @method setHeaders($headers)
31
 * @method setOpt($option, $value)
32
 */
33
class HttpClient
34
{
35
36
    /**
37
     * Available methods
38
     */
39
    const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
40
41
    /**
42
     * Response headers section
43
     */
44
    const RESPONSE_HEADERS = 'headers';
45
46
    /**
47
     * Response cookies section
48
     */
49
    const RESPONSE_COOKIES = 'cookies';
50
51
    /**
52
     * Response body section
53
     */
54
    const RESPONSE_BODY = 'body';
55
56
    /**
57
     * @var MultiCurl|Curl
58
     */
59
    private $client = null;
60
61
    /**
62
     * @var string
63
     */
64
    private $method = 'GET';
65
66
    /**
67
     * @var mixed|null
68
     */
69
    private $data = null;
70
71
    /**
72
     * @var array
73
     */
74
    private $response = [];
75
76
    /**
77
     * @var array
78
     */
79
    private $errors = [];
80
81
    /**
82
     * Creates request
83
     * @param string $url
84
     * @return HttpClient
85
     */
86
    public function createRequest(string $url): HttpClient
87
    {
88
        $this->client = new Curl();
89
        $this->client->setUrl($url);
90
        return $this;
91
    }
92
93
    /**
94
     * Creates multi request
95
     * @return HttpClient
96
     */
97
    public function createMultiRequest(): HttpClient
98
    {
99
        $this->client = new MultiCurl();
100
101
        $this->client->complete(function (Curl $instance) {
102
            $this->handleResponse($instance);
103
        });
104
105
        return $this;
106
    }
107
108
    /**
109
     * Creates async multi request
110
     * @param callable $success
111
     * @param callable $error
112
     * @return HttpClient
113
     */
114
    public function createAsyncMultiRequest(callable $success, callable $error): HttpClient
115
    {
116
        $this->client = new MultiCurl();
117
118
        $this->client->success($success);
119
        $this->client->error($error);
120
121
        return $this;
122
    }
123
124
    /**
125
     * Sets http method
126
     * @param string $method
127
     * @return $this
128
     * @throws HttpClientException
129
     */
130
    public function setMethod(string $method): HttpClient
131
    {
132
        if (!in_array($method, self::METHODS)) {
133
            throw HttpClientException::methodNotAvailable($method);
134
        }
135
136
        $this->method = $method;
137
        return $this;
138
    }
139
140
    /**
141
     * Gets the current http method
142
     * @return string
143
     */
144
    public function getMethod(): string
145
    {
146
        return $this->method;
147
    }
148
149
    /**
150
     * Sets data
151
     * @param mixed $data
152
     * @return HttpClient
153
     */
154
    public function setData($data): HttpClient
155
    {
156
        $this->data = $data;
157
        return $this;
158
    }
159
160
    /**
161
     * Gets the data
162
     * @return mixed|null
163
     */
164
    public function getData()
165
    {
166
        return $this->data;
167
    }
168
169
    /**
170
     * Checks if the request is multi cURL
171
     * @return bool
172
     */
173
    public function isMultiRequest(): bool
174
    {
175
        return $this->client instanceof MultiCurl;
176
    }
177
178
    /**
179
     * Starts the request
180
     * @throws ErrorException
181
     * @throws HttpClientException
182
     */
183
    public function start(): HttpClient
184
    {
185
        if (!$this->client) {
186
            throw HttpClientException::requestNotCreated();
187
        }
188
189
        if ($this->isMultiRequest()) {
190
            $this->client->start();
191
        } else {
192
            $this->startSingleRequest();
193
        }
194
195
        return $this;
196
    }
197
198
    /**
199
     * Gets single or all request headers
200
     * @param string|null $header
201
     * @return mixed|null
202
     * @throws BaseException
203
     */
204
    public function getRequestHeaders(string $header = null)
205
    {
206
        $this->ensureSingleRequest();
207
208
        $requestHeaders = $this->formatHeaders($this->client->getRequestHeaders());
0 ignored issues
show
Bug introduced by
The method getRequestHeaders() does not exist on Curl\MultiCurl. ( Ignorable by Annotation )

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

208
        $requestHeaders = $this->formatHeaders($this->client->/** @scrutinizer ignore-call */ getRequestHeaders());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
$this->client->getRequestHeaders() of type array is incompatible with the type Curl\CaseInsensitiveArray expected by parameter $headers of Quantum\Libraries\HttpCl...Client::formatHeaders(). ( Ignorable by Annotation )

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

208
        $requestHeaders = $this->formatHeaders(/** @scrutinizer ignore-type */ $this->client->getRequestHeaders());
Loading history...
209
210
        if ($header) {
211
            return $requestHeaders[$header] ?? null;
212
        }
213
214
        return $requestHeaders;
215
    }
216
217
    /**
218
     * Gets the response headers
219
     * @param string|null $header
220
     * @return mixed|null
221
     * @throws BaseException
222
     */
223
    public function getResponseHeaders(string $header = null)
224
    {
225
        $this->ensureSingleRequest();
226
227
        $responseHeaders = $this->getResponse()[self::RESPONSE_HEADERS];
228
229
        if ($header) {
230
            return $responseHeaders[$header] ?? null;
231
        }
232
233
        return $responseHeaders;
234
    }
235
236
    /**
237
     * Gets the response cookies
238
     * @param string|null $cookie
239
     * @return mixed|null
240
     * @throws BaseException
241
     */
242
    public function getResponseCookies(string $cookie = null)
243
    {
244
        $this->ensureSingleRequest();
245
246
        $responseCookies = $this->getResponse()[self::RESPONSE_COOKIES];
247
248
        if ($cookie) {
249
            return $responseCookies[$cookie] ?? null;
250
        }
251
252
        return $responseCookies;
253
    }
254
255
    /**
256
     * Gets the response body
257
     * @return mixed|null
258
     * @throws BaseException
259
     */
260
    public function getResponseBody()
261
    {
262
        $this->ensureSingleRequest();
263
264
        return $this->response[$this->client->getId()][self::RESPONSE_BODY] ?? null;
0 ignored issues
show
Bug introduced by
The method getId() does not exist on Curl\MultiCurl. ( Ignorable by Annotation )

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

264
        return $this->response[$this->client->/** @scrutinizer ignore-call */ getId()][self::RESPONSE_BODY] ?? null;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
265
    }
266
267
    /**
268
     * Gets the entire response
269
     * @return array
270
     */
271
    public function getResponse(): array
272
    {
273
        return $this->isMultiRequest() ? $this->response : ($this->response[$this->client->getId()] ?? []);
274
    }
275
276
    /**
277
     * Returns the errors
278
     * @return array
279
     */
280
    public function getErrors(): array
281
    {
282
        return $this->isMultiRequest() ? $this->errors : ($this->errors[$this->client->getId()] ?? []);
283
    }
284
285
    /**
286
     * Gets the curl info
287
     * @param int|null $option
288
     * @return mixed
289
     * @throws BaseException
290
     */
291
    public function info(int $option = null)
292
    {
293
        $this->ensureSingleRequest();
294
295
        return $option ? $this->client->getInfo($option) : $this->client->getInfo();
0 ignored issues
show
Bug introduced by
The method getInfo() does not exist on Curl\MultiCurl. ( Ignorable by Annotation )

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

295
        return $option ? $this->client->/** @scrutinizer ignore-call */ getInfo($option) : $this->client->getInfo();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
296
    }
297
298
    /**
299
     * Gets the current url being executed
300
     * @return string|null
301
     * @throws BaseException
302
     */
303
    public function url(): ?string
304
    {
305
        $this->ensureSingleRequest();
306
307
        return $this->client->getUrl();
0 ignored issues
show
Bug introduced by
The method getUrl() does not exist on Curl\MultiCurl. ( Ignorable by Annotation )

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

307
        return $this->client->/** @scrutinizer ignore-call */ getUrl();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
308
    }
309
310
    /**
311
     * @param string $method
312
     * @param array $arguments
313
     * @return $this
314
     * @throws BaseException
315
     * @throws HttpClientException
316
     */
317
    public function __call(string $method, array $arguments): HttpClient
318
    {
319
        if (is_null($this->client)) {
320
            throw HttpClientException::requestNotCreated();
321
        }
322
323
        if (!method_exists($this->client, $method)) {
324
            throw HttpClientException::methodNotSupported($method, get_class($this->client));
325
        }
326
327
        $this->client->$method(...$arguments);
328
329
        return $this;
330
    }
331
332
    /**
333
     * @return void
334
     * @throws ErrorException
335
     */
336
    private function startSingleRequest(): void
337
    {
338
        $this->client->setOpt(CURLOPT_CUSTOMREQUEST, $this->method);
339
340
        if ($this->data) {
341
            $this->client->setOpt(CURLOPT_POSTFIELDS, $this->client->buildPostData($this->data));
0 ignored issues
show
Bug introduced by
The method buildPostData() does not exist on Curl\MultiCurl. ( Ignorable by Annotation )

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

341
            $this->client->setOpt(CURLOPT_POSTFIELDS, $this->client->/** @scrutinizer ignore-call */ buildPostData($this->data));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
342
        }
343
344
        $this->client->exec();
0 ignored issues
show
Bug introduced by
The method exec() does not exist on Curl\MultiCurl. ( Ignorable by Annotation )

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

344
        $this->client->/** @scrutinizer ignore-call */ 
345
                       exec();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
345
        $this->handleResponse($this->client);
0 ignored issues
show
Bug introduced by
It seems like $this->client can also be of type Curl\MultiCurl; however, parameter $instance of Quantum\Libraries\HttpCl...lient::handleResponse() does only seem to accept Curl\Curl, maybe add an additional type check? ( Ignorable by Annotation )

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

345
        $this->handleResponse(/** @scrutinizer ignore-type */ $this->client);
Loading history...
346
    }
347
348
    /**
349
     * Handles the response
350
     * @param Curl $instance
351
     */
352
    private function handleResponse(Curl $instance)
353
    {
354
        if ($instance->isError()) {
355
            $this->errors[$instance->getId()] = [
356
                'code' => $instance->getErrorCode(),
357
                'message' => $instance->getErrorMessage()
358
            ];
359
        }
360
361
        $this->response[$instance->getId()] = [
362
            self::RESPONSE_HEADERS => $this->formatHeaders($instance->getResponseHeaders()),
363
            self::RESPONSE_COOKIES => $instance->getResponseCookies(),
364
            self::RESPONSE_BODY => $instance->getResponse()
365
        ];
366
    }
367
368
    private function formatHeaders(CaseInsensitiveArray $headers): array
369
    {
370
        $formatted = [];
371
372
        foreach ($headers as $key => $value) {
373
            $formatted[strtolower($key)] = $value;
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type null; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

373
            $formatted[strtolower(/** @scrutinizer ignore-type */ $key)] = $value;
Loading history...
374
        }
375
376
        return $formatted;
377
    }
378
379
    /**
380
     * @return void
381
     * @throws BaseException
382
     */
383
    private function ensureSingleRequest(): void
384
    {
385
        if ($this->isMultiRequest()) {
386
            throw HttpClientException::methodNotSupported(__METHOD__, MultiCurl::class);
387
        }
388
    }
389
}