Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.58% covered (success)
91.58%
87 / 95
82.35% covered (success)
82.35%
14 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Curl
91.58% covered (success)
91.58%
87 / 95
82.35% covered (success)
82.35%
14 / 17
48.32
0.00% covered (danger)
0.00%
0 / 1
 __construct
87.50% covered (success)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 setMethod
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 curl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOption
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setOptions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setReturnHeader
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setReturnTransfer
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isReturnHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isReturnTransfer
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getOption
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 hasOption
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 open
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
14
 send
83.33% covered (success)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
7.23
 reset
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 version
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 disconnect
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
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-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Http\Client;
15
16use Pop\Http\Parser;
17use Pop\Mime\Message;
18
19/**
20 * HTTP curl client class
21 *
22 * @category   Pop
23 * @package    Pop\Http
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    4.1.0
28 */
29class Curl extends AbstractClient
30{
31
32    /**
33     * cURL options
34     * @var array
35     */
36    protected $options = [];
37
38    /**
39     * Constructor
40     *
41     * Instantiate the cURL object
42     *
43     * @param  string $url
44     * @param  string $method
45     * @param  array  $opts
46     * @throws Exception
47     */
48    public function __construct($url = null, $method = 'GET', array $opts = null)
49    {
50        if (!function_exists('curl_init')) {
51            throw new Exception('Error: cURL is not available.');
52        }
53
54        $this->resource = curl_init();
55
56        parent::__construct($url, $method);
57
58
59        $this->setOption(CURLOPT_HEADER, true);
60        $this->setOption(CURLOPT_RETURNTRANSFER, true);
61
62        if (null !== $opts) {
63            $this->setOptions($opts);
64        }
65    }
66
67    /**
68     * Set the method
69     *
70     * @param  string  $method
71     * @param  boolean $strict
72     * @throws Exception
73     * @return Curl
74     */
75    public function setMethod($method, $strict = true)
76    {
77        parent::setMethod($method, $strict);
78
79        if ($this->method != 'GET') {
80            switch ($this->method) {
81                case 'POST':
82                    $this->setOption(CURLOPT_POST, true);
83                    break;
84                default:
85                    $this->setOption(CURLOPT_CUSTOMREQUEST, $this->method);
86            }
87        }
88
89
90        return $this;
91    }
92
93    /**
94     * Return cURL resource (alias to $this->getResource())
95     *
96     * @return resource
97     */
98    public function curl()
99    {
100        return $this->resource;
101    }
102
103    /**
104     * Set cURL session option
105     *
106     * @param  int   $opt
107     * @param  mixed $val
108     * @return Curl
109     */
110    public function setOption($opt, $val)
111    {
112        // Set the protected property to keep track of the cURL options.
113        $this->options[$opt] = $val;
114        curl_setopt($this->resource, $opt, $val);
115
116        return $this;
117    }
118
119    /**
120     * Set cURL session options
121     *
122     * @param  array $opts
123     * @return Curl
124     */
125    public function setOptions($opts)
126    {
127        // Set the protected property to keep track of the cURL options.
128        foreach ($opts as $k => $v) {
129            $this->options[$k] = $v;
130        }
131
132        curl_setopt_array($this->resource, $opts);
133
134        return $this;
135    }
136
137    /**
138     * Set cURL option to return the header
139     *
140     * @param  boolean $header
141     * @return Curl
142     */
143    public function setReturnHeader($header = true)
144    {
145        $this->setOption(CURLOPT_HEADER, (bool)$header);
146        return $this;
147    }
148
149    /**
150     * Set cURL option to return the transfer
151     *
152     * @param  boolean $transfer
153     * @return Curl
154     */
155    public function setReturnTransfer($transfer = true)
156    {
157        $this->setOption(CURLOPT_RETURNTRANSFER, (bool)$transfer);
158        return $this;
159    }
160
161    /**
162     * Check if cURL is set to return header
163     *
164     * @return boolean
165     */
166    public function isReturnHeader()
167    {
168        return (isset($this->options[CURLOPT_HEADER]) && ($this->options[CURLOPT_HEADER] == true));
169    }
170
171    /**
172     * Check if cURL is set to return transfer
173     *
174     * @return boolean
175     */
176    public function isReturnTransfer()
177    {
178        return (isset($this->options[CURLOPT_RETURNTRANSFER]) && ($this->options[CURLOPT_RETURNTRANSFER] == true));
179    }
180
181    /**
182     * Get a cURL session option
183     *
184     * @param  int $opt
185     * @return string
186     */
187    public function getOption($opt)
188    {
189        return (isset($this->options[$opt])) ? $this->options[$opt] : null;
190    }
191
192    /**
193     * Has a cURL session option
194     *
195     * @param  int $opt
196     * @return boolean
197     */
198    public function hasOption($opt)
199    {
200        return (isset($this->options[$opt]));
201    }
202
203    /**
204     * Return the cURL session last info
205     *
206     * @param  int $opt
207     * @return array|string
208     */
209    public function getInfo($opt = null)
210    {
211        return (null !== $opt) ? curl_getinfo($this->resource, $opt) : curl_getinfo($this->resource);
212    }
213
214    /**
215     * Create and open cURL resource
216     *
217     * @return Curl
218     */
219    public function open()
220    {
221        $url = $this->url;
222
223        if (null !== $this->request) {
224            // Set query data if there is any
225            if ($this->request->hasFields()) {
226                // Append GET query string to URL
227                if (($this->method == 'GET') && ((!$this->request->hasHeader('Content-Type')) ||
228                        ($this->request->getHeaderValue('Content-Type') == 'application/x-www-form-urlencoded'))) {
229                    $url .= '?' . $this->request->getQuery();
230                    if (!$this->request->hasHeader('Content-Type')) {
231                        $this->request->addHeader('Content-Type', 'application/x-www-form-urlencoded');
232                    }
233                // Else, prepare request data for transmission
234                } else {
235                    // If request is JSON
236                    if ($this->request->getHeaderValue('Content-Type') == 'application/json') {
237                        $content = json_encode($this->request->getFields(), JSON_PRETTY_PRINT);
238                        $this->request->addHeader('Content-Length', strlen($content));
239                        $this->setOption(CURLOPT_POSTFIELDS, $content);
240                    // If request is a URL-encoded form
241                    } else if ($this->request->getHeaderValue('Content-Type') == 'application/x-www-form-urlencoded') {
242                        $this->request->addHeader('Content-Length', $this->request->getQueryLength());
243                        $this->setOption(CURLOPT_POSTFIELDS, $this->request->getQuery());
244                    // Else, if request is a multipart form
245                    } else if ($this->request->isMultipartForm()) {
246                        $formMessage = Message::createForm($this->request->getFields());
247                        $header      = $formMessage->getHeader('Content-Type');
248                        $content     = $formMessage->render(false);
249                        $formMessage->removeHeader('Content-Type');
250                        $this->request->addHeader($header)
251                            ->addHeader('Content-Length', strlen($content));
252                        $this->setOption(CURLOPT_POSTFIELDS, $content);
253                    // Else, basic request with data
254                    } else {
255                        $this->setOption(CURLOPT_POSTFIELDS, $this->request->getFields());
256                        if (!$this->request->hasHeader('Content-Type')) {
257                            $this->request->addHeader('Content-Type', 'application/x-www-form-urlencoded');
258                        }
259                    }
260                }
261            // Else, if there is raw body content
262            } else if ($this->request->hasBodyContent()) {
263                $this->request->addHeader('Content-Length', strlen($this->request->getBodyContent()));
264                $this->setOption(CURLOPT_POSTFIELDS, $this->request->getBodyContent());
265            }
266
267            if ($this->request->hasHeaders()) {
268                $headers = [];
269                foreach ($this->request->getHeaders() as $header) {
270                    $headers[] = $header->render();
271                }
272                $this->setOption(CURLOPT_HTTPHEADER, $headers);
273            }
274        }
275
276        $this->setOption(CURLOPT_URL, $url);
277
278        return $this;
279    }
280
281    /**
282     * Method to send the request and get the response
283     *
284     * @return void
285     */
286    public function send()
287    {
288        $this->open();
289
290        $response = curl_exec($this->resource);
291
292        if ($response === false) {
293            $this->throwError('Error: ' . curl_errno($this->resource) . ' => ' . curl_error($this->resource) . '.');
294        }
295
296        if (null === $this->response) {
297            $this->response = new Response();
298        }
299
300        // If the CURLOPT_RETURNTRANSFER option is set, get the response body and parse the headers.
301        if (isset($this->options[CURLOPT_RETURNTRANSFER]) && ($this->options[CURLOPT_RETURNTRANSFER] == true)) {
302            $headerSize = $this->getInfo(CURLINFO_HEADER_SIZE);
303            if ($this->options[CURLOPT_HEADER]) {
304                $parsedHeaders = Parser::parseHeaders(substr($response, 0, $headerSize));
305                $this->response->setVersion($parsedHeaders['version']);
306                $this->response->setCode($parsedHeaders['code']);
307                $this->response->setMessage($parsedHeaders['message']);
308                $this->response->addHeaders($parsedHeaders['headers']);
309                $this->response->setBody(substr($response, $headerSize));
310            } else {
311                $this->response->setBody($response);
312            }
313        }
314
315        if ($this->response->hasHeader('Content-Encoding')) {
316            $this->response->decodeBodyContent();
317        }
318    }
319    /**
320     * Method to reset the client object
321     *
322     * @return CUrl
323     */
324    public function reset()
325    {
326        $this->request  = new Request();
327        $this->response = new Response();
328        $this->options  = [];
329
330        return $this;
331    }
332
333    /**
334     * Return the cURL version
335     *
336     * @return array
337     */
338    public function version()
339    {
340        return curl_version();
341    }
342
343    /**
344     * Close the cURL connection
345     *
346     * @return void
347     */
348    public function disconnect()
349    {
350        if ($this->hasResource()) {
351            curl_close($this->resource);
352            $this->resource = null;
353        }
354    }
355
356}