Curl.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. <?php
  2. namespace App\Models;
  3. use RuntimeException;
  4. class Curl
  5. {
  6. const METHOD_GET = 'GET';
  7. const METHOD_POST = 'POST';
  8. const METHOD_DELETE = 'DELETE';
  9. const METHOD_PATCH = 'PATCH';
  10. const METHOD_PUT = 'PUT';
  11. const METHOD_OPTIONS = 'OPTIONS';
  12. /**
  13. * @var string The HTTP address to use.
  14. */
  15. public $url;
  16. /**
  17. * @var string The method the request should use.
  18. */
  19. public $method;
  20. /**
  21. * @var array The headers to be sent with the request.
  22. */
  23. public $headers = [];
  24. /**
  25. * @var string The last response body.
  26. */
  27. public $body = '';
  28. /**
  29. * @var string The last response body (without headers extracted).
  30. */
  31. public $rawBody = '';
  32. /**
  33. * @var array The last returned HTTP code.
  34. */
  35. public $code;
  36. /**
  37. * @var array The cURL response information.
  38. */
  39. public $info;
  40. /**
  41. * @var array cURL Options.
  42. */
  43. public $requestOptions;
  44. /**
  45. * @var array Request data.
  46. */
  47. public $requestData;
  48. /**
  49. * @var array Request headers.
  50. */
  51. public $requestHeaders;
  52. /**
  53. * @var string Argument separator.
  54. */
  55. public $argumentSeparator = '&';
  56. /**
  57. * @var string If writing response to a file, which file to use.
  58. */
  59. public $streamFile;
  60. /**
  61. * @var string If writing response to a file, which write filter to apply.
  62. */
  63. public $streamFilter;
  64. /**
  65. * @var int The maximum redirects allowed.
  66. */
  67. public $maxRedirects = 10;
  68. /**
  69. * @var int Internal counter
  70. */
  71. protected $redirectCount = null;
  72. /**
  73. * Make the object with common properties
  74. * @param string $url HTTP request address
  75. * @param string $method Request method (GET, POST, PUT, DELETE, etc)
  76. * @param callable $options Callable helper function to modify the object
  77. */
  78. public static function make($url, $method, $options = null)
  79. {
  80. $http = new self;
  81. $http->url = $url;
  82. $http->method = $method;
  83. if ($options && is_callable($options)) {
  84. $options($http);
  85. }
  86. return $http;
  87. }
  88. /**
  89. * Make a HTTP GET call.
  90. * @param string $url
  91. * @param callable $options
  92. * @return self
  93. */
  94. public static function get($url, $options = null)
  95. {
  96. $http = self::make($url, self::METHOD_GET, $options);
  97. return $http->send();
  98. }
  99. /**
  100. * Make a HTTP POST call.
  101. * @param string $url
  102. * @param callable $options
  103. * @return self
  104. */
  105. public static function post($url, $options = null)
  106. {
  107. $http = self::make($url, self::METHOD_POST, $options);
  108. return $http->send();
  109. }
  110. /**
  111. * Make a HTTP DELETE call.
  112. * @param string $url
  113. * @param callable $options
  114. * @return self
  115. */
  116. public static function delete($url, $options = null)
  117. {
  118. $http = self::make($url, self::METHOD_DELETE, $options);
  119. return $http->send();
  120. }
  121. /**
  122. * Make a HTTP PATCH call.
  123. * @param string $url
  124. * @param callable $options
  125. * @return self
  126. */
  127. public static function patch($url, $options = null)
  128. {
  129. $http = self::make($url, self::METHOD_PATCH, $options);
  130. return $http->send();
  131. }
  132. /**
  133. * Make a HTTP PUT call.
  134. * @param string $url
  135. * @param callable $options
  136. * @return self
  137. */
  138. public static function put($url, $options = null)
  139. {
  140. $http = self::make($url, self::METHOD_PUT, $options);
  141. return $http->send();
  142. }
  143. /**
  144. * Make a HTTP OPTIONS call.
  145. * @param string $url
  146. * @param callable $options
  147. * @return self
  148. */
  149. public static function options($url, $options = null)
  150. {
  151. $http = self::make($url, self::METHOD_OPTIONS, $options);
  152. return $http->send();
  153. }
  154. /**
  155. * Execute the HTTP request.
  156. * @return string response body
  157. */
  158. public function send()
  159. {
  160. if (!function_exists('curl_init')) {
  161. echo 'cURL PHP extension required.'.PHP_EOL;
  162. exit(1);
  163. }
  164. /*
  165. * Create and execute the cURL Resource
  166. */
  167. $curl = curl_init();
  168. curl_setopt($curl, CURLOPT_URL, $this->url);
  169. curl_setopt($curl, CURLOPT_HEADER, true);
  170. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  171. curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
  172. curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
  173. if (defined('CURLOPT_FOLLOWLOCATION') && !ini_get('open_basedir')) {
  174. curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
  175. curl_setopt($curl, CURLOPT_MAXREDIRS, $this->maxRedirects);
  176. }
  177. if ($this->requestOptions && is_array($this->requestOptions)) {
  178. curl_setopt_array($curl, $this->requestOptions);
  179. }
  180. /*
  181. * Set request method
  182. */
  183. if ($this->method == self::METHOD_POST) {
  184. curl_setopt($curl, CURLOPT_POST, true);
  185. }
  186. elseif ($this->method !== self::METHOD_GET) {
  187. curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $this->method);
  188. }
  189. /*
  190. * Set request data
  191. */
  192. if ($this->requestData) {
  193. if (in_array($this->method, [self::METHOD_POST, self::METHOD_PATCH, self::METHOD_PUT])) {
  194. curl_setopt($curl, CURLOPT_POSTFIELDS, $this->getRequestData());
  195. }
  196. elseif ($this->method == self::METHOD_GET) {
  197. curl_setopt($curl, CURLOPT_URL, $this->url . '?' . $this->getRequestData());
  198. }
  199. }
  200. /*
  201. * Set request headers
  202. */
  203. if ($this->requestHeaders) {
  204. $requestHeaders = [];
  205. foreach ($this->requestHeaders as $key => $value) {
  206. $requestHeaders[] = $key . ': ' . $value;
  207. }
  208. curl_setopt($curl, CURLOPT_HTTPHEADER, $requestHeaders);
  209. }
  210. /*
  211. * Handle output to file
  212. */
  213. if ($this->streamFile) {
  214. $stream = fopen($this->streamFile, 'w');
  215. if ($this->streamFilter) {
  216. stream_filter_append($stream, $this->streamFilter, STREAM_FILTER_WRITE);
  217. }
  218. curl_setopt($curl, CURLOPT_HEADER, false);
  219. curl_setopt($curl, CURLOPT_FILE, $stream);
  220. curl_exec($curl);
  221. }
  222. /*
  223. * Handle output to variable
  224. */
  225. else {
  226. $response = $this->rawBody = curl_exec($curl);
  227. $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
  228. $this->headers = $this->headerToArray(substr($response, 0, $headerSize));
  229. $this->body = substr($response, $headerSize);
  230. }
  231. $this->info = curl_getinfo($curl);
  232. $this->code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  233. /*
  234. * Close resources
  235. */
  236. curl_close($curl);
  237. if ($this->streamFile) {
  238. fclose($stream);
  239. }
  240. /*
  241. * Emulate FOLLOW LOCATION behavior
  242. */
  243. if (!defined('CURLOPT_FOLLOWLOCATION') || ini_get('open_basedir')) {
  244. if ($this->redirectCount === null) {
  245. $this->redirectCount = $this->maxRedirects;
  246. }
  247. if (in_array($this->code, [301, 302])) {
  248. $this->url = array_get($this->info, 'url');
  249. if (!empty($this->url) && $this->redirectCount > 0) {
  250. $this->redirectCount -= 1;
  251. return $this->send();
  252. }
  253. }
  254. }
  255. return $this;
  256. }
  257. /**
  258. * Return the request data set.
  259. * @return string
  260. */
  261. public function getRequestData()
  262. {
  263. if (
  264. $this->method !== self::METHOD_GET
  265. && isset($this->requestOptions[CURLOPT_POSTFIELDS])
  266. && empty($this->requestData)
  267. ) {
  268. return $this->requestOptions[CURLOPT_POSTFIELDS];
  269. }
  270. if (!empty($this->requestData)) {
  271. return http_build_query($this->requestData, '', $this->argumentSeparator);
  272. }
  273. return '';
  274. }
  275. /**
  276. * Turn a header string into an array.
  277. * @param string $header
  278. * @return array
  279. */
  280. protected function headerToArray($header)
  281. {
  282. $headers = [];
  283. $parts = explode("\r\n", $header);
  284. foreach ($parts as $singleHeader) {
  285. $delimiter = strpos($singleHeader, ': ');
  286. if ($delimiter !== false) {
  287. $key = substr($singleHeader, 0, $delimiter);
  288. $val = substr($singleHeader, $delimiter + 2);
  289. $headers[$key] = $val;
  290. }
  291. else {
  292. $delimiter = strpos($singleHeader, ' ');
  293. if ($delimiter !== false) {
  294. $key = substr($singleHeader, 0, $delimiter);
  295. $val = substr($singleHeader, $delimiter + 1);
  296. $headers[$key] = $val;
  297. }
  298. }
  299. }
  300. return $headers;
  301. }
  302. /**
  303. * Add a data to the request.
  304. * @param string $value
  305. * @return self
  306. */
  307. public function data($key, $value = null)
  308. {
  309. if (is_array($key)) {
  310. foreach ($key as $_key => $_value) {
  311. $this->data($_key, $_value);
  312. }
  313. return $this;
  314. }
  315. $this->requestData[$key] = $value;
  316. return $this;
  317. }
  318. /**
  319. * Add a header to the request.
  320. * @param string $value
  321. * @return self
  322. */
  323. public function header($key, $value = null)
  324. {
  325. if (is_array($key)) {
  326. foreach ($key as $_key => $_value) {
  327. $this->header($_key, $_value);
  328. }
  329. return $this;
  330. }
  331. $this->requestHeaders[$key] = $value;
  332. return $this;
  333. }
  334. /**
  335. * Sets a proxy to use with this request
  336. */
  337. public function proxy($type, $host, $port, $username = null, $password = null)
  338. {
  339. if ($type === 'http') {
  340. $this->setOption(CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
  341. }
  342. elseif ($type === 'socks4') {
  343. $this->setOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  344. }
  345. elseif ($type === 'socks5') {
  346. $this->setOption(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  347. }
  348. $this->setOption(CURLOPT_PROXY, $host . ':' . $port);
  349. if ($username && $password) {
  350. $this->setOption(CURLOPT_PROXYUSERPWD, $username . ':' . $password);
  351. }
  352. return $this;
  353. }
  354. /**
  355. * Adds authentication to the comms.
  356. * @param string $user
  357. * @param string $pass
  358. * @return self
  359. */
  360. public function auth($user, $pass = null)
  361. {
  362. if (strpos($user, ':') !== false && !$pass) {
  363. list($user, $pass) = explode(':', $user);
  364. }
  365. $this->setOption(CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
  366. $this->setOption(CURLOPT_USERPWD, $user . ':' . $pass);
  367. return $this;
  368. }
  369. /**
  370. * Disable follow location (redirects)
  371. */
  372. public function noRedirect()
  373. {
  374. $this->setOption(CURLOPT_FOLLOWLOCATION, false);
  375. return $this;
  376. }
  377. /**
  378. * Enable SSL verification
  379. */
  380. public function verifySSL()
  381. {
  382. $this->setOption(CURLOPT_SSL_VERIFYPEER, true);
  383. $this->setOption(CURLOPT_SSL_VERIFYHOST, true);
  384. return $this;
  385. }
  386. /**
  387. * Sets the request timeout.
  388. * @param string $timeout
  389. * @return self
  390. */
  391. public function timeout($timeout)
  392. {
  393. $this->setOption(CURLOPT_CONNECTTIMEOUT, $timeout);
  394. $this->setOption(CURLOPT_TIMEOUT, $timeout);
  395. return $this;
  396. }
  397. /**
  398. * Write the response to a file
  399. * @param string $path Path to file
  400. * @param string $filter Stream filter as listed in stream_get_filters()
  401. * @return self
  402. */
  403. public function toFile($path, $filter = null)
  404. {
  405. $this->streamFile = $path;
  406. if ($filter) {
  407. $this->streamFilter = $filter;
  408. }
  409. return $this;
  410. }
  411. /**
  412. * Add a single option to the request.
  413. * @param string $option
  414. * @param string $value
  415. * @return self
  416. */
  417. public function setOption($option, $value = null)
  418. {
  419. if (is_array($option)) {
  420. foreach ($option as $_option => $_value) {
  421. $this->setOption($_option, $_value);
  422. }
  423. return $this;
  424. }
  425. if (is_string($option) && defined($option)) {
  426. $optionKey = constant($option);
  427. $this->requestOptions[$optionKey] = $value;
  428. } elseif (is_int($option)) {
  429. $constants = get_defined_constants(true);
  430. $curlOptConstants = array_flip(array_filter($constants['curl'], function ($key) {
  431. return strpos($key, 'CURLOPT_') === 0;
  432. }, ARRAY_FILTER_USE_KEY));
  433. if (isset($curlOptConstants[$option])) {
  434. $this->requestOptions[$option] = $value;
  435. } else {
  436. throw new RuntimeException('$option parameter must be a CURLOPT constant or equivalent integer');
  437. }
  438. } else {
  439. throw new RuntimeException('$option parameter must be a CURLOPT constant or equivalent integer');
  440. }
  441. return $this;
  442. }
  443. /**
  444. * Handy if this object is called directly.
  445. * @return string The last response.
  446. */
  447. public function __toString()
  448. {
  449. return (string) $this->body;
  450. }
  451. }