get('http://www.example.com/search', array( * 'q' => 'keyword', * )); * ``` * * Example post request with post data: * * ```php * $curl = new Curl\Curl(); * $curl->post('http://www.example.com/login/', array( * 'username' => 'myusername', * 'password' => 'mypassword', * )); * ``` * * @see https://php.net/manual/curl.setup.php */ class Curl { // The HTTP authentication method(s) to use. /** * @var string Type AUTH_BASIC */ const AUTH_BASIC = CURLAUTH_BASIC; /** * @var string Type AUTH_DIGEST */ const AUTH_DIGEST = CURLAUTH_DIGEST; /** * @var string Type AUTH_GSSNEGOTIATE */ const AUTH_GSSNEGOTIATE = CURLAUTH_GSSNEGOTIATE; /** * @var string Type AUTH_NTLM */ const AUTH_NTLM = CURLAUTH_NTLM; /** * @var string Type AUTH_ANY */ const AUTH_ANY = CURLAUTH_ANY; /** * @var string Type AUTH_ANYSAFE */ const AUTH_ANYSAFE = CURLAUTH_ANYSAFE; /** * @var string The user agent name which is set when making a request */ const USER_AGENT = 'PHP Curl/1.9 (+https://github.com/php-mod/curl)'; private $_cookies = array(); private $_headers = array(); /** * @var resource Contains the curl resource created by `curl_init()` function */ public $curl; /** * @var bool Whether an error occured or not */ public $error = false; /** * @var int Contains the error code of the curren request, 0 means no error happend */ public $error_code = 0; /** * @var string If the curl request failed, the error message is contained */ public $error_message = null; /** * @var bool Whether an error occured or not */ public $curl_error = false; /** * @var int Contains the error code of the curren request, 0 means no error happend. * @see https://curl.haxx.se/libcurl/c/libcurl-errors.html */ public $curl_error_code = 0; /** * @var string If the curl request failed, the error message is contained */ public $curl_error_message = null; /** * @var bool Whether an error occured or not */ public $http_error = false; /** * @var int Contains the status code of the current processed request. */ public $http_status_code = 0; /** * @var string If the curl request failed, the error message is contained */ public $http_error_message = null; /** * @var string|array TBD (ensure type) Contains the request header informations */ public $request_headers = null; /** * @var string|array TBD (ensure type) Contains the response header informations */ public $response_headers = array(); /** * @var string Contains the response from the curl request */ public $response = null; /** * @var bool Whether the current section of response headers is after 'HTTP/1.1 100 Continue' */ protected $response_header_continue = false; /** * Constructor ensures the available curl extension is loaded. * * @throws \ErrorException */ public function __construct() { if (!extension_loaded('curl')) { throw new \ErrorException('The cURL extensions is not loaded, make sure you have installed the cURL extension: https://php.net/manual/curl.setup.php'); } $this->init(); } // private methods /** * Initializer for the curl resource. * * Is called by the __construct() of the class or when the curl request is reseted. * @return self */ private function init() { $this->curl = curl_init(); $this->setUserAgent(self::USER_AGENT); $this->setOpt(CURLINFO_HEADER_OUT, true); $this->setOpt(CURLOPT_HEADER, false); $this->setOpt(CURLOPT_RETURNTRANSFER, true); $this->setOpt(CURLOPT_HEADERFUNCTION, array($this, 'addResponseHeaderLine')); return $this; } /** * Handle writing the response headers * * @param resource $curl The current curl resource * @param string $header_line A line from the list of response headers * * @return int Returns the length of the $header_line */ public function addResponseHeaderLine($curl, $header_line) { $trimmed_header = trim($header_line, "\r\n"); if ($trimmed_header === "") { $this->response_header_continue = false; } elseif (strtolower($trimmed_header) === 'http/1.1 100 continue') { $this->response_header_continue = true; } elseif (!$this->response_header_continue) { $this->response_headers[] = $trimmed_header; } return strlen($header_line); } // protected methods /** * Execute the curl request based on the respectiv settings. * * @return int Returns the error code for the current curl request */ protected function exec() { $this->response_headers = array(); $this->response = curl_exec($this->curl); $this->curl_error_code = curl_errno($this->curl); $this->curl_error_message = curl_error($this->curl); $this->curl_error = !($this->curl_error_code === 0); $this->http_status_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); $this->http_error = in_array(floor($this->http_status_code / 100), array(4, 5)); $this->error = $this->curl_error || $this->http_error; $this->error_code = $this->error ? ($this->curl_error ? $this->curl_error_code : $this->http_status_code) : 0; $this->request_headers = preg_split('/\r\n/', curl_getinfo($this->curl, CURLINFO_HEADER_OUT), null, PREG_SPLIT_NO_EMPTY); $this->http_error_message = $this->error ? (isset($this->response_headers['0']) ? $this->response_headers['0'] : '') : ''; $this->error_message = $this->curl_error ? $this->curl_error_message : $this->http_error_message; return $this->error_code; } /** * @param array|object|string $data */ protected function preparePayload($data) { $this->setOpt(CURLOPT_POST, true); if (is_array($data) || is_object($data)) { $skip = false; foreach ($data as $key => $value) { // If a value is an instance of CurlFile skip the http_build_query // see issue https://github.com/php-mod/curl/issues/46 // suggestion from: https://stackoverflow.com/a/36603038/4611030 if ($value instanceof \CurlFile) { $skip = true; } } if (!$skip) { $data = http_build_query($data); } } $this->setOpt(CURLOPT_POSTFIELDS, $data); } /** * Set auth options for the current request. * * Available auth types are: * * + self::AUTH_BASIC * + self::AUTH_DIGEST * + self::AUTH_GSSNEGOTIATE * + self::AUTH_NTLM * + self::AUTH_ANY * + self::AUTH_ANYSAFE * * @param int $httpauth The type of authentication */ protected function setHttpAuth($httpauth) { $this->setOpt(CURLOPT_HTTPAUTH, $httpauth); } // public methods /** * @deprecated calling exec() directly is discouraged */ public function _exec() { return $this->exec(); } // functions /** * Make a get request with optional data. * * The get request has no body data, the data will be correctly added to the $url with the http_build_query() method. * * @param string $url The url to make the get request for * @param array $data Optional arguments who are part of the url * @return self */ public function get($url, $data = array()) { if (count($data) > 0) { $this->setOpt(CURLOPT_URL, $url.'?'.http_build_query($data)); } else { $this->setOpt(CURLOPT_URL, $url); } $this->setOpt(CURLOPT_HTTPGET, true); $this->exec(); return $this; } /** * Make a post request with optional post data. * * @param string $url The url to make the post request * @param array $data Post data to pass to the url * @return self */ public function post($url, $data = array(), $payload = false) { if (! empty($data)) { if ($payload === false) { // Check if the url has not already been modified $url .= strpos($url, '?') !== false ? '&' : '?'; $url .= http_build_query($data); } else { $this->preparePayload($data); } } $this->setOpt(CURLOPT_URL, $url); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST'); $this->exec(); return $this; } /** * Make a put request with optional data. * * The put request data can be either sent via payload or as get paramters of the string. * * @param string $url The url to make the put request * @param array $data Optional data to pass to the $url * @param bool $payload Whether the data should be transmitted trough payload or as get parameters of the string * @return self */ public function put($url, $data = array(), $payload = false) { if (! empty($data)) { if ($payload === false) { $url .= '?'.http_build_query($data); } else { $this->preparePayload($data); } } $this->setOpt(CURLOPT_URL, $url); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); $this->exec(); return $this; } /** * Make a patch request with optional data. * * The patch request data can be either sent via payload or as get paramters of the string. * * @param string $url The url to make the patch request * @param array $data Optional data to pass to the $url * @param bool $payload Whether the data should be transmitted trough payload or as get parameters of the string * @return self */ public function patch($url, $data = array(), $payload = false) { if (! empty($data)) { if ($payload === false) { $url .= '?'.http_build_query($data); } else { $this->preparePayload($data); } } $this->setOpt(CURLOPT_URL, $url); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); $this->exec(); return $this; } /** * Make a delete request with optional data. * * @param string $url The url to make the delete request * @param array $data Optional data to pass to the $url * @param bool $payload Whether the data should be transmitted trough payload or as get parameters of the string * @return self */ public function delete($url, $data = array(), $payload = false) { if (! empty($data)) { if ($payload === false) { $url .= '?'.http_build_query($data); } else { $this->preparePayload($data); } } $this->setOpt(CURLOPT_URL, $url); $this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); $this->exec(); return $this; } // setters /** * Pass basic auth data. * * If the the rquested url is secured by an httaccess basic auth mechanism you can use this method to provided the auth data. * * ```php * $curl = new Curl(); * $curl->setBasicAuthentication('john', 'doe'); * $curl->get('http://example.com/secure.php'); * ``` * * @param string $username The username for the authentification * @param string $password The password for the given username for the authentification * @return self */ public function setBasicAuthentication($username, $password) { $this->setHttpAuth(self::AUTH_BASIC); $this->setOpt(CURLOPT_USERPWD, $username.':'.$password); return $this; } /** * Provide optional header informations. * * In order to pass optional headers by key value pairing: * * ```php * $curl = new Curl(); * $curl->setHeader('X-Requested-With', 'XMLHttpRequest'); * $curl->get('http://example.com/request.php'); * ``` * * @param string $key The header key * @param string $value The value for the given header key * @return self */ public function setHeader($key, $value) { $this->_headers[$key] = $key.': '.$value; $this->setOpt(CURLOPT_HTTPHEADER, array_values($this->_headers)); return $this; } /** * Provide a User Agent. * * In order to provide you cusomtized user agent name you can use this method. * * ```php * $curl = new Curl(); * $curl->setUserAgent('My John Doe Agent 1.0'); * $curl->get('http://example.com/request.php'); * ``` * * @param string $useragent The name of the user agent to set for the current request * @return self */ public function setUserAgent($useragent) { $this->setOpt(CURLOPT_USERAGENT, $useragent); return $this; } /** * @deprecated Call setReferer() instead */ public function setReferrer($referrer) { $this->setReferer($referrer); return $this; } /** * Set the HTTP referer header. * * The $referer informations can help identify the requested client where the requested was made. * * @param string $referer An url to pass and will be set as referer header * @return self */ public function setReferer($referer) { $this->setOpt(CURLOPT_REFERER, $referer); return $this; } /** * Set contents of HTTP Cookie header. * * @param string $key The name of the cookie * @param string $value The value for the provided cookie name * @return self */ public function setCookie($key, $value) { $this->_cookies[$key] = $value; $this->setOpt(CURLOPT_COOKIE, http_build_query($this->_cookies, '', '; ')); return $this; } /** * Set customized curl options. * * To see a full list of options: http://php.net/curl_setopt * * @see http://php.net/curl_setopt * * @param int $option The curl option constante e.g. `CURLOPT_AUTOREFERER`, `CURLOPT_COOKIESESSION` * @param mixed $value The value to pass for the given $option */ public function setOpt($option, $value) { return curl_setopt($this->curl, $option, $value); } /** * Get customized curl options. * * To see a full list of options: http://php.net/curl_getinfo * * @see http://php.net/curl_getinfo * * @param int $option The curl option constante e.g. `CURLOPT_AUTOREFERER`, `CURLOPT_COOKIESESSION` * @param mixed $value The value to check for the given $option */ public function getOpt($option) { return curl_getinfo($this->curl, $option); } /** * Return the endpoint set for curl * * @see http://php.net/curl_getinfo * * @return string of endpoint */ public function getEndpoint() { return $this->getOpt(CURLINFO_EFFECTIVE_URL); } /** * Enable verbositiy. * * @todo As to keep naming convention it should be renamed to `setVerbose()` * * @param string $on * @return self */ public function verbose($on = true) { $this->setOpt(CURLOPT_VERBOSE, $on); return $this; } /** * Reset all curl options. * * In order to make multiple requests with the same curl object all settings requires to be reset. * @return self */ public function reset() { $this->close(); $this->_cookies = array(); $this->_headers = array(); $this->error = false; $this->error_code = 0; $this->error_message = null; $this->curl_error = false; $this->curl_error_code = 0; $this->curl_error_message = null; $this->http_error = false; $this->http_status_code = 0; $this->http_error_message = null; $this->request_headers = null; $this->response_headers = array(); $this->response = null; $this->init(); return $this; } /** * Closing the current open curl resource. * @return self */ public function close() { if (is_resource($this->curl)) { curl_close($this->curl); } return $this; } /** * Close the connection when the Curl object will be destroyed. */ public function __destruct() { $this->close(); } /** * Was an 'info' header returned. * @return bool */ public function isInfo() { return $this->http_status_code >= 100 && $this->http_status_code < 200; } /** * Was an 'OK' response returned. * @return bool */ public function isSuccess() { return $this->http_status_code >= 200 && $this->http_status_code < 300; } /** * Was a 'redirect' returned. * @return bool */ public function isRedirect() { return $this->http_status_code >= 300 && $this->http_status_code < 400; } /** * Was an 'error' returned (client error or server error). * @return bool */ public function isError() { return $this->http_status_code >= 400 && $this->http_status_code < 600; } /** * Was a 'client error' returned. * @return bool */ public function isClientError() { return $this->http_status_code >= 400 && $this->http_status_code < 500; } /** * Was a 'server error' returned. * @return bool */ public function isServerError() { return $this->http_status_code >= 500 && $this->http_status_code < 600; } /** * Get a specific response header key or all values from the response headers array. * * Usage example: * * ```php * $curl = (new Curl())->get('http://example.com'); * * echo $curl->getResponseHeaders('Content-Type'); * ``` * * Or in order to dump all keys with the given values use: * * ```php * $curl = (new Curl())->get('http://example.com'); * * var_dump($curl->getResponseHeaders()); * ``` * * @param string $headerKey Optional key to get from the array. * @return bool|string * @since 1.9 */ public function getResponseHeaders($headerKey = null) { $headers = array(); $headerKey = strtolower($headerKey); foreach ($this->response_headers as $header) { $parts = explode(":", $header, 2); $key = isset($parts[0]) ? $parts[0] : null; $value = isset($parts[1]) ? $parts[1] : null; $headers[trim(strtolower($key))] = trim($value); } if ($headerKey) { return isset($headers[$headerKey]) ? $headers[$headerKey] : false; } return $headers; } public function getResponse() { return $this->response; } public function getErrorCode() { return $this->curl_error_code; } public function getErrorMessage() { return $this->curl_error_message; } public function getHttpStatus() { return $this->http_status_code; } }