HttpClient::createMultiRequest()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

209
        $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

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

265
        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...
266
    }
267
268
    /**
269
     * Gets the entire response
270
     * @return array
271
     */
272
    public function getResponse(): array
273
    {
274
        return $this->isMultiRequest() ? $this->response : ($this->response[$this->client->getId()] ?? []);
275
    }
276
277
    /**
278
     * Returns the errors
279
     * @return array
280
     */
281
    public function getErrors(): array
282
    {
283
        return $this->isMultiRequest() ? $this->errors : ($this->errors[$this->client->getId()] ?? []);
284
    }
285
286
    /**
287
     * Gets the curl info
288
     * @param int|null $option
289
     * @return mixed
290
     * @throws BaseException
291
     */
292
    public function info(int $option = null)
293
    {
294
        $this->ensureSingleRequest();
295
296
        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

296
        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...
297
    }
298
299
    /**
300
     * Gets the current url being executed
301
     * @return string|null
302
     * @throws BaseException
303
     */
304
    public function url(): ?string
305
    {
306
        $this->ensureSingleRequest();
307
308
        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

308
        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...
309
    }
310
311
    /**
312
     * @param string $method
313
     * @param array $arguments
314
     * @return $this
315
     * @throws BaseException
316
     * @throws HttpClientException
317
     */
318
    public function __call(string $method, array $arguments): HttpClient
319
    {
320
        if (is_null($this->client)) {
321
            throw HttpClientException::requestNotCreated();
322
        }
323
324
        if (!method_exists($this->client, $method)) {
325
            throw HttpClientException::methodNotSupported($method, get_class($this->client));
326
        }
327
328
        $this->client->$method(...$arguments);
329
330
        return $this;
331
    }
332
333
    /**
334
     * @return void
335
     * @throws ErrorException
336
     */
337
    private function startSingleRequest(): void
338
    {
339
        $this->client->setOpt(CURLOPT_CUSTOMREQUEST, $this->method);
340
341
        if ($this->data) {
342
            $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

342
            $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...
343
        }
344
345
        $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

345
        $this->client->/** @scrutinizer ignore-call */ 
346
                       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...
346
        $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

346
        $this->handleResponse(/** @scrutinizer ignore-type */ $this->client);
Loading history...
347
    }
348
349
    /**
350
     * Handles the response
351
     * @param Curl $instance
352
     */
353
    private function handleResponse(Curl $instance)
354
    {
355
        if ($instance->isError()) {
356
            $this->errors[$instance->getId()] = [
357
                'code' => $instance->getErrorCode(),
358
                'message' => $instance->getErrorMessage()
359
            ];
360
        }
361
362
        $this->response[$instance->getId()] = [
363
            self::RESPONSE_HEADERS => $this->formatHeaders($instance->getResponseHeaders()),
364
            self::RESPONSE_COOKIES => $instance->getResponseCookies(),
365
            self::RESPONSE_BODY => $instance->getResponse()
366
        ];
367
    }
368
369
    private function formatHeaders(CaseInsensitiveArray $headers): array
370
    {
371
        $formatted = [];
372
373
        foreach ($headers as $key => $value) {
374
            $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

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