Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
99.21% |
252 / 254 |
|
96.49% |
55 / 57 |
CRAP | |
0.00% |
0 / 1 |
Client | |
99.21% |
252 / 254 |
|
96.49% |
55 / 57 |
169 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
10 | |||
createMulti | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
fromCurlCommand | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMethod | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getMethod | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
hasMethod | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
3 | |||
setOptions | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addOptions | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addOption | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasOption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setHandler | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getHandler | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasHandler | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMultiHandler | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getMultiHandler | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasMultiHandler | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setAuth | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getAuth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasAuth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setData | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addData | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getData | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
hasData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
removeData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
removeAllData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setQuery | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addQuery | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getQuery | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
hasQuery | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
removeQuery | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
removeAllQuery | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setFiles | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
addFile | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
5 | |||
getFiles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFile | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
3 | |||
hasFiles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasFile | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
removeFile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
removeFiles | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
setBody | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
setBodyFromFile | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
hasBody | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getBody | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
3 | |||
getBodyContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
3 | |||
getBodyContentLength | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
3 | |||
removeBody | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
prepare | |
97.78% |
44 / 45 |
|
0.00% |
0 / 1 |
26 | |||
send | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
8 | |||
sendAsync | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
render | |
92.31% |
12 / 13 |
|
0.00% |
0 / 1 |
5.01 | |||
toCurlCommand | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
__toString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__call | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
__callStatic | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * Pop PHP Framework (http://www.popphp.org/) |
4 | * |
5 | * @link https://github.com/popphp/popphp-framework |
6 | * @author Nick Sagona, III <dev@nolainteractive.com> |
7 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
8 | * @license http://www.popphp.org/license New BSD License |
9 | */ |
10 | |
11 | /** |
12 | * @namespace |
13 | */ |
14 | namespace Pop\Http; |
15 | |
16 | use Pop\Http\Client\Handler\Stream; |
17 | use Pop\Http\Client\Request; |
18 | use Pop\Http\Client\Response; |
19 | use Pop\Http\Client\Handler\Curl; |
20 | use Pop\Http\Client\Handler\CurlMulti; |
21 | use Pop\Http\Client\Handler\HandlerInterface; |
22 | use Pop\Mime\Part\Body; |
23 | |
24 | /** |
25 | * HTTP client class |
26 | * |
27 | * @category Pop |
28 | * @package Pop\Http |
29 | * @author Nick Sagona, III <dev@nolainteractive.com> |
30 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
31 | * @license http://www.popphp.org/license New BSD License |
32 | * @version 5.0.0 |
33 | */ |
34 | class Client extends AbstractHttp |
35 | { |
36 | |
37 | /** |
38 | * Client options |
39 | * @var array |
40 | */ |
41 | protected array $options = []; |
42 | |
43 | /** |
44 | * Request handler |
45 | * @var ?HandlerInterface |
46 | */ |
47 | protected ?HandlerInterface $handler = null; |
48 | |
49 | /** |
50 | * Request multi-handler |
51 | * @var ?CurlMulti |
52 | */ |
53 | protected ?CurlMulti $multiHandler = null; |
54 | |
55 | /** |
56 | * HTTP auth object |
57 | * @var ?Auth |
58 | */ |
59 | protected ?Auth $auth = null; |
60 | |
61 | /** |
62 | * Instantiate the client object |
63 | * |
64 | * Optional parameters are a request URI string, client request instance, a client response instance, |
65 | * a client handler instance, an auth instance, or an options array |
66 | */ |
67 | public function __construct() |
68 | { |
69 | $args = func_get_args(); |
70 | $request = null; |
71 | $response = null; |
72 | $handler = null; |
73 | |
74 | foreach ($args as $arg) { |
75 | if (is_string($arg)) { |
76 | $request = new Request($arg); |
77 | } else if ($arg instanceof Client\Request) { |
78 | $request = $arg; |
79 | } else if ($arg instanceof Client\Response) { |
80 | $response = $arg; |
81 | } else if ($arg instanceof Client\Handler\HandlerInterface) { |
82 | $handler = $arg; |
83 | } else if ($arg instanceof Auth) { |
84 | $this->setAuth($arg); |
85 | } else if (is_array($arg)) { |
86 | $this->setOptions($arg); |
87 | } |
88 | } |
89 | |
90 | parent::__construct($request, $response); |
91 | |
92 | if ($handler !== null) { |
93 | if ($handler instanceof CurlMulti) { |
94 | $this->setMultiHandler($handler); |
95 | } else { |
96 | $this->setHandler($handler); |
97 | } |
98 | } |
99 | } |
100 | |
101 | /** |
102 | * Factory to create a multi-handler object |
103 | * |
104 | * @param array $requests |
105 | * @return CurlMulti |
106 | */ |
107 | public static function createMulti(array $requests): CurlMulti |
108 | { |
109 | $multiHandler = new Client\Handler\CurlMulti(); |
110 | |
111 | foreach ($requests as $request) { |
112 | $client = new Client($request); |
113 | $client->setMultiHandler($multiHandler); |
114 | } |
115 | |
116 | return $multiHandler; |
117 | } |
118 | |
119 | /** |
120 | * Method to convert Curl CLI command to a client object |
121 | * |
122 | * @param string $command |
123 | * @throws Exception |
124 | * @return Client |
125 | */ |
126 | public static function fromCurlCommand(string $command): Client |
127 | { |
128 | return Curl\Command::commandToClient($command); |
129 | } |
130 | |
131 | /** |
132 | * Set method |
133 | * |
134 | * @param string $method |
135 | * @return Client |
136 | */ |
137 | public function setMethod(string $method): Client |
138 | { |
139 | if ($this->hasRequest()) { |
140 | $this->request->setMethod($method); |
141 | } else { |
142 | $this->options['method'] = $method; |
143 | } |
144 | |
145 | return $this; |
146 | } |
147 | |
148 | /** |
149 | * Get method |
150 | * |
151 | * @return string|null |
152 | */ |
153 | public function getMethod(): string|null |
154 | { |
155 | if ($this->hasRequest()) { |
156 | return $this->request->getMethod(); |
157 | } else { |
158 | return $this->options['method'] ?? null; |
159 | } |
160 | } |
161 | |
162 | /** |
163 | * Has method |
164 | * |
165 | * @return bool |
166 | */ |
167 | public function hasMethod(): bool |
168 | { |
169 | return ((($this->hasRequest()) && !empty($this->request->getMethod())) || isset($this->options['method'])); |
170 | } |
171 | |
172 | /** |
173 | * Set options |
174 | * |
175 | * Supported options |
176 | * - 'base_uri' |
177 | * - 'method' |
178 | * - 'headers' |
179 | * - 'user_agent' |
180 | * - 'query' (can only be encoded query string on the URI) |
181 | * - 'data' (can be any request data) |
182 | * - 'files' |
183 | * - 'type' |
184 | * - 'auto' |
185 | * - 'async' |
186 | * - 'verify_peer' |
187 | * - 'allow_self_signed' |
188 | * - 'force_custom_method' (Curl only - forces CURLOPT_CUSTOMREQUEST) |
189 | * |
190 | * @param array $options |
191 | * @return Client |
192 | */ |
193 | public function setOptions(array $options): Client |
194 | { |
195 | $this->options = $options; |
196 | return $this; |
197 | } |
198 | |
199 | /** |
200 | * Add options |
201 | * |
202 | * @param array $options |
203 | * @return Client |
204 | */ |
205 | public function addOptions(array $options): Client |
206 | { |
207 | foreach ($options as $name => $value) { |
208 | $this->addOption($name, $value); |
209 | } |
210 | return $this; |
211 | } |
212 | |
213 | /** |
214 | * Add an option |
215 | * |
216 | * @param mixed $value |
217 | * @return Client |
218 | */ |
219 | public function addOption(string $name, mixed $value): Client |
220 | { |
221 | $this->options[$name] = $value; |
222 | return $this; |
223 | } |
224 | |
225 | /** |
226 | * Get options |
227 | * |
228 | * @return array |
229 | */ |
230 | public function getOptions(): array |
231 | { |
232 | return $this->options; |
233 | } |
234 | |
235 | /** |
236 | * Get options |
237 | * |
238 | * @param string $name |
239 | * @return mixed |
240 | */ |
241 | public function getOption(string $name): mixed |
242 | { |
243 | return $this->options[$name] ?? null; |
244 | } |
245 | |
246 | /** |
247 | * Has options |
248 | * |
249 | * @return bool |
250 | */ |
251 | public function hasOptions(): bool |
252 | { |
253 | return !empty($this->options); |
254 | } |
255 | |
256 | /** |
257 | * Has option |
258 | * |
259 | * @return bool |
260 | */ |
261 | public function hasOption(string $name): bool |
262 | { |
263 | return isset($this->options[$name]); |
264 | } |
265 | |
266 | /** |
267 | * Set handler |
268 | * |
269 | * @param HandlerInterface $handler |
270 | * @return Client |
271 | */ |
272 | public function setHandler(HandlerInterface $handler): Client |
273 | { |
274 | $this->handler = $handler; |
275 | return $this; |
276 | } |
277 | |
278 | /** |
279 | * Get handler |
280 | * |
281 | * @return HandlerInterface|null |
282 | */ |
283 | public function getHandler(): HandlerInterface|null |
284 | { |
285 | return $this->handler; |
286 | } |
287 | |
288 | /** |
289 | * Has handler |
290 | * |
291 | * @return bool |
292 | */ |
293 | public function hasHandler(): bool |
294 | { |
295 | return ($this->handler !== null); |
296 | } |
297 | |
298 | /** |
299 | * Set multi-handler |
300 | * |
301 | * @param CurlMulti $multiHandler |
302 | * @return Client |
303 | */ |
304 | public function setMultiHandler(CurlMulti $multiHandler): Client |
305 | { |
306 | $this->multiHandler = $multiHandler; |
307 | |
308 | if (!($this->handler instanceof Curl)) { |
309 | $this->handler = new Curl(); |
310 | $this->multiHandler->addClient($this); |
311 | } |
312 | |
313 | return $this; |
314 | } |
315 | |
316 | /** |
317 | * Get multi-handler |
318 | * |
319 | * @return CurlMulti |
320 | */ |
321 | public function getMultiHandler(): CurlMulti |
322 | { |
323 | return $this->multiHandler; |
324 | } |
325 | |
326 | /** |
327 | * Has multi-handler |
328 | * |
329 | * @return bool |
330 | */ |
331 | public function hasMultiHandler(): bool |
332 | { |
333 | return ($this->multiHandler !== null); |
334 | } |
335 | |
336 | /** |
337 | * Set auth |
338 | * |
339 | * @param Auth $auth |
340 | * @return Client |
341 | */ |
342 | public function setAuth(Auth $auth): Client |
343 | { |
344 | $this->auth = $auth; |
345 | return $this; |
346 | } |
347 | |
348 | /** |
349 | * Get auth |
350 | * |
351 | * @return Auth|null |
352 | */ |
353 | public function getAuth(): Auth|null |
354 | { |
355 | return $this->auth; |
356 | } |
357 | |
358 | /** |
359 | * Has auth |
360 | * |
361 | * @return bool |
362 | */ |
363 | public function hasAuth(): bool |
364 | { |
365 | return ($this->auth !== null); |
366 | } |
367 | |
368 | /** |
369 | * Set data |
370 | * |
371 | * @param array $data |
372 | * @return Client |
373 | */ |
374 | public function setData(array $data): Client |
375 | { |
376 | $this->options['data'] = $data; |
377 | return $this; |
378 | } |
379 | |
380 | /** |
381 | * Add data |
382 | * |
383 | * @param string $name |
384 | * @param mixed $value |
385 | * @return Client |
386 | */ |
387 | public function addData(string $name, mixed $value): Client |
388 | { |
389 | if (!isset($this->options['data'])) { |
390 | $this->options['data'] = []; |
391 | } |
392 | $this->options['data'][$name] = $value; |
393 | return $this; |
394 | } |
395 | |
396 | /** |
397 | * Get data |
398 | * |
399 | * @param ?string $key |
400 | * @return mixed |
401 | */ |
402 | public function getData(?string $key = null): mixed |
403 | { |
404 | if ($key !== null) { |
405 | return (isset($this->options['data']) && isset($this->options['data'][$key])) ? |
406 | $this->options['data'][$key] : null; |
407 | } else { |
408 | return $this->options['data'] ?? null; |
409 | } |
410 | } |
411 | |
412 | /** |
413 | * Has data |
414 | * |
415 | * @param ?string $key |
416 | * @return bool |
417 | */ |
418 | public function hasData(?string $key = null): bool |
419 | { |
420 | if ($key !== null) { |
421 | return (isset($this->options['data']) && isset($this->options['data'][$key])); |
422 | } else { |
423 | return isset($this->options['data']); |
424 | } |
425 | } |
426 | |
427 | /** |
428 | * Remove data |
429 | * |
430 | * @param string $key |
431 | * @return Client |
432 | */ |
433 | public function removeData(string $key): Client |
434 | { |
435 | if (isset($this->options['data']) && isset($this->options['data'][$key])) { |
436 | unset($this->options['data'][$key]); |
437 | } |
438 | |
439 | return $this; |
440 | } |
441 | |
442 | /** |
443 | * Remove all data |
444 | * |
445 | * @return Client |
446 | */ |
447 | public function removeAllData(): Client |
448 | { |
449 | if (isset($this->options['data'])) { |
450 | unset($this->options['data']); |
451 | } |
452 | |
453 | return $this; |
454 | } |
455 | |
456 | /** |
457 | * Set query |
458 | * |
459 | * @param array $query |
460 | * @return Client |
461 | */ |
462 | public function setQuery(array $query): Client |
463 | { |
464 | $this->options['query'] = $query; |
465 | return $this; |
466 | } |
467 | |
468 | /** |
469 | * Add query |
470 | * |
471 | * @param string $name |
472 | * @param mixed $value |
473 | * @return Client |
474 | */ |
475 | public function addQuery(string $name, mixed $value): Client |
476 | { |
477 | if (!isset($this->options['query'])) { |
478 | $this->options['query'] = []; |
479 | } |
480 | $this->options['query'][$name] = $value; |
481 | return $this; |
482 | } |
483 | |
484 | /** |
485 | * Get query |
486 | * |
487 | * @param ?string $key |
488 | * @return mixed |
489 | */ |
490 | public function getQuery(?string $key = null): mixed |
491 | { |
492 | if ($key !== null) { |
493 | return (isset($this->options['query']) && isset($this->options['query'][$key])) ? |
494 | $this->options['query'][$key] : null; |
495 | } else { |
496 | return $this->options['query'] ?? null; |
497 | } |
498 | } |
499 | |
500 | /** |
501 | * Has query |
502 | * |
503 | * @param ?string $key |
504 | * @return bool |
505 | */ |
506 | public function hasQuery(?string $key = null): bool |
507 | { |
508 | if ($key !== null) { |
509 | return (isset($this->options['query']) && isset($this->options['query'][$key])); |
510 | } else { |
511 | return isset($this->options['query']); |
512 | } |
513 | } |
514 | |
515 | /** |
516 | * Remove query |
517 | * |
518 | * @param string $key |
519 | * @return Client |
520 | */ |
521 | public function removeQuery(string $key): Client |
522 | { |
523 | if (isset($this->options['query']) && isset($this->options['query'][$key])) { |
524 | unset($this->options['query'][$key]); |
525 | } |
526 | |
527 | return $this; |
528 | } |
529 | |
530 | /** |
531 | * Remove all query data |
532 | * |
533 | * @return Client |
534 | */ |
535 | public function removeAllQuery(): Client |
536 | { |
537 | if (isset($this->options['query'])) { |
538 | unset($this->options['query']); |
539 | } |
540 | |
541 | return $this; |
542 | } |
543 | |
544 | /** |
545 | * Set files |
546 | * |
547 | * @param array|string $files |
548 | * @param bool $multipart |
549 | * @throws Exception |
550 | * @return Client |
551 | */ |
552 | public function setFiles(array|string $files, bool $multipart = true): Client |
553 | { |
554 | if (is_string($files)) { |
555 | $files = [$files]; |
556 | } |
557 | |
558 | $filenames = []; |
559 | |
560 | foreach ($files as $i => $file) { |
561 | if (!file_exists($file)) { |
562 | throw new Exception("Error: The file '" . $file . "' does not exist."); |
563 | } |
564 | |
565 | $name = (is_numeric($i)) ? 'file' . ($i + 1) : $i; |
566 | $filenames[$name] = $file; |
567 | } |
568 | |
569 | $this->options['files'] = $filenames; |
570 | |
571 | if ($multipart) { |
572 | $this->options['type'] = Request::MULTIPART; |
573 | } |
574 | return $this; |
575 | } |
576 | |
577 | /** |
578 | * Add file |
579 | * |
580 | * @param string $file |
581 | * @param ?string $name |
582 | * @throws Exception |
583 | * @return Client |
584 | */ |
585 | public function addFile(string $file, ?string $name = null): Client |
586 | { |
587 | if (!file_exists($file)) { |
588 | throw new Exception("Error: The file '" . $file . "' does not exist."); |
589 | } |
590 | |
591 | if (!isset($this->options['files'])) { |
592 | $this->options['files'] = []; |
593 | } |
594 | |
595 | if ($name === null) { |
596 | $i = 1; |
597 | $name = 'file' . $i; |
598 | while (isset($this->options['files'][$name])) { |
599 | $i++; |
600 | $name = 'file' . $i; |
601 | } |
602 | } |
603 | |
604 | $this->options['files'][$name] = $file; |
605 | return $this; |
606 | } |
607 | |
608 | /** |
609 | * Get files |
610 | * |
611 | * @return array|null |
612 | */ |
613 | public function getFiles(): array|null |
614 | { |
615 | return $this->options['files'] ?? null; |
616 | } |
617 | |
618 | /** |
619 | * Get file |
620 | * |
621 | * @param string $key |
622 | * @return string|null |
623 | */ |
624 | public function getFile(string $key): string|null |
625 | { |
626 | return (isset($this->options['files']) && isset($this->options['files'][$key])) ? |
627 | $this->options['files'][$key] : null; |
628 | } |
629 | |
630 | /** |
631 | * Has files |
632 | * |
633 | * @return bool |
634 | */ |
635 | public function hasFiles(): bool |
636 | { |
637 | return isset($this->options['files']); |
638 | } |
639 | |
640 | /** |
641 | * Has file |
642 | * |
643 | * @param string $key |
644 | * @return bool |
645 | */ |
646 | public function hasFile(string $key): bool |
647 | { |
648 | return (isset($this->options['files']) && isset($this->options['files'][$key])); |
649 | } |
650 | |
651 | /** |
652 | * Remove file |
653 | * |
654 | * @param string $key |
655 | * @return Client |
656 | */ |
657 | public function removeFile(string $key): Client |
658 | { |
659 | if (isset($this->options['files']) && isset($this->options['files'][$key])) { |
660 | unset($this->options['files'][$key]); |
661 | } |
662 | |
663 | return $this; |
664 | } |
665 | |
666 | /** |
667 | * Remove all files |
668 | * |
669 | * @return Client |
670 | */ |
671 | public function removeFiles(): Client |
672 | { |
673 | if (isset($this->options['files'])) { |
674 | unset($this->options['files']); |
675 | } |
676 | |
677 | return $this; |
678 | } |
679 | |
680 | /** |
681 | * Set request body |
682 | * |
683 | * @param string $body |
684 | * @return Client |
685 | */ |
686 | public function setBody(string $body): Client |
687 | { |
688 | if ($this->request === null) { |
689 | throw new Exception('Error: The request object has not been created.'); |
690 | } |
691 | |
692 | $this->request->setBody($body); |
693 | |
694 | return $this; |
695 | } |
696 | |
697 | /** |
698 | * Set request body from file |
699 | * |
700 | * @param string $file |
701 | * @return Client |
702 | */ |
703 | public function setBodyFromFile(string $file): Client |
704 | { |
705 | if ($this->request === null) { |
706 | throw new Exception('Error: The request object has not been created.'); |
707 | } |
708 | if (!file_exists($file)) { |
709 | throw new Exception("Error: The file '" . $file . "' does not exist."); |
710 | } |
711 | |
712 | $this->request->setBody(file_get_contents($file)); |
713 | |
714 | return $this; |
715 | } |
716 | |
717 | /** |
718 | * Has request body |
719 | * |
720 | * @return bool |
721 | */ |
722 | public function hasBody(): bool |
723 | { |
724 | return (($this->request !== null) && ($this->request->hasBody())); |
725 | } |
726 | |
727 | /** |
728 | * Get request body |
729 | * |
730 | * @return Body|null |
731 | */ |
732 | public function getBody(): Body|null |
733 | { |
734 | return (($this->request !== null) && ($this->request->hasBody())) ? $this->request->getBody() : null; |
735 | } |
736 | |
737 | /** |
738 | * Get request body content |
739 | * |
740 | * @return string|null |
741 | */ |
742 | public function getBodyContent(): string|null |
743 | { |
744 | return (($this->request !== null) && ($this->request->hasBody())) ? $this->request->getBodyContent() : null; |
745 | } |
746 | |
747 | /** |
748 | * Get request body content length |
749 | * |
750 | * @return int |
751 | */ |
752 | public function getBodyContentLength(): int |
753 | { |
754 | return (($this->request !== null) && ($this->request->hasBody())) ? $this->request->getBodyContentLength() : 0; |
755 | } |
756 | |
757 | /** |
758 | * Remove the body |
759 | * |
760 | * @return Client |
761 | */ |
762 | public function removeBody(): Client |
763 | { |
764 | if (($this->request !== null) && ($this->request->hasBody())) { |
765 | $this->request->removeBody(); |
766 | } |
767 | return $this; |
768 | } |
769 | |
770 | /** |
771 | * Prepare the client request |
772 | * |
773 | * @param ?string $uri |
774 | * @param ?string $method |
775 | * @throws Exception|Client\Exception |
776 | * @return Client |
777 | */ |
778 | public function prepare(?string $uri = null, string $method = null): Client |
779 | { |
780 | if ((!$this->hasRequest()) && ($uri === null)) { |
781 | throw new Exception('Error: There is no request URI to send.'); |
782 | } |
783 | |
784 | if (($method === null) && isset($this->options['method'])) { |
785 | $method = $this->options['method']; |
786 | } |
787 | |
788 | // Set request URI |
789 | if ($uri !== null) { |
790 | if ($this->hasRequest()) { |
791 | $this->request->setUri(new Uri($uri)); |
792 | } else { |
793 | $request = new Request(new Uri($uri), ($method ?? 'GET')); |
794 | $this->setRequest($request); |
795 | } |
796 | } else if ($method !== null) { |
797 | $this->request->setMethod($method); |
798 | } |
799 | |
800 | // Add headers |
801 | if (($this->hasOption('headers')) && is_array($this->options['headers'])) { |
802 | $this->request->addHeaders($this->options['headers']); |
803 | } |
804 | |
805 | // Add data and files |
806 | $data = []; |
807 | if ($this->hasOption('data')) { |
808 | $data = $this->options['data']; |
809 | } |
810 | |
811 | if ($this->hasOption('files')) { |
812 | $files = $this->options['files']; |
813 | foreach ($files as $file => $value) { |
814 | $file = (is_numeric($file)) ? 'file' . ($file + 1) : $file; |
815 | $data[$file] = [ |
816 | 'filename' => $value, |
817 | 'contentType' => Client\Data::getMimeTypeFromFilename($value) |
818 | ]; |
819 | } |
820 | } |
821 | |
822 | if (!empty($data)) { |
823 | $this->request->setData($data); |
824 | } |
825 | |
826 | // Add query |
827 | if ($this->hasOption('query')) { |
828 | $this->request->setQuery($this->options['query']); |
829 | } |
830 | |
831 | // Set request type |
832 | if ($this->hasOption('type')) { |
833 | $this->request->setRequestType($this->options['type']); |
834 | } |
835 | |
836 | // Set handler |
837 | if (!$this->hasHandler()) { |
838 | $this->setHandler(new Curl()); |
839 | } |
840 | |
841 | // Set user-agent |
842 | if ($this->hasOption('user_agent')) { |
843 | if ($this->handler instanceof Curl) { |
844 | $this->handler->setOption(CURLOPT_USERAGENT, $this->options['user_agent']); |
845 | } else if ($this->handler instanceof Stream) { |
846 | $this->handler->addContextOption('http', ['user_agent' => $this->options['user_agent']]); |
847 | } |
848 | } |
849 | |
850 | // Handle SSL options |
851 | if (!($this->handler instanceof CurlMulti)) { |
852 | if ($this->hasOption('verify_peer')) { |
853 | $this->handler->setVerifyPeer((bool)$this->options['verify_peer']); |
854 | } |
855 | if ($this->hasOption('allow_self_signed')) { |
856 | $this->handler->allowSelfSigned((bool)$this->options['allow_self_signed']); |
857 | } |
858 | } |
859 | |
860 | // Adjust for base_uri |
861 | if (($this->hasOption('base_uri')) && !str_starts_with($this->request->getUriAsString(), $this->getOption('base_uri'))) { |
862 | $this->request->setUri($this->getOption('base_uri') . $this->request->getUriAsString()); |
863 | } |
864 | |
865 | return $this; |
866 | } |
867 | |
868 | /** |
869 | * Send the client request |
870 | * |
871 | * @param ?string $uri |
872 | * @param ?string $method |
873 | * @throws Exception|Client\Exception|Client\Handler\Exception |
874 | * @return Response|Promise|array|string |
875 | */ |
876 | public function send(?string $uri = null, string $method = null): Response|Promise|array|string |
877 | { |
878 | if (isset($this->options['async']) && ($this->options['async'] === true)) { |
879 | return $this->sendAsync(); |
880 | } else { |
881 | $this->prepare($uri, $method); |
882 | $this->response = (isset($this->options['force_custom_method']) && ($this->handler instanceof Curl)) ? |
883 | $this->handler->prepare($this->request, $this->auth, (bool)$this->options['force_custom_method'])->send() : |
884 | $this->handler->prepare($this->request, $this->auth)->send(); |
885 | |
886 | return (($this->hasOption('auto')) && ($this->options['auto']) && ($this->response instanceof Response)) ? |
887 | $this->response->getParsedResponse() : $this->response; |
888 | } |
889 | } |
890 | |
891 | /** |
892 | * Method to send the request asynchronously |
893 | * |
894 | * @return Promise |
895 | */ |
896 | public function sendAsync(): Promise |
897 | { |
898 | return new Promise($this); |
899 | } |
900 | |
901 | /** |
902 | * Method to render the request as a string |
903 | * |
904 | * @return string |
905 | */ |
906 | public function render(): string |
907 | { |
908 | $this->prepare(); |
909 | |
910 | if (isset($this->options['force_custom_method']) && ($this->handler instanceof Curl)) { |
911 | $this->handler->prepare($this->request, $this->auth, (bool)$this->options['force_custom_method']); |
912 | } else { |
913 | $this->handler->prepare($this->request, $this->auth); |
914 | } |
915 | |
916 | $uri = $this->handler->getUriObject(); |
917 | $uriString = $uri->getUri(); |
918 | if ($uri->hasQuery()) { |
919 | $uriString .= '?' . $uri->getQuery(); |
920 | } |
921 | |
922 | $request = $this->request->getMethod() . ' ' . $uriString . ' HTTP/' . $this->handler->getHttpVersion() . "\r\n" . |
923 | 'Host: ' . $uri->getFullHost() . "\r\n" . $this->request->getHeadersAsString() . "\r\n"; |
924 | |
925 | if ($this->request->hasDataContent()) { |
926 | $request .= $this->request->getDataContent(); |
927 | } |
928 | return $request; |
929 | } |
930 | |
931 | /** |
932 | * Method to convert client object to a Curl command for the CLI |
933 | * |
934 | * @throws Exception|Curl\Exception |
935 | * @return string |
936 | */ |
937 | public function toCurlCommand(): string |
938 | { |
939 | if ($this->handler instanceof Stream) { |
940 | throw new Exception('Error: The client handler must be an instance of Curl'); |
941 | } |
942 | |
943 | if (!$this->hasHandler()) { |
944 | $this->setHandler(new Curl()); |
945 | } |
946 | |
947 | return Curl\Command::clientToCommand($this); |
948 | } |
949 | |
950 | /** |
951 | * To string magic method to render the client request to a raw string |
952 | * |
953 | */ |
954 | public function __toString(): string |
955 | { |
956 | return $this->render(); |
957 | } |
958 | |
959 | /** |
960 | * Magic method to send requests by the method name, i.e. $client->get('http://localhost/'); |
961 | * |
962 | * @param string $methodName |
963 | * @param array $arguments |
964 | * @throws Exception|Client\Exception |
965 | * @return Response|Promise|array|string |
966 | */ |
967 | public function __call(string $methodName, array $arguments): Response|Promise|array|string |
968 | { |
969 | if (str_contains($methodName, 'Async')) { |
970 | if (isset($arguments[0])) { |
971 | $methodName = strtoupper(substr($methodName, 0, strpos($methodName, 'Async'))); |
972 | $this->prepare($arguments[0], $methodName); |
973 | } |
974 | return $this->sendAsync(); |
975 | } else { |
976 | return $this->send(($arguments[0] ?? null), strtoupper($methodName)); |
977 | } |
978 | } |
979 | |
980 | /** |
981 | * Magic method to send requests by the static method name, i.e. Client::get('http://localhost/'); |
982 | * |
983 | * @param string $methodName |
984 | * @param array $arguments |
985 | * @throws Exception|Client\Exception |
986 | * @return Response|Promise|array|string |
987 | */ |
988 | public static function __callStatic(string $methodName, array $arguments): Response|Promise|array|string |
989 | { |
990 | $client = new static(); |
991 | $uri = null; |
992 | |
993 | if (count($arguments) > 1) { |
994 | foreach ($arguments as $arg) { |
995 | if (is_string($arg)) { |
996 | $client->setRequest(new Client\Request($arg)); |
997 | } else if ($arg instanceof Client\Request) { |
998 | $client->setRequest($arg); |
999 | } else if ($arg instanceof Client\Response) { |
1000 | $client->setResponse($arg); |
1001 | } else if ($arg instanceof Client\Handler\HandlerInterface) { |
1002 | $client->setHandler($arg); |
1003 | } else if ($arg instanceof Auth) { |
1004 | $client->setAuth($arg); |
1005 | } else if (is_array($arg)) { |
1006 | $client->setOptions($arg); |
1007 | } |
1008 | } |
1009 | } |
1010 | |
1011 | if ((!$client->hasRequest()) && isset($arguments[0])) { |
1012 | $uri = ($arguments[0]); |
1013 | } |
1014 | |
1015 | if (str_contains($methodName, 'Async')) { |
1016 | $methodName = strtoupper(substr($methodName, 0, strpos($methodName, 'Async'))); |
1017 | $client->prepare($uri, $methodName); |
1018 | return $client->sendAsync(); |
1019 | } else { |
1020 | return $client->send($uri, strtoupper($methodName)); |
1021 | } |
1022 | } |
1023 | |
1024 | } |