Passed
Pull Request — master (#123)
by Arman
05:42 queued 02:39
created

HttpClient   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 352
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 37
eloc 91
c 2
b 0
f 0
dl 0
loc 352
rs 9.44

19 Methods

Rating   Name   Duplication   Size   Complexity  
A createMultiRequest() 0 9 1
A createRequest() 0 5 1
A start() 0 20 4
A setData() 0 4 1
A getData() 0 3 1
A setMethod() 0 8 2
A createAsyncMultiRequest() 0 8 1
A isMultiRequest() 0 3 1
A getMethod() 0 3 1
A getRequestHeaders() 0 13 3
A getResponse() 0 7 2
A getResponseHeaders() 0 13 3
A getResponseBody() 0 7 2
A handleResponse() 0 15 2
A getErrors() 0 7 2
A getResponseCookies() 0 13 3
A handleHeaders() 0 12 2
A __call() 0 9 2
A info() 0 11 3
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.0
13
 */
14
15
namespace Quantum\Libraries\Curl;
16
17
use Quantum\Exceptions\HttpException;
18
use Quantum\Exceptions\LangException;
19
use Quantum\Exceptions\AppException;
20
use Curl\CaseInsensitiveArray;
21
use ErrorException;
22
use Curl\MultiCurl;
23
use Curl\Curl;
24
25
/**
26
 * Curl Class
27
 * @package Quantum\Libraries\Curl
28
 * @uses php-curl-class/php-curl-class
29
 */
30
class HttpClient
31
{
32
33
    /**
34
     * Available methods
35
     */
36
    const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
37
38
    /**
39
     * Response headers section
40
     */
41
    const RESPONSE_HEADERS = 'headers';
42
43
    /**
44
     * Response cookies section
45
     */
46
    const RESPONSE_COOKIES = 'cookies';
47
48
    /**
49
     * Response body section
50
     */
51
    const RESPONSE_BODY = 'body';
52
53
    /**
54
     * @var MultiCurl|Curl
55
     */
56
    private $client = null;
57
58
    /**
59
     * @var string
60
     */
61
    private $method = 'GET';
62
63
    /**
64
     * @var mixed|null
65
     */
66
    private $data = null;
67
68
    /**
69
     * @var array
70
     */
71
    private $response = [];
72
73
    /**
74
     * @var array
75
     */
76
    private $errors = [];
77
78
    /**
79
     * Creates request
80
     * @param string $url
81
     * @return HttpClient
82
     */
83
    public function createRequest(string $url): HttpClient
84
    {
85
        $this->client = new Curl();
86
        $this->client->setUrl($url);
87
        return $this;
88
    }
89
90
    /**
91
     * Creates multi request
92
     * @return HttpClient
93
     */
94
    public function createMultiRequest(): HttpClient
95
    {
96
        $this->client = new MultiCurl();
97
98
        $this->client->complete(function (Curl $instance) {
99
            $this->handleResponse($instance);
100
        });
101
102
        return $this;
103
    }
104
105
    /**
106
     * Creates async multi request
107
     * @param callable $success
108
     * @param callable $error
109
     * @return HttpClient
110
     */
111
    public function createAsyncMultiRequest(callable $success, callable $error): HttpClient
112
    {
113
        $this->client = new MultiCurl();
114
115
        $this->client->success($success);
116
        $this->client->error($error);
117
118
        return $this;
119
    }
120
121
    /**
122
     * Sets http method
123
     * @param string $method
124
     * @return HttpClient
125
     * @throws HttpException
126
     * @throws LangException
127
     */
128
    public function setMethod(string $method): HttpClient
129
    {
130
        if (!in_array($method, self::METHODS)) {
131
            throw HttpException::methodNotAvailable($method);
132
        }
133
134
        $this->method = $method;
135
        return $this;
136
    }
137
138
    /**
139
     * Gets the current http method
140
     * @return string
141
     */
142
    public function getMethod(): string
143
    {
144
        return $this->method;
145
    }
146
147
    /**
148
     * Sets data
149
     * @param mixed $data
150
     * @return HttpClient
151
     */
152
    public function setData($data): HttpClient
153
    {
154
        $this->data = $data;
155
        return $this;
156
    }
157
158
    /**
159
     * Gets the data
160
     * @return mixed|null
161
     */
162
    public function getData()
163
    {
164
        return $this->data;
165
    }
166
167
    /**
168
     * Checks if the request is multi cURL
169
     * @return bool
170
     */
171
    public function isMultiRequest(): bool
172
    {
173
        return $this->client instanceof MultiCurl;
174
    }
175
176
    /**
177
     * Starts the request
178
     * @throws ErrorException
179
     * @throws HttpException
180
     * @throws LangException
181
     */
182
    public function start()
183
    {
184
        if (!$this->client) {
185
            throw HttpException::requestNotCreated();
186
        }
187
188
        if ($this->isMultiRequest()) {
189
            $this->client->start();
190
            return;
191
        }
192
193
        $this->client->setOpt(CURLOPT_CUSTOMREQUEST, $this->method);
194
195
        if ($this->data) {
196
            $this->client->setOpt(CURLOPT_POSTFIELDS, $this->client->buildPostData($this->data));
197
        }
198
199
        $this->client->exec();
200
201
        $this->handleResponse($this->client);
202
    }
203
204
    /**
205
     * Gets single or all request headers
206
     * @param string|null $header
207
     * @return mixed|null
208
     * @throws AppException
209
     */
210
    public function getRequestHeaders(string $header = null)
211
    {
212
        if ($this->isMultiRequest()) {
213
            throw AppException::methodNotSupported(__METHOD__, MultiCurl::class);
214
        }
215
216
        $requestHeaders = $this->handleHeaders($this->client->getRequestHeaders());
217
218
        if ($header) {
219
            return $requestHeaders[$header] ?? null;
220
        }
221
222
        return $requestHeaders;
223
    }
224
225
    /**
226
     * Gets the response headers
227
     * @param string|null $header
228
     * @return mixed|null
229
     * @throws AppException
230
     */
231
    public function getResponseHeaders(string $header = null)
232
    {
233
        if ($this->isMultiRequest()) {
234
            throw AppException::methodNotSupported(__METHOD__, MultiCurl::class);
235
        }
236
237
        $responseHeaders = $this->getResponse()[self::RESPONSE_HEADERS];
238
239
        if ($header) {
240
            return $responseHeaders[$header] ?? null;
241
        }
242
243
        return $responseHeaders;
244
    }
245
246
    /**
247
     * Gets the response cookies
248
     * @param string|null $cookie
249
     * @return mixed|null
250
     * @throws AppException
251
     */
252
    public function getResponseCookies(string $cookie = null)
253
    {
254
        if ($this->isMultiRequest()) {
255
            throw AppException::methodNotSupported(__METHOD__, MultiCurl::class);
256
        }
257
258
        $responseCookies = $this->getResponse()[self::RESPONSE_COOKIES];
259
260
        if ($cookie) {
261
            return $responseCookies[$cookie] ?? null;
262
        }
263
264
        return $responseCookies;
265
    }
266
267
    /**
268
     * Gets the response body
269
     * @return mixed|null
270
     * @throws AppException
271
     */
272
    public function getResponseBody()
273
    {
274
        if ($this->isMultiRequest()) {
275
            throw AppException::methodNotSupported(__METHOD__, MultiCurl::class);
276
        }
277
278
        return $this->response[$this->client->getId()][self::RESPONSE_BODY] ?? null;
279
    }
280
281
    /**
282
     * Gets the entire response
283
     * @return array
284
     */
285
    public function getResponse(): array
286
    {
287
        if (!$this->isMultiRequest()) {
288
            return $this->response[$this->client->getId()];
289
        }
290
291
        return $this->response;
292
    }
293
294
    /**
295
     * Returns the errors
296
     * @return array
297
     */
298
    public function getErrors(): array
299
    {
300
        if (!$this->isMultiRequest()) {
301
            return $this->errors[$this->client->getId()];
302
        }
303
304
        return $this->errors;
305
    }
306
307
    /**
308
     * Gets the curl info
309
     * @param int|null $option
310
     * @return mixed
311
     * @throws AppException
312
     */
313
    public function info(int $option = null)
314
    {
315
        if ($this->isMultiRequest()) {
316
            throw AppException::methodNotSupported(__METHOD__, MultiCurl::class);
317
        }
318
319
        if ($option) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $option of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
320
            return $this->client->getInfo($option);
321
        }
322
323
        return $this->client->getInfo();
324
    }
325
326
    /**
327
     * @param string $function
328
     * @param array $arguments
329
     * @return HttpClient
330
     * @throws HttpException
331
     * @throws LangException
332
     */
333
    public function __call(string $function, array $arguments): HttpClient
334
    {
335
        if (is_null($this->client)) {
336
            throw HttpException::requestNotCreated();
337
        }
338
339
        $this->client->{$function}(...$arguments);
340
341
        return $this;
342
    }
343
344
    /**
345
     * Handles the response
346
     * @param Curl $instance
347
     */
348
    private function handleResponse(Curl $instance)
349
    {
350
        if ($instance->isError()) {
351
            $this->errors[$instance->getId()] = [
352
                'code' => $instance->getErrorCode(),
353
                'message' => $instance->getErrorMessage()
354
            ];
355
356
            return;
357
        }
358
359
        $this->response[$instance->getId()] = [
360
            self::RESPONSE_HEADERS => $this->handleHeaders($instance->getResponseHeaders()),
361
            self::RESPONSE_COOKIES => $instance->getResponseCookies(),
362
            self::RESPONSE_BODY => $instance->getResponse()
363
        ];
364
    }
365
366
    /**
367
     * @param CaseInsensitiveArray $httpHeaders
368
     * @return array
369
     */
370
    private function handleHeaders(CaseInsensitiveArray $httpHeaders): array
371
    {
372
        $headers = [];
373
374
        while ($httpHeaders->valid()) {
375
            $headers[strtolower((string)$httpHeaders->key())] = $httpHeaders->current();
376
            $httpHeaders->next();
377
        }
378
379
        $httpHeaders->rewind();
380
381
        return $headers;
382
    }
383
384
}
385