Passed
Pull Request — master (#59)
by Raúl
05:19 queued 01:21
created

Request::_curlPrep()   F

Complexity

Conditions 27
Paths > 20000

Size

Total Lines 129
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 27
eloc 69
c 1
b 0
f 0
nc 1769505
nop 0
dl 0
loc 129
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Httpful;
4
5
use Httpful\Exception\ConnectionErrorException;
6
7
/**
8
 * Clean, simple class for sending HTTP requests
9
 * in PHP.
10
 *
11
 * There is an emphasis of readability without loosing concise
12
 * syntax.  As such, you will notice that the library lends
13
 * itself very nicely to "chaining".  You will see several "alias"
14
 * methods: more readable method definitions that wrap
15
 * their more concise counterparts.  You will also notice
16
 * no public constructor.  This two adds to the readability
17
 * and "chainabilty" of the library.
18
 *
19
 * @author Nate Good <[email protected]>
20
 */
21
class Request
22
{
23
24
    // Option constants
25
    const SERIALIZE_PAYLOAD_NEVER   = 0;
26
    const SERIALIZE_PAYLOAD_ALWAYS  = 1;
27
    const SERIALIZE_PAYLOAD_SMART   = 2;
28
29
    const MAX_REDIRECTS_DEFAULT     = 25;
30
31
    public $uri,
32
           $method                  = Http::GET,
33
           $headers                 = array(),
34
           $raw_headers             = '',
35
           $strict_ssl              = false,
36
           $content_type,
37
           $expected_type,
38
           $additional_curl_opts    = array(),
39
           $auto_parse              = true,
40
           $serialize_payload_method = self::SERIALIZE_PAYLOAD_SMART,
41
           $username,
42
           $password,
43
           $serialized_payload,
44
           $payload,
45
           $parse_callback,
46
           $error_callback,
47
           $send_callback,
48
           $follow_redirects        = false,
49
           $max_redirects           = self::MAX_REDIRECTS_DEFAULT,
50
           $payload_serializers     = array();
51
52
    // Options
53
    // private $_options = array(
54
    //     'serialize_payload_method' => self::SERIALIZE_PAYLOAD_SMART
55
    //     'auto_parse' => true
56
    // );
57
58
    // Curl Handle
59
    public $_ch,
60
           $_debug;
61
62
    // Template Request object
63
    private static $_template;
64
65
    /**
66
     * We made the constructor private to force the factory style.  This was
67
     * done to keep the syntax cleaner and better the support the idea of
68
     * "default templates".  Very basic and flexible as it is only intended
69
     * for internal use.
70
     * @param array $attrs hash of initial attribute values
71
     */
72
    private function __construct($attrs = null)
73
    {
74
        if (!is_array($attrs)) return;
75
        foreach ($attrs as $attr => $value) {
76
            $this->$attr = $value;
77
        }
78
    }
79
80
    // Defaults Management
81
82
    /**
83
     * Let's you configure default settings for this
84
     * class from a template Request object.  Simply construct a
85
     * Request object as much as you want to and then pass it to
86
     * this method.  It will then lock in those settings from
87
     * that template object.
88
     * The most common of which may be default mime
89
     * settings or strict ssl settings.
90
     * Again some slight memory overhead incurred here but in the grand
91
     * scheme of things as it typically only occurs once
92
     * @param Request $template
93
     */
94
    public static function ini(Request $template)
95
    {
96
        self::$_template = clone $template;
97
    }
98
99
    /**
100
     * Reset the default template back to the
101
     * library defaults.
102
     */
103
    public static function resetIni()
104
    {
105
        self::_initializeDefaults();
106
    }
107
108
    /**
109
     * Get default for a value based on the template object
110
     * @param string|null $attr Name of attribute (e.g. mime, headers)
111
     *    if null just return the whole template object;
112
     * @return mixed default value
113
     */
114
    public static function d($attr)
115
    {
116
        return isset($attr) ? self::$_template->$attr : self::$_template;
117
    }
118
119
    // Accessors
120
121
    /**
122
     * @return bool does the request have a timeout?
123
     */
124
    public function hasTimeout()
125
    {
126
        return isset($this->timeout);
127
    }
128
129
    /**
130
     * @return bool has the internal curl request been initialized?
131
     */
132
    public function hasBeenInitialized()
133
    {
134
        return isset($this->_ch);
135
    }
136
137
    /**
138
     * @return bool Is this request setup for basic auth?
139
     */
140
    public function hasBasicAuth()
141
    {
142
        return isset($this->password) && isset($this->username);
143
    }
144
145
    /**
146
     * @return bool Is this request setup for digest auth?
147
     */
148
    public function hasDigestAuth()
149
    {
150
        return isset($this->password) && isset($this->username) && $this->additional_curl_opts[CURLOPT_HTTPAUTH] == CURLAUTH_DIGEST;
151
    }
152
153
    /**
154
     * Specify a HTTP timeout
155
     * @param float|int $timeout seconds to timeout the HTTP call
156
     * @return Request
157
     */
158
    public function timeout($timeout)
159
    {
160
        $this->timeout = $timeout;
0 ignored issues
show
Bug Best Practice introduced by
The property timeout does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
161
        return $this;
162
    }
163
164
    // alias timeout
165
    public function timeoutIn($seconds)
166
    {
167
        return $this->timeout($seconds);
168
    }
169
170
    /**
171
     * If the response is a 301 or 302 redirect, automatically
172
     * send off another request to that location
173
     * @param bool|int $follow follow or not to follow or maximal number of redirects
174
     * @return Request
175
     */
176
    public function followRedirects($follow = true)
177
    {
178
        $this->max_redirects = $follow === true ? self::MAX_REDIRECTS_DEFAULT : max(0, $follow);
179
        $this->follow_redirects = (bool) $follow;
180
        return $this;
181
    }
182
183
    /**
184
     * @see Request::followRedirects()
185
     * @return Request
186
     */
187
    public function doNotFollowRedirects()
188
    {
189
        return $this->followRedirects(false);
190
    }
191
192
    /**
193
     * Actually send off the request, and parse the response
194
     * @return Response with parsed results
195
     * @throws ConnectionErrorException when unable to parse or communicate w server
196
     */
197
    public function send()
198
    {
199
        if (!$this->hasBeenInitialized())
200
            $this->_curlPrep();
201
202
        $result = curl_exec($this->_ch);
203
204
        $response = $this->buildResponse($result);
205
206
        curl_close($this->_ch);
207
208
        return $response;
209
    }
210
    public function sendIt()
211
    {
212
        return $this->send();
213
    }
214
215
    // Setters
216
217
    /**
218
     * @param string $uri
219
     * @return Request
220
     */
221
    public function uri($uri)
222
    {
223
        $this->uri = $uri;
224
        return $this;
225
    }
226
227
    /**
228
     * User Basic Auth.
229
     * Only use when over SSL/TSL/HTTPS.
230
     * @param string $username
231
     * @param string $password
232
     * @return Request
233
     */
234
    public function basicAuth($username, $password)
235
    {
236
        $this->username = $username;
237
        $this->password = $password;
238
        return $this;
239
    }
240
    // @alias of basicAuth
241
    public function authenticateWith($username, $password)
242
    {
243
        return $this->basicAuth($username, $password);
244
    }
245
    // @alias of basicAuth
246
    public function authenticateWithBasic($username, $password)
247
    {
248
        return $this->basicAuth($username, $password);
249
    }
250
251
    // @alias of ntlmAuth
252
    public function authenticateWithNTLM($username, $password)
253
    {
254
        return $this->ntlmAuth($username, $password);
255
    }
256
257
    public function ntlmAuth($username, $password)
258
    {
259
        $this->addOnCurlOption(CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
260
        return $this->basicAuth($username, $password);
261
    }
262
263
    /**
264
     * User Digest Auth.
265
     * @param string $username
266
     * @param string $password
267
     * @return Request
268
     */
269
    public function digestAuth($username, $password)
270
    {
271
        $this->addOnCurlOption(CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
272
        return $this->basicAuth($username, $password);
273
    }
274
275
    // @alias of digestAuth
276
    public function authenticateWithDigest($username, $password)
277
    {
278
        return $this->digestAuth($username, $password);
279
    }
280
281
    /**
282
     * @return bool is this request setup for client side cert?
283
     */
284
    public function hasClientSideCert()
285
    {
286
        return isset($this->client_cert) && isset($this->client_key);
287
    }
288
289
    /**
290
     * Use Client Side Cert Authentication
291
     * @param string $key file path to client key
292
     * @param string $cert file path to client cert
293
     * @param string $passphrase for client key
294
     * @param string $encoding default PEM
295
     * @return Request
296
     */
297
    public function clientSideCert($cert, $key, $passphrase = null, $encoding = 'PEM')
298
    {
299
        $this->client_cert          = $cert;
0 ignored issues
show
Bug Best Practice introduced by
The property client_cert does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
300
        $this->client_key           = $key;
0 ignored issues
show
Bug Best Practice introduced by
The property client_key does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
301
        $this->client_passphrase    = $passphrase;
0 ignored issues
show
Bug Best Practice introduced by
The property client_passphrase does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
302
        $this->client_encoding      = $encoding;
0 ignored issues
show
Bug Best Practice introduced by
The property client_encoding does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
303
304
        return $this;
305
    }
306
    // @alias of basicAuth
307
    public function authenticateWithCert($cert, $key, $passphrase = null, $encoding = 'PEM')
308
    {
309
        return $this->clientSideCert($cert, $key, $passphrase, $encoding);
310
    }
311
312
    /**
313
     * Set the body of the request
314
     * @param mixed $payload
315
     * @param string $mimeType currently, sets the sends AND expects mime type although this
316
     *    behavior may change in the next minor release (as it is a potential breaking change).
317
     * @return Request
318
     */
319
    public function body($payload, $mimeType = null)
320
    {
321
        $this->mime($mimeType);
322
        $this->payload = $payload;
323
        // Iserntentially don't call _serializePayload yet.  Wait until
324
        // we actually send off the request to convert payload to string.
325
        // At that time, the `serialized_payload` is set accordingly.
326
        return $this;
327
    }
328
329
    /**
330
     * Helper function to set the Content type and Expected as same in
331
     * one swoop
332
     * @param string $mime mime type to use for content type and expected return type
333
     * @return Request
334
     */
335
    public function mime($mime)
336
    {
337
        if (empty($mime)) return $this;
338
        $this->content_type = $this->expected_type = Mime::getFullMime($mime);
339
        if ($this->isUpload()) {
340
            $this->neverSerializePayload();
341
        }
342
        return $this;
343
    }
344
    // @alias of mime
345
    public function sendsAndExpectsType($mime)
346
    {
347
        return $this->mime($mime);
348
    }
349
    // @alias of mime
350
    public function sendsAndExpects($mime)
351
    {
352
        return $this->mime($mime);
353
    }
354
355
    /**
356
     * Set the method.  Shouldn't be called often as the preferred syntax
357
     * for instantiation is the method specific factory methods.
358
     * @param string $method
359
     * @return Request
360
     */
361
    public function method($method)
362
    {
363
        if (empty($method)) return $this;
364
        $this->method = $method;
365
        return $this;
366
    }
367
368
    /**
369
     * @param string $mime
370
     * @return Request
371
     */
372
    public function expects($mime)
373
    {
374
        if (empty($mime)) return $this;
375
        $this->expected_type = Mime::getFullMime($mime);
376
        return $this;
377
    }
378
    // @alias of expects
379
    public function expectsType($mime)
380
    {
381
        return $this->expects($mime);
382
    }
383
384
    public function attach($files)
385
    {
386
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
387
        foreach ($files as $key => $file) {
388
            $mimeType = finfo_file($finfo, $file);
389
            if (function_exists('curl_file_create')) {
390
                $this->payload[$key] = curl_file_create($file, $mimeType);
391
            } else {
392
                $this->payload[$key] = '@' . $file;
393
	            if ($mimeType) {
394
		            $this->payload[$key] .= ';type=' . $mimeType;
395
	            }
396
            }
397
        }
398
        $this->sendsType(Mime::UPLOAD);
399
        return $this;
400
    }
401
402
    /**
403
     * @param string $mime
404
     * @return Request
405
     */
406
    public function contentType($mime)
407
    {
408
        if (empty($mime)) return $this;
409
        $this->content_type  = Mime::getFullMime($mime);
410
        if ($this->isUpload()) {
411
            $this->neverSerializePayload();
412
        }
413
        return $this;
414
    }
415
    // @alias of contentType
416
    public function sends($mime)
417
    {
418
        return $this->contentType($mime);
419
    }
420
    // @alias of contentType
421
    public function sendsType($mime)
422
    {
423
        return $this->contentType($mime);
424
    }
425
426
    /**
427
     * Do we strictly enforce SSL verification?
428
     * @param bool $strict
429
     * @return Request
430
     */
431
    public function strictSSL($strict)
432
    {
433
        $this->strict_ssl = $strict;
434
        return $this;
435
    }
436
    public function withoutStrictSSL()
437
    {
438
        return $this->strictSSL(false);
439
    }
440
    public function withStrictSSL()
441
    {
442
        return $this->strictSSL(true);
443
    }
444
445
    /**
446
     * Use proxy configuration
447
     * @param string $proxy_host Hostname or address of the proxy
448
     * @param int $proxy_port Port of the proxy. Default 80
449
     * @param string $auth_type Authentication type or null. Accepted values are CURLAUTH_BASIC, CURLAUTH_NTLM. Default null, no authentication
450
     * @param string $auth_username Authentication username. Default null
451
     * @param string $auth_password Authentication password. Default null
452
     * @return Request
453
     */
454
    public function useProxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null, $proxy_type = Proxy::HTTP)
455
    {
456
        $this->addOnCurlOption(CURLOPT_PROXY, "{$proxy_host}:{$proxy_port}");
457
        $this->addOnCurlOption(CURLOPT_PROXYTYPE, $proxy_type);
458
        if (in_array($auth_type, array(CURLAUTH_BASIC,CURLAUTH_NTLM))) {
459
            $this->addOnCurlOption(CURLOPT_PROXYAUTH, $auth_type)
460
                ->addOnCurlOption(CURLOPT_PROXYUSERPWD, "{$auth_username}:{$auth_password}");
461
        }
462
        return $this;
463
    }
464
465
    /**
466
     * Shortcut for useProxy to configure SOCKS 4 proxy
467
     * @see Request::useProxy
468
     * @return Request
469
     */
470
    public function useSocks4Proxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null)
471
    {
472
        return $this->useProxy($proxy_host, $proxy_port, $auth_type, $auth_username, $auth_password, Proxy::SOCKS4);
473
    }
474
475
    /**
476
     * Shortcut for useProxy to configure SOCKS 5 proxy
477
     * @see Request::useProxy
478
     * @return Request
479
     */
480
    public function useSocks5Proxy($proxy_host, $proxy_port = 80, $auth_type = null, $auth_username = null, $auth_password = null)
481
    {
482
        return $this->useProxy($proxy_host, $proxy_port, $auth_type, $auth_username, $auth_password, Proxy::SOCKS5);
483
    }
484
485
    /**
486
     * @return bool is this request setup for using proxy?
487
     */
488
    public function hasProxy()
489
    {
490
        return isset($this->additional_curl_opts[CURLOPT_PROXY]) && is_string($this->additional_curl_opts[CURLOPT_PROXY]);
491
    }
492
493
    /**
494
     * Determine how/if we use the built in serialization by
495
     * setting the serialize_payload_method
496
     * The default (SERIALIZE_PAYLOAD_SMART) is...
497
     *  - if payload is not a scalar (object/array)
498
     *    use the appropriate serialize method according to
499
     *    the Content-Type of this request.
500
     *  - if the payload IS a scalar (int, float, string, bool)
501
     *    than just return it as is.
502
     * When this option is set SERIALIZE_PAYLOAD_ALWAYS,
503
     * it will always use the appropriate
504
     * serialize option regardless of whether payload is scalar or not
505
     * When this option is set SERIALIZE_PAYLOAD_NEVER,
506
     * it will never use any of the serialization methods.
507
     * Really the only use for this is if you want the serialize methods
508
     * to handle strings or not (e.g. Blah is not valid JSON, but "Blah"
509
     * is).  Forcing the serialization helps prevent that kind of error from
510
     * happening.
511
     * @param int $mode
512
     * @return Request
513
     */
514
    public function serializePayload($mode)
515
    {
516
        $this->serialize_payload_method = $mode;
517
        return $this;
518
    }
519
520
    /**
521
     * @see Request::serializePayload()
522
     * @return Request
523
     */
524
    public function neverSerializePayload()
525
    {
526
        return $this->serializePayload(self::SERIALIZE_PAYLOAD_NEVER);
527
    }
528
529
    /**
530
     * This method is the default behavior
531
     * @see Request::serializePayload()
532
     * @return Request
533
     */
534
    public function smartSerializePayload()
535
    {
536
        return $this->serializePayload(self::SERIALIZE_PAYLOAD_SMART);
537
    }
538
539
    /**
540
     * @see Request::serializePayload()
541
     * @return Request
542
     */
543
    public function alwaysSerializePayload()
544
    {
545
        return $this->serializePayload(self::SERIALIZE_PAYLOAD_ALWAYS);
546
    }
547
548
    /**
549
     * Add an additional header to the request
550
     * Can also use the cleaner syntax of
551
     * $Request->withMyHeaderName($my_value);
552
     * @see Request::__call()
553
     *
554
     * @param string $header_name
555
     * @param string $value
556
     * @return Request
557
     */
558
    public function addHeader($header_name, $value)
559
    {
560
        $this->headers[$header_name] = $value;
561
        return $this;
562
    }
563
564
    /**
565
     * Add group of headers all at once.  Note: This is
566
     * here just as a convenience in very specific cases.
567
     * The preferred "readable" way would be to leverage
568
     * the support for custom header methods.
569
     * @param array $headers
570
     * @return Request
571
     */
572
    public function addHeaders(array $headers)
573
    {
574
        foreach ($headers as $header => $value) {
575
            $this->addHeader($header, $value);
576
        }
577
        return $this;
578
    }
579
580
    /**
581
     * @param bool $auto_parse perform automatic "smart"
582
     *    parsing based on Content-Type or "expectedType"
583
     *    If not auto parsing, Response->body returns the body
584
     *    as a string.
585
     * @return Request
586
     */
587
    public function autoParse($auto_parse = true)
588
    {
589
        $this->auto_parse = $auto_parse;
590
        return $this;
591
    }
592
593
    /**
594
     * @see Request::autoParse()
595
     * @return Request
596
     */
597
    public function withoutAutoParsing()
598
    {
599
        return $this->autoParse(false);
600
    }
601
602
    /**
603
     * @see Request::autoParse()
604
     * @return Request
605
     */
606
    public function withAutoParsing()
607
    {
608
        return $this->autoParse(true);
609
    }
610
611
    /**
612
     * Use a custom function to parse the response.
613
     * @param \Closure $callback Takes the raw body of
614
     *    the http response and returns a mixed
615
     * @return Request
616
     */
617
    public function parseWith(\Closure $callback)
618
    {
619
        $this->parse_callback = $callback;
620
        return $this;
621
    }
622
623
    /**
624
     * @see Request::parseResponsesWith()
625
     * @param \Closure $callback
626
     * @return Request
627
     */
628
    public function parseResponsesWith(\Closure $callback)
629
    {
630
        return $this->parseWith($callback);
631
    }
632
633
    /**
634
     * Callback called to handle HTTP errors. When nothing is set, defaults
635
     * to logging via `error_log`
636
     * @param \Closure $callback (string $error)
637
     * @return Request
638
     */
639
    public function whenError(\Closure $callback)
640
    {
641
        $this->error_callback = $callback;
642
        return $this;
643
    }
644
645
    /**
646
     * Callback invoked after payload has been serialized but before
647
     * the request has been built.
648
     * @param \Closure $callback (Request $request)
649
     * @return Request
650
     */
651
    public function beforeSend(\Closure $callback)
652
    {
653
        $this->send_callback = $callback;
654
        return $this;
655
    }
656
657
    /**
658
     * Register a callback that will be used to serialize the payload
659
     * for a particular mime type.  When using "*" for the mime
660
     * type, it will use that parser for all responses regardless of the mime
661
     * type.  If a custom '*' and 'application/json' exist, the custom
662
     * 'application/json' would take precedence over the '*' callback.
663
     *
664
     * @param string $mime mime type we're registering
665
     * @param \Closure $callback takes one argument, $payload,
666
     *    which is the payload that we'll be
667
     * @return Request
668
     */
669
    public function registerPayloadSerializer($mime, \Closure $callback)
670
    {
671
        $this->payload_serializers[Mime::getFullMime($mime)] = $callback;
672
        return $this;
673
    }
674
675
    /**
676
     * @see Request::registerPayloadSerializer()
677
     * @param \Closure $callback
678
     * @return Request
679
     */
680
    public function serializePayloadWith(\Closure $callback)
681
    {
682
        return $this->registerPayloadSerializer('*', $callback);
683
    }
684
685
    /**
686
     * Magic method allows for neatly setting other headers in a
687
     * similar syntax as the other setters.  This method also allows
688
     * for the sends* syntax.
689
     * @param string $method "missing" method name called
690
     *    the method name called should be the name of the header that you
691
     *    are trying to set in camel case without dashes e.g. to set a
692
     *    header for Content-Type you would use contentType() or more commonly
693
     *    to add a custom header like X-My-Header, you would use xMyHeader().
694
     *    To promote readability, you can optionally prefix these methods with
695
     *    "with"  (e.g. withXMyHeader("blah") instead of xMyHeader("blah")).
696
     * @param array $args in this case, there should only ever be 1 argument provided
697
     *    and that argument should be a string value of the header we're setting
698
     * @return Request
699
     */
700
    public function __call($method, $args)
701
    {
702
        // This method supports the sends* methods
703
        // like sendsJSON, sendsForm
704
        //!method_exists($this, $method) &&
705
        if (substr($method, 0, 5) === 'sends') {
706
            $mime = strtolower(substr($method, 5));
707
            if (Mime::supportsMimeType($mime)) {
708
                $this->sends(Mime::getFullMime($mime));
709
                return $this;
710
            }
711
            // else {
712
            //     throw new \Exception("Unsupported Content-Type $mime");
713
            // }
714
        }
715
        if (substr($method, 0, 7) === 'expects') {
716
            $mime = strtolower(substr($method, 7));
717
            if (Mime::supportsMimeType($mime)) {
718
                $this->expects(Mime::getFullMime($mime));
719
                return $this;
720
            }
721
            // else {
722
            //     throw new \Exception("Unsupported Content-Type $mime");
723
            // }
724
        }
725
726
        // This method also adds the custom header support as described in the
727
        // method comments
728
        if (count($args) === 0)
729
            return;
730
731
        // Strip the sugar.  If it leads with "with", strip.
732
        // This is okay because: No defined HTTP headers begin with with,
733
        // and if you are defining a custom header, the standard is to prefix it
734
        // with an "X-", so that should take care of any collisions.
735
        if (substr($method, 0, 4) === 'with')
736
            $method = substr($method, 4);
737
738
        // Precede upper case letters with dashes, uppercase the first letter of method
739
        $header = ucwords(implode('-', preg_split('/([A-Z][^A-Z]*)/', $method, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)));
0 ignored issues
show
Bug introduced by
It seems like preg_split('/([A-Z][^A-Z...ul\PREG_SPLIT_NO_EMPTY) can also be of type false; however, parameter $pieces of implode() does only seem to accept array, 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

739
        $header = ucwords(implode('-', /** @scrutinizer ignore-type */ preg_split('/([A-Z][^A-Z]*)/', $method, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY)));
Loading history...
740
        $this->addHeader($header, $args[0]);
741
        return $this;
742
    }
743
744
    // Internal Functions
745
746
    /**
747
     * This is the default template to use if no
748
     * template has been provided.  The template
749
     * tells the class which default values to use.
750
     * While there is a slight overhead for object
751
     * creation once per execution (not once per
752
     * Request instantiation), it promotes readability
753
     * and flexibility within the class.
754
     */
755
    private static function _initializeDefaults()
756
    {
757
        // This is the only place you will
758
        // see this constructor syntax.  It
759
        // is only done here to prevent infinite
760
        // recusion.  Do not use this syntax elsewhere.
761
        // It goes against the whole readability
762
        // and transparency idea.
763
        self::$_template = new Request(array('method' => Http::GET));
764
765
        // This is more like it...
766
        self::$_template
767
            ->withoutStrictSSL();
768
    }
769
770
    /**
771
     * Set the defaults on a newly instantiated object
772
     * Doesn't copy variables prefixed with _
773
     * @return Request
774
     */
775
    private function _setDefaults()
776
    {
777
        if (!isset(self::$_template))
778
            self::_initializeDefaults();
779
        foreach (self::$_template as $k=>$v) {
780
            if ($k[0] != '_')
781
                $this->$k = $v;
782
        }
783
        return $this;
784
    }
785
786
    private function _error($error)
787
    {
788
        // TODO add in support for various Loggers that follow
789
        // PSR 3 https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
790
        if (isset($this->error_callback)) {
791
            $this->error_callback->__invoke($error);
792
        } else {
793
            error_log($error);
794
        }
795
    }
796
797
    /**
798
     * Factory style constructor works nicer for chaining.  This
799
     * should also really only be used internally.  The Request::get,
800
     * Request::post syntax is preferred as it is more readable.
801
     * @param string $method Http Method
802
     * @param string $mime Mime Type to Use
803
     * @return Request
804
     */
805
    public static function init($method = null, $mime = null)
806
    {
807
        // Setup our handlers, can call it here as it's idempotent
808
        Bootstrap::init();
809
810
        // Setup the default template if need be
811
        if (!isset(self::$_template))
812
            self::_initializeDefaults();
813
814
        $request = new Request();
815
        return $request
816
               ->_setDefaults()
817
               ->method($method)
818
               ->sendsType($mime)
819
               ->expectsType($mime);
820
    }
821
822
    /**
823
     * Does the heavy lifting.  Uses de facto HTTP
824
     * library cURL to set up the HTTP request.
825
     * Note: It does NOT actually send the request
826
     * @return Request
827
     * @throws \Exception
828
     */
829
    public function _curlPrep()
830
    {
831
        // Check for required stuff
832
        if (!isset($this->uri))
833
            throw new \Exception('Attempting to send a request before defining a URI endpoint.');
834
835
        if (isset($this->payload)) {
836
            $this->serialized_payload = $this->_serializePayload($this->payload);
837
        }
838
839
        if (isset($this->send_callback)) {
840
            call_user_func($this->send_callback, $this);
841
        }
842
843
        $ch = curl_init($this->uri);
844
845
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
0 ignored issues
show
Bug introduced by
It seems like $ch can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, 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

845
        curl_setopt(/** @scrutinizer ignore-type */ $ch, CURLOPT_CUSTOMREQUEST, $this->method);
Loading history...
846
        if ($this->method === Http::HEAD) {
847
            curl_setopt($ch, CURLOPT_NOBODY, true);
848
        }
849
850
        if ($this->hasBasicAuth()) {
851
            curl_setopt($ch, CURLOPT_USERPWD, $this->username . ':' . $this->password);
852
        }
853
854
        if ($this->hasClientSideCert()) {
855
856
            if (!file_exists($this->client_key))
857
                throw new \Exception('Could not read Client Key');
858
859
            if (!file_exists($this->client_cert))
860
                throw new \Exception('Could not read Client Certificate');
861
862
            curl_setopt($ch, CURLOPT_SSLCERTTYPE,   $this->client_encoding);
863
            curl_setopt($ch, CURLOPT_SSLKEYTYPE,    $this->client_encoding);
864
            curl_setopt($ch, CURLOPT_SSLCERT,       $this->client_cert);
865
            curl_setopt($ch, CURLOPT_SSLKEY,        $this->client_key);
866
            curl_setopt($ch, CURLOPT_SSLKEYPASSWD,  $this->client_passphrase);
867
            // curl_setopt($ch, CURLOPT_SSLCERTPASSWD,  $this->client_cert_passphrase);
868
        }
869
870
        if ($this->hasTimeout()) {
871
            if (defined('CURLOPT_TIMEOUT_MS')) {
872
                curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->timeout * 1000);
873
            } else {
874
                curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
875
            }
876
        }
877
878
        if ($this->follow_redirects) {
879
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
880
            curl_setopt($ch, CURLOPT_MAXREDIRS, $this->max_redirects);
881
        }
882
883
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->strict_ssl);
884
        // zero is safe for all curl versions
885
        $verifyValue = $this->strict_ssl + 0;
886
        //Support for value 1 removed in cURL 7.28.1 value 2 valid in all versions
887
        if ($verifyValue > 0) $verifyValue++;
888
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyValue);
889
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
890
891
        // https://github.com/nategood/httpful/issues/84
892
        // set Content-Length to the size of the payload if present
893
        if (isset($this->payload)) {
894
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->serialized_payload);
895
            if (!$this->isUpload()) {
896
                $this->headers['Content-Length'] =
897
                    $this->_determineLength($this->serialized_payload);
898
            }
899
        }
900
901
        $headers = array();
902
        // https://github.com/nategood/httpful/issues/37
903
        // Except header removes any HTTP 1.1 Continue from response headers
904
        $headers[] = 'Expect:';
905
906
        if (!isset($this->headers['User-Agent'])) {
907
            $headers[] = $this->buildUserAgent();
908
        }
909
910
        $headers[] = "Content-Type: {$this->content_type}";
911
912
        // allow custom Accept header if set
913
        if (!isset($this->headers['Accept'])) {
914
            // http://pretty-rfc.herokuapp.com/RFC2616#header.accept
915
            $accept = 'Accept: */*; q=0.5, text/plain; q=0.8, text/html;level=3;';
916
917
            if (!empty($this->expected_type)) {
918
                $accept .= "q=0.9, {$this->expected_type}";
919
            }
920
921
            $headers[] = $accept;
922
        }
923
924
        // Solve a bug on squid proxy, NONE/411 when miss content length
925
        if (!isset($this->headers['Content-Length']) && !$this->isUpload()) {
926
            $this->headers['Content-Length'] = 0;
927
        }
928
929
        foreach ($this->headers as $header => $value) {
930
            $headers[] = "$header: $value";
931
        }
932
933
        $url = \parse_url($this->uri);
934
        $path = (isset($url['path']) ? $url['path'] : '/').(isset($url['query']) ? '?'.$url['query'] : '');
935
        $this->raw_headers = "{$this->method} $path HTTP/1.1\r\n";
936
        $host = (isset($url['host']) ? $url['host'] : 'localhost').(isset($url['port']) ? ':'.$url['port'] : '');
937
        $this->raw_headers .= "Host: $host\r\n";
938
        $this->raw_headers .= \implode("\r\n", $headers);
939
        $this->raw_headers .= "\r\n";
940
941
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
942
943
        if ($this->_debug) {
944
            curl_setopt($ch, CURLOPT_VERBOSE, true);
945
        }
946
947
        curl_setopt($ch, CURLOPT_HEADER, 1);
948
949
        // If there are some additional curl opts that the user wants
950
        // to set, we can tack them in here
951
        foreach ($this->additional_curl_opts as $curlopt => $curlval) {
952
            curl_setopt($ch, $curlopt, $curlval);
953
        }
954
955
        $this->_ch = $ch;
956
957
        return $this;
958
    }
959
960
    /**
961
     * @param string $str payload
962
     * @return int length of payload in bytes
963
     */
964
    public function _determineLength($str)
965
    {
966
        if (function_exists('mb_strlen')) {
967
            return mb_strlen($str, '8bit');
968
        } else {
969
            return strlen($str);
970
        }
971
    }
972
973
    /**
974
     * @return bool
975
     */
976
    public function isUpload()
977
    {
978
        return Mime::UPLOAD == $this->content_type;
979
    }
980
981
    /**
982
     * @return string
983
     */
984
    public function buildUserAgent()
985
    {
986
        $user_agent = 'User-Agent: Httpful/' . Httpful::VERSION . ' (cURL/';
987
        $curl = \curl_version();
988
989
        if (isset($curl['version'])) {
990
            $user_agent .= $curl['version'];
991
        } else {
992
            $user_agent .= '?.?.?';
993
        }
994
995
        $user_agent .= ' PHP/'. PHP_VERSION . ' (' . PHP_OS . ')';
996
997
        if (isset($_SERVER['SERVER_SOFTWARE'])) {
998
            $user_agent .= ' ' . \preg_replace('~PHP/[\d\.]+~U', '',
999
                $_SERVER['SERVER_SOFTWARE']);
1000
        } else {
1001
            if (isset($_SERVER['TERM_PROGRAM'])) {
1002
                $user_agent .= " {$_SERVER['TERM_PROGRAM']}";
1003
            }
1004
1005
            if (isset($_SERVER['TERM_PROGRAM_VERSION'])) {
1006
                $user_agent .= "/{$_SERVER['TERM_PROGRAM_VERSION']}";
1007
            }
1008
        }
1009
1010
        if (isset($_SERVER['HTTP_USER_AGENT'])) {
1011
            $user_agent .= " {$_SERVER['HTTP_USER_AGENT']}";
1012
        }
1013
1014
        $user_agent .= ')';
1015
1016
        return $user_agent;
1017
    }
1018
1019
    /**
1020
     * Takes a curl result and generates a Response from it
1021
     * @return Response
1022
     */
1023
    public function buildResponse($result) {
1024
        if ($result === false) {
1025
            if ($curlErrorNumber = curl_errno($this->_ch)) {
1026
                $curlErrorString = curl_error($this->_ch);
1027
                $this->_error($curlErrorString);
1028
                throw new ConnectionErrorException('Unable to connect to "'.$this->uri.'": ' . $curlErrorNumber . ' ' . $curlErrorString);
1029
            }
1030
1031
            $this->_error('Unable to connect to "'.$this->uri.'".');
1032
            throw new ConnectionErrorException('Unable to connect to "'.$this->uri.'".');
1033
        }
1034
1035
        $info = curl_getinfo($this->_ch);
1036
1037
        // Remove the "HTTP/1.x 200 Connection established" string and any other headers added by proxy
1038
        $proxy_regex = "/HTTP\/1\.[01] 200 Connection established.*?\r\n\r\n/si";
1039
        if ($this->hasProxy() && preg_match($proxy_regex, $result)) {
1040
            $result = preg_replace($proxy_regex, '', $result);
1041
        }
1042
1043
        $response = explode("\r\n\r\n", $result, 2 + $info['redirect_count']);
1044
1045
        $body = array_pop($response);
1046
        $headers = array_pop($response);
1047
1048
        return new Response($body, $headers, $this, $info);
1049
    }
1050
1051
    /**
1052
     * Semi-reluctantly added this as a way to add in curl opts
1053
     * that are not otherwise accessible from the rest of the API.
1054
     * @param string $curlopt
1055
     * @param mixed $curloptval
1056
     * @return Request
1057
     */
1058
    public function addOnCurlOption($curlopt, $curloptval)
1059
    {
1060
        $this->additional_curl_opts[$curlopt] = $curloptval;
1061
        return $this;
1062
    }
1063
1064
    /**
1065
     * Turn payload from structured data into
1066
     * a string based on the current Mime type.
1067
     * This uses the auto_serialize option to determine
1068
     * it's course of action.  See serialize method for more.
1069
     * Renamed from _detectPayload to _serializePayload as of
1070
     * 2012-02-15.
1071
     *
1072
     * Added in support for custom payload serializers.
1073
     * The serialize_payload_method stuff still holds true though.
1074
     * @see Request::registerPayloadSerializer()
1075
     *
1076
     * @param mixed $payload
1077
     * @return string
1078
     */
1079
    private function _serializePayload($payload)
1080
    {
1081
        if (empty($payload) || $this->serialize_payload_method === self::SERIALIZE_PAYLOAD_NEVER)
1082
            return $payload;
1083
1084
        // When we are in "smart" mode, don't serialize strings/scalars, assume they are already serialized
1085
        if ($this->serialize_payload_method === self::SERIALIZE_PAYLOAD_SMART && is_scalar($payload))
1086
            return $payload;
1087
1088
        // Use a custom serializer if one is registered for this mime type
1089
        if (isset($this->payload_serializers['*']) || isset($this->payload_serializers[$this->content_type])) {
1090
            $key = isset($this->payload_serializers[$this->content_type]) ? $this->content_type : '*';
1091
            return call_user_func($this->payload_serializers[$key], $payload);
1092
        }
1093
1094
        return Httpful::get($this->content_type)->serialize($payload);
1095
    }
1096
1097
    /**
1098
     * HTTP Method Get
1099
     * @param string $uri optional uri to use
1100
     * @param string $mime expected
1101
     * @return Request
1102
     */
1103
    public static function get($uri, $mime = null)
1104
    {
1105
        return self::init(Http::GET)->uri($uri)->mime($mime);
1106
    }
1107
1108
1109
    /**
1110
     * Like Request:::get, except that it sends off the request as well
1111
     * returning a response
1112
     * @param string $uri optional uri to use
1113
     * @param string $mime expected
1114
     * @return Response
1115
     */
1116
    public static function getQuick($uri, $mime = null)
1117
    {
1118
        return self::get($uri, $mime)->send();
1119
    }
1120
1121
    /**
1122
     * HTTP Method Post
1123
     * @param string $uri optional uri to use
1124
     * @param string $payload data to send in body of request
1125
     * @param string $mime MIME to use for Content-Type
1126
     * @return Request
1127
     */
1128
    public static function post($uri, $payload = null, $mime = null)
1129
    {
1130
        return self::init(Http::POST)->uri($uri)->body($payload, $mime);
1131
    }
1132
1133
    /**
1134
     * HTTP Method Put
1135
     * @param string $uri optional uri to use
1136
     * @param string $payload data to send in body of request
1137
     * @param string $mime MIME to use for Content-Type
1138
     * @return Request
1139
     */
1140
    public static function put($uri, $payload = null, $mime = null)
1141
    {
1142
        return self::init(Http::PUT)->uri($uri)->body($payload, $mime);
1143
    }
1144
1145
    /**
1146
     * HTTP Method Patch
1147
     * @param string $uri optional uri to use
1148
     * @param string $payload data to send in body of request
1149
     * @param string $mime MIME to use for Content-Type
1150
     * @return Request
1151
     */
1152
    public static function patch($uri, $payload = null, $mime = null)
1153
    {
1154
        return self::init(Http::PATCH)->uri($uri)->body($payload, $mime);
1155
    }
1156
1157
    /**
1158
     * HTTP Method Delete
1159
     * @param string $uri optional uri to use
1160
     * @return Request
1161
     */
1162
    public static function delete($uri, $mime = null)
1163
    {
1164
        return self::init(Http::DELETE)->uri($uri)->mime($mime);
1165
    }
1166
1167
    /**
1168
     * HTTP Method Head
1169
     * @param string $uri optional uri to use
1170
     * @return Request
1171
     */
1172
    public static function head($uri)
1173
    {
1174
        return self::init(Http::HEAD)->uri($uri);
1175
    }
1176
1177
    /**
1178
     * HTTP Method Options
1179
     * @param string $uri optional uri to use
1180
     * @return Request
1181
     */
1182
    public static function options($uri)
1183
    {
1184
        return self::init(Http::OPTIONS)->uri($uri);
1185
    }
1186
}
1187