Completed
Push — master ( fcec15...cb3dfd )
by Philipp
02:59 queued 15s
created

Client::performRequest()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0582

Importance

Changes 7
Bugs 2 Features 1
Metric Value
c 7
b 2
f 1
dl 0
loc 20
ccs 11
cts 13
cp 0.8462
rs 9.2
cc 4
eloc 13
nc 8
nop 4
crap 4.0582
1
<?php
2
3
/**
4
 * Puzzle\Client
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to version 3 of the GPL license,
9
 * that is bundled with this package in the file LICENSE, and is
10
 * available online at http://www.gnu.org/licenses/gpl.txt
11
 *
12
 * PHP version 5
13
 *
14
 * @category  Puzzle
15
 * @package   Puzzle
16
 * @author    Philipp Dittert <[email protected]>
17
 * @copyright 2015 Philipp Dittert
18
 * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License, version 3 (GPL-3.0)
19
 * @link      https://github.com/dittertp/Puzzle
20
 */
21
22
namespace Puzzle;
23
24
use Puzzle\Exceptions\ConnectionException;
25
use Puzzle\Interfaces\ClientInterface;
26
use Puzzle\Interfaces\SerializerInterface;
27
use Puzzle\Exceptions\ConfigurationException;
28
use Puzzle\Exceptions\ClientErrorResponseException;
29
use Puzzle\Exceptions\ClientErrorException;
30
use Puzzle\Exceptions\TransportException;
31
use Puzzle\Exceptions\ServerErrorResponseException;
32
use Puzzle\Exceptions\ServerErrorException;
33
use Puzzle\Exceptions\InvalidRequestMethodException;
34
use Puzzle\Exceptions\InvalidRequestException;
35
use Puzzle\Serializer\DefaultSerializer;
36
37
/**
38
 * class Client
39
 *
40
 * @category  Puzzle
41
 * @package   Puzzle
42
 * @author    Philipp Dittert <[email protected]>
43
 * @copyright 2015 Philipp Dittert
44
 * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License, version 3 (GPL-3.0)
45
 * @link      https://github.com/dittertp/Puzzle
46
 */
47
48
class Client
49
{
50
    /**
51
     * @var string
52
     */
53
    const SCHEME_SSL = "https://";
54
55
    /**
56
     * @var string
57
     */
58
    const SCHEME_PLAIN = "http://";
59
60
    /**
61
     * @var SerializerInterface
62
     */
63
    protected $serializer;
64
65
    /**
66
     * @var resource
67
     */
68
    protected $handle;
69
70
    /**
71
     * @var array
72
     */
73
    protected $httpOptions = array();
74
75
    /**
76
     * @var array
77
     */
78
    protected $httpHeaders = array();
79
80
    /**
81
     * @var string
82
     */
83
    protected $host;
84
85
    /**
86
     * @var integer
87
     */
88
    protected $port;
89
90
    /**
91
     * @var string
92
     */
93
    protected $scheme;
94
95
    /**
96
     * new class instance
97
     *
98
     * @param string $host the host
99
     * @param mixed  $port (optional) the port
100
     *
101
     * @throws ConfigurationException
102
     */
103 19
    public function __construct($host, $port)
104
    {
105 19
        $this->setHost($host, $port);
106 18
        $this->init();
107 18
    }
108
109
    /**
110
     * resets all attributes
111
     *
112
     * @return void
113
     */
114
    public function reset()
115
    {
116
        $this->init();
117
    }
118
119
    /**
120
     * initialize default values
121
     *
122
     * @return void
123
     * @throws Exceptions\ServerErrorException
124
     */
125 18
    protected function init()
126
    {
127 18
        $handle = curl_init();
128
129 18
        $this->handle = $handle;
130
131 18
        $this->setSerializer(new DefaultSerializer());
132 18
        $this->scheme = self::SCHEME_PLAIN;
133 18
        $this->httpOptions = array();
134 18
        $this->httpOptions[CURLOPT_RETURNTRANSFER] = true;
135 18
        $this->httpOptions[CURLOPT_FOLLOWLOCATION] = false;
136 18
    }
137
138
    /**
139
     * Sets a serializer instance
140
     *
141
     * @param SerializerInterface $serializer the serializer instance
142
     *
143
     * @return void
144
     */
145 18
    public function setSerializer(SerializerInterface $serializer)
146
    {
147 18
        $this->serializer = $serializer;
148 18
    }
149
150
    /**
151
     * Returns serializer instance
152
     *
153
     * @return SerializerInterface
154
     */
155 10
    public function getSerializer()
156
    {
157 10
        return $this->serializer;
158
    }
159
160
    /**
161
     * Returns curl resource
162
     *
163
     * @return resource
164
     */
165 9
    protected function getHandle()
166
    {
167 9
        return $this->handle;
168
    }
169
170
    /**
171
     * Sets username and password for basic authentication
172
     *
173
     * @param string $username the username
174
     * @param string $password the password
175
     *
176
     * @return void
177
     */
178 1
    public function setAuthentication($username, $password)
179
    {
180 1
        $this->httpOptions[CURLOPT_USERPWD] = $username . ":" . $password;
181 1
    }
182
183
    /**
184
     * Sets hostname and optional port
185
     *
186
     * @param string $host the hostname
187
     * @param mixed  $port the port
188
     *
189
     * @return void
190
     * @throws \Puzzle\Exceptions\ConfigurationException
191
     */
192 19
    protected function setHost($host, $port = null)
193
    {
194 19
        $this->host = $host;
195 19
        if ($port !== null) {
196 19
            if (is_numeric($port)) {
197 18
                $this->port = (int) $port;
198 18
            } else {
199 1
                throw new ConfigurationException("Port '{$port}' is not numeric");
200
            }
201 18
        }
202 18
    }
203
204
    /**
205
     * closes curl resource
206
     *
207
     * @return void
208
     */
209
    protected function close()
210
    {
211
        curl_close($this->getHandle());
212
    }
213
214
    /**
215
     * Return's http status response code
216
     *
217
     * @return mixed
218
     */
219
    public function getStatusCode()
220
    {
221
        return curl_getinfo($this->getHandle(), CURLINFO_HTTP_CODE);
222
    }
223
224
    /**
225
     * performs the request
226
     *
227
     * @param string $method request method
228
     * @param string $uri    uri string
229
     * @param mixed  $params optional query string parameters
230
     * @param mixed  $body   "post" body
231
     *
232
     * @return mixed
233
     * @throws \Puzzle\Exceptions\ServerErrorResponseException
234
     * @throws \Puzzle\Exceptions\ClientErrorResponseException
235
     */
236 10
    public function performRequest($method, $uri, $params = null, $body = null)
237
    {
238
        try {
239
            // serialize(json) body if it's not already a string
240 10
            if ($body !== null) {
241 8
                $body = $this->getSerializer()->serialize($body);
242 8
            }
243
244 10
            return $this->processRequest(
245 10
                $method,
246 10
                $uri,
247 10
                $params,
248
                $body
249 10
            );
250 3
        } catch (ClientErrorResponseException $exception) {
251
            throw $exception;
252 3
        } catch (ServerErrorResponseException $exception) {
253
            throw $exception;
254
        }
255
    }
256
257
    /**
258
     * perform a get request
259
     *
260
     * @param string $uri    uri string
261
     * @param mixed  $params optional query string parameters
262
     * @param mixed  $body   the "post" body
263
     *
264
     * @return mixed
265
     * @throws ClientErrorResponseException
266
     * @throws ServerErrorResponseException
267
     * @throws \Exception
268
     */
269 1
    public function get($uri, $params = null, $body = null)
270
    {
271 1
        return $this->performRequest("get", $uri, $params, $body);
272
    }
273
274
    /**
275
     * perform a put request
276
     *
277
     * @param string $uri    uri string
278
     * @param mixed  $params optional query string parameters
279
     * @param mixed  $body   the "post" body
280
     *
281
     * @return mixed
282
     * @throws ClientErrorResponseException
283
     * @throws ServerErrorResponseException
284
     * @throws \Exception
285
     */
286 1
    public function put($uri, $params = null, $body = null)
287
    {
288 1
        return $this->performRequest("put", $uri, $params, $body);
289
    }
290
291
    /**
292
     * perform a post request
293
     *
294
     * @param string $uri    uri string
295
     * @param mixed  $params optional query string parameters
296
     * @param mixed  $body   the "post" body
297
     *
298
     * @return mixed
299
     * @throws ClientErrorResponseException
300
     * @throws ServerErrorResponseException
301
     * @throws \Exception
302
     */
303 1
    public function post($uri, $params = null, $body = null)
304
    {
305 1
        return $this->performRequest("post", $uri, $params, $body);
306
    }
307
308
    /**
309
     * perform a patch
310
     *
311
     * @param string $uri    uri string
312
     * @param mixed  $params optional query string parameters
313
     * @param mixed  $body   the "post" body
314
     *
315
     * @return mixed
316
     * @throws ClientErrorResponseException
317
     * @throws ServerErrorResponseException
318
     * @throws \Exception
319
     */
320 1
    public function patch($uri, $params = null, $body = null)
321
    {
322 1
        return $this->performRequest("patch", $uri, $params, $body);
323
    }
324
325
    /**
326
     * perform a head request
327
     *
328
     * @param string $uri    uri string
329
     * @param mixed  $params optional query string parameters
330
     * @param mixed  $body   the "post" body
331
     *
332
     * @return mixed
333
     * @throws ClientErrorResponseException
334
     * @throws ServerErrorResponseException
335
     * @throws \Exception
336
     */
337 1
    public function head($uri, $params = null, $body = null)
338
    {
339 1
        return $this->performRequest("head", $uri, $params, $body);
340
    }
341
342
    /**
343
     * perform a delete request
344
     *
345
     * @param string $uri    uri string
346
     * @param mixed  $params optional query string parameters
347
     * @param mixed  $body   the "post" body
348
     *
349
     * @return mixed
350
     * @throws ClientErrorResponseException
351
     * @throws ServerErrorResponseException
352
     * @throws \Exception
353
     */
354 1
    public function delete($uri, $params = null, $body = null)
355
    {
356 1
        return $this->performRequest("delete", $uri, $params, $body);
357
    }
358
359
    /**
360
     * precess the request
361
     *
362
     * @param string $method the request method
363
     * @param string $uri    the uri string
364
     * @param mixed  $params the optional query string parameters
365
     * @param string $body   the (post) body
366
     *
367
     * @return mixed
368
     * @throws Exceptions\InvalidRequestMethodException
369
     */
370 10
    protected function processRequest($method, $uri, $params = null, $body = null)
371
    {
372 10
        $methodString = $this->getMethod($method);
373
374 10
        if (method_exists($this, $methodString)) {
375 9
            return $this->$methodString($method, $uri, $params, $body);
376
        } else {
377 1
            throw new InvalidRequestMethodException("request method '{$method}' not implemented");
378
        }
379
    }
380
381
    /**
382
     * returns the request method
383
     *
384
     * @param string $method the request method
385
     *
386
     * @return string
387
     */
388 10
    protected function getMethod($method)
389
    {
390 10
        $method = strtolower($method);
391
392 10
        if ($method === "patch") {
393 1
            return "putRequest";
394
        }
395
396 9
        return $method . "Request";
397
    }
398
399
    /**
400
     * get request implementation
401
     *
402
     * @param string $method the request method
403
     * @param string $uri    the uri
404
     * @param mixed  $params optional query string parameters
405
     * @param string $body   body/post parameters
406
     *
407
     * @return string
408
     * @throws \Puzzle\Exceptions\InvalidRequestException
409
     */
410 1
    protected function getRequest($method, $uri, $params, $body)
411
    {
412
        // get requests has no specific necessary requirements
413
414 1
        return $this->execute($method, $uri, $params, $body);
415
    }
416
417
    /**
418
     * delete request implementation
419
     *
420
     * @param string $method the request method
421
     * @param string $uri    the uri
422
     * @param mixed  $params optional query string parameters
423
     * @param string $body   body/post parameters
424
     *
425
     * @return string
426
     * @throws \Puzzle\Exceptions\InvalidRequestException
427
     */
428 1
    protected function deleteRequest($method, $uri, $params, $body)
429
    {
430 1
        return $this->execute($method, $uri, $params, $body);
431
    }
432
433
    /**
434
     * head request implementation
435
     *
436
     * @param string $method the request method
437
     * @param string $uri    the uri
438
     * @param mixed  $params optional query string parameters
439
     * @param string $body   body/post parameters
440
     *
441
     * @return string
442
     * @throws \Puzzle\Exceptions\InvalidRequestException
443
     */
444 1
    protected function headRequest($method, $uri, $params, $body)
445
    {
446
        // head requests has no specific necessary requirements
447
448 1
        return $this->execute($method, $uri, $params, $body);
449
    }
450
451
    /**
452
     * post request implementation
453
     *
454
     * @param string $method the request method
455
     * @param string $uri    the uri
456
     * @param mixed  $params optional query string parameters
457
     * @param string $body   body/post parameters
458
     *
459
     * @return string
460
     * @throws \Puzzle\Exceptions\InvalidRequestException
461
     */
462 4
    protected function postRequest($method, $uri, $params, $body)
463
    {
464
        // post requests has no specific necessary requirements
465
466 4
        return $this->execute($method, $uri, $params, $body);
467
    }
468
469
    /**
470
     * put request implementation
471
     *
472
     * @param string $method the request method
473
     * @param string $uri    the uri
474
     * @param mixed  $params optional query string parameters
475
     * @param string $body   body/post parameters
476
     *
477
     * @return string
478
     * @throws \Puzzle\Exceptions\InvalidRequestException
479
     */
480 2
    protected function putRequest($method, $uri, $params, $body)
481
    {
482 2
        $this->checkBody($body, $method);
483
484
        // put requests requires content-length header
485 2
        $this->setHttpHeader('Content-Length: ' . strlen($body));
486
487 2
        return $this->execute($method, $uri, $params, $body);
488
    }
489
490
    /**
491
     * checks if body is not null
492
     *
493
     * @param mixed  $body   the body or null
494
     * @param string $method the request method
495
     *
496
     * @return void
497
     * @throws InvalidRequestException
498
     */
499 4
    protected function checkBody($body, $method)
500
    {
501 4
        if (is_null($body)) {
502 1
            throw new InvalidRequestException("body is required for '{$method}' requests");
503
        }
504 3
    }
505
506
    /**
507
     * Executes the curl request
508
     *
509
     * @param string $method the request method
510
     * @param string $uri    the uri
511
     * @param mixed  $params optional query string parameters
512
     * @param string $body   body/post parameters
513
     *
514
     * @return mixed
515
     * @throws Exceptions\ClientErrorException
516
     * @throws Exceptions\ServerErrorException
517
     * @throws Exceptions\TransportException
518
     */
519 9
    protected function execute($method, $uri, $params, $body)
520
    {
521 9
        $this->setMethod(strtoupper($method));
522 9
        $url = $this->buildUrl($uri, $params);
523 9
        $this->setUrl($url);
524 9
        $this->setBody($body);
525
526 9
        curl_setopt_array($this->getHandle(), $this->getOptions());
527
528 9
        if (!is_null($this->getHttpHeaders())) {
529 9
            curl_setopt($this->getHandle(), CURLOPT_HTTPHEADER, $this->getHttpHeaders());
530 9
        }
531
532
        // wrap curl_exec for easier unit testing
533 9
        $result = $this->curlExec();
534
535 9
        $response = $this->prepareResponse($result);
536
        
537 7
        return $response;
538
    }
539
540
    /**
541
     * prepares the response
542
     *
543
     * @param string $result the curl result
544
     *
545
     * @return array
546
     * @throws ClientErrorException
547
     * @throws ConnectionException
548
     * @throws ServerErrorException
549
     */
550 9
    protected function prepareResponse($result)
551
    {
552 9
        $this->checkForCurlErrors();
553
554 9
        $response = array();
555 9
        $response["data"] = $this->getSerializer()->deserialize($result);
556 9
        $response["status"] = $this->getStatusCode();
557
558
559 9
        if ($response['status'] >= 400 && $response['status'] < 500) {
560 1
            $statusCode = $response['status'];
561 1
            $exceptionText = "{$statusCode} Client Exception: {$result}";
562
563 1
            throw new ClientErrorException($exceptionText, $statusCode);
564 8
        } else if ($response['status'] >= 500) {
565 1
            $statusCode = $response['status'];
566 1
            $exceptionText = "{$statusCode} Server Exception: {$result}";
567
568 1
            throw new ServerErrorException($exceptionText, $statusCode);
569
        }
570
571 7
        return $response;
572
    }
573
574
    /**
575
     * executes curl_exec
576
     *
577
     * @return mixed
578
     */
579
    protected function curlExec()
580
    {
581
        return curl_exec($this->getHandle());
582
    }
583
584
    /**
585
     * checks if a  curl error occurred
586
     *
587
     * @return void
588
     * @throws ConnectionException
589
     */
590
    protected function checkForCurlErrors()
591
    {
592
        if (curl_errno($this->getHandle())) {
593
            $exceptionText = "Connection Error: " . curl_error($this->getHandle());
594
            throw new ConnectionException($exceptionText);
595
        }
596
    }
597
598
    /**
599
     * Enables ssl for the connection
600
     *
601
     * @param bool $strict optional value if ssl should be strict (check server certificate)
602
     *
603
     * @return void
604
     */
605 1
    public function enableSSL($strict = false)
606
    {
607 1
        $this->setScheme(self::SCHEME_SSL);
608 1
        if ($strict === false) {
609 1
            $this->setOption(CURLOPT_SSL_VERIFYPEER, 0);
610 1
            $this->setOption(CURLOPT_SSL_VERIFYHOST, 0);
611 1
        } else {
612 1
            $this->setOption(CURLOPT_SSL_VERIFYPEER, 0);
613 1
            $this->setOption(CURLOPT_SSL_VERIFYHOST, 1);
614
        }
615 1
    }
616
617
    /**
618
     * Disables ssl for the connection
619
     *
620
     * @return void
621
     */
622 1
    public function disableSSL()
623
    {
624 1
        $this->scheme = self::SCHEME_PLAIN;
625 1
    }
626
627
    /**
628
     * Sets the http/https scheme string
629
     *
630
     * @param string $scheme http/http scheme string
631
     *
632
     * @return void
633
     */
634 1
    protected function setScheme($scheme)
635
    {
636 1
        $this->scheme = $scheme;
637 1
    }
638
639
    /**
640
     * Sets a http option (e.g. use strict ssl)
641
     *
642
     * @param integer $key   the curl option key
643
     * @param integer $value the value to set
644
     *
645
     * @return void
646
     */
647 1
    protected function setOption($key, $value)
648
    {
649 1
        $this->httpOptions[$key] = $value;
650 1
    }
651
652
    /**
653
     * Returns all set http options as array
654
     *
655
     * @return array
656
     */
657 10
    protected function getOptions()
658
    {
659 10
        return $this->httpOptions;
660
    }
661
662
    /**
663
     * build complete url
664
     *
665
     * @param string $uri    uri string
666
     * @param mixed  $params query string parameters
667
     *
668
     * @return string
669
     */
670 10
    protected function buildUrl($uri, $params)
671
    {
672 10
        $host = $this->buildHostString();
673
674 10
        if (strpos($uri, "/") !== 0) {
675 7
            $uri = "/" . $uri;
676 7
        }
677
678 10
        $url = $host . $uri;
679
680
681 10
        if ($params === null) {
682 7
            $params = array();
683 7
        }
684
685 10
        $url .= $this->buildQueryString($params);
686
687 10
        return $url;
688
    }
689
690
    /**
691
     * build the query string
692
     *
693
     * @param array $params query string parameters
694
     *
695
     * @return string
696
     */
697 11
    protected function buildQueryString(array $params)
698
    {
699 11
        $qs = "";
700 11
        foreach ($params as $key => $value) {
701 4
            if ($qs === "") {
702 4
                $qs = "?";
703 4
            } else {
704 4
                $qs .= "&&";
705
            }
706 4
            $qs .= $key . "=" . $value;
707 11
        }
708 11
        return $qs;
709
    }
710
711
    /**
712
     * build host string (scheme - hostname/ip - (optional) port
713
     *
714
     * @return string
715
     *
716
     * @throws Exceptions\ConfigurationException
717
     */
718 9
    protected function buildHostString()
719
    {
720 9
        $scheme = $this->getScheme();
721 9
        $host = $this->getHost();
722 9
        $port = $this->getPort();
723
724 9
        $hostString = $this->prepareHost($scheme, $host);
725
726 9
        if (!is_null($port)) {
727 9
            $hostString = $hostString . ":" . $port;
728 9
        }
729
730 9
        return $hostString;
731
    }
732
733
    /**
734
     * Adds given scheme to hostname
735
     *
736
     * @param string $scheme the http scheme
737
     * @param string $host   the hostname
738
     *
739
     * @return string
740
     * @throws Exceptions\ConfigurationException
741
     */
742 9
    protected function prepareHost($scheme, $host)
743
    {
744 9
        $host = $this->stripScheme($host);
745
746 9
        if (substr($host, -1) === "/") {
747 9
            $host = substr($host, 0, -1);
748 9
        }
749
750 9
        return $scheme . $host;
751
    }
752
753
    /**
754
     * strip http/https scheme from hostname
755
     *
756
     * @param string $host the hostname
757
     *
758
     * @return string
759
     */
760 10
    protected function stripScheme($host)
761
    {
762 10
        return preg_replace("(^https?://)", "", $host);
763
    }
764
765
    /**
766
     * Returns scheme string
767
     *
768
     * @return string
769
     */
770 11
    protected function getScheme()
771
    {
772 11
        return $this->scheme;
773
    }
774
775
    /**
776
     * Returns hostname
777
     *
778
     * @return string
779
     */
780 9
    protected function getHost()
781
    {
782 9
        return $this->host;
783
    }
784
785
    /**
786
     * Returns port
787
     *
788
     * @return int
789
     */
790 9
    protected function getPort()
791
    {
792 9
        return $this->port;
793
    }
794
795
    /**
796
     * Sets given url as curl "url" parameter
797
     *
798
     * @param string $url complete url to query
799
     *
800
     * @return void
801
     */
802 9
    protected function setUrl($url)
803
    {
804 9
        curl_setopt($this->getHandle(), CURLOPT_URL, $url);
805 9
    }
806
807
    /**
808
     * Sets given request method as curl "request" parameter
809
     *
810
     * @param string $value the request method
811
     *
812
     * @return void
813
     */
814 9
    protected function setMethod($value)
815
    {
816 9
        curl_setopt($this->getHandle(), CURLOPT_CUSTOMREQUEST, $value);
817 9
    }
818
819
    /**
820
     * Sets given string as curl post body
821
     *
822
     * @param string $body the "post" body
823
     *
824
     * @return void
825
     */
826 9
    protected function setBody($body)
827
    {
828 9
        curl_setopt($this->getHandle(), CURLOPT_POSTFIELDS, $body);
829 9
    }
830
831
    /**
832
     * Adds a new http header to header list
833
     *
834
     * @param string $header http header to set
835
     *
836
     * @return void
837
     */
838 3
    public function setHttpHeader($header)
839
    {
840 3
        $headers = $this->getHttpHeaders();
841 3
        $headers[] = $header;
842 3
        $this->setHttpHeaders($headers);
843
844 3
    }
845
846
    /**
847
     * Returns http headers as array
848
     *
849
     * @return array
850
     */
851 10
    protected function getHttpHeaders()
852
    {
853 10
        return $this->httpHeaders;
854
    }
855
856
    /**
857
     * Saving given array of http headers to attributes
858
     *
859
     * @param array $headers http headers as array
860
     *
861
     * @return void
862
     */
863 3
    protected function setHttpHeaders(array $headers)
864
    {
865 3
        $this->httpHeaders = $headers;
866 3
    }
867
}
868