├── .gitignore ├── CurlHelper.php ├── LICENSE ├── README.md └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /CurlHelper.php: -------------------------------------------------------------------------------- 1 | 5 | * @link https://github.com/mervick/curl-helper 6 | * @license MIT 7 | */ 8 | 9 | namespace Mervick; 10 | 11 | /** 12 | * Class CurlHelper 13 | */ 14 | class CurlHelper 15 | { 16 | /** 17 | * @var string 18 | */ 19 | public $user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36'; 20 | 21 | /** 22 | * @var int 23 | */ 24 | public $timeout = 30; 25 | 26 | /** 27 | * @var resource 28 | */ 29 | protected $ch; 30 | 31 | /** 32 | * @var null|string 33 | */ 34 | protected $url; 35 | 36 | /** 37 | * @var array 38 | */ 39 | protected $get_data = []; 40 | 41 | /** 42 | * @var array 43 | */ 44 | protected $post_data = []; 45 | 46 | /** 47 | * @var null|string 48 | */ 49 | protected $post_raw; 50 | 51 | /** 52 | * @var array 53 | */ 54 | protected $cookies = []; 55 | 56 | /** 57 | * @var array 58 | */ 59 | protected $headers = []; 60 | 61 | /** 62 | * @var array 63 | */ 64 | protected $xpath = []; 65 | 66 | /** 67 | * @var array 68 | */ 69 | protected $files = []; 70 | 71 | 72 | const MIME_X_WWW_FORM = 'application/x-www-form-urlencoded'; 73 | const MIME_FORM_DATA = 'multipart/form-data'; 74 | const MIME_JSON = 'application/json'; 75 | 76 | 77 | /** 78 | * @param string|null $url [optional] 79 | * @return CurlHelper 80 | */ 81 | public static function factory($url=null) 82 | { 83 | return new self($url); 84 | } 85 | 86 | /** 87 | * @param string $url [optional] 88 | */ 89 | public function __construct($url=null) 90 | { 91 | $this->ch = curl_init(); 92 | $this->url = $url; 93 | } 94 | 95 | /** 96 | * Set config 97 | * @param $config 98 | * @return $this 99 | */ 100 | public function config($config) 101 | { 102 | $alias = [ 103 | 'url' => 'setUrl', 104 | 'userAgent' => 'setUserAgent', 105 | 'user-agent' => 'setUserAgent', 106 | 'timeout' => 'setTimeout', 107 | 'postRaw' => 'setPostRaw', 108 | 'post-raw' => 'setPostRaw', 109 | 'postParams' => 'setPostParams', 110 | 'postFields' => 'setPostParams', 111 | 'post-params' => 'setPostParams', 112 | 'post-fields' => 'setPostParams', 113 | 'post' => 'setPostParams', 114 | 'getParams' => 'setGetParams', 115 | 'getFields' => 'setGetParams', 116 | 'get-params' => 'setGetParams', 117 | 'get-fields' => 'setGetParams', 118 | 'get' => 'setGetParams', 119 | 'headers' => 'setHeaders', 120 | 'cookies' => 'setCookies', 121 | 'cookieFile' => 'setCookieFile', 122 | 'cookie-file' => 'setCookieFile', 123 | 'proxy' => 'useProxy', 124 | 'options' => 'setOptions', 125 | 'file' => 'putFile', 126 | 'fileRaw' => 'putFileRaw', 127 | 'file-raw' => 'putFileRaw', 128 | ]; 129 | $config = array_intersect_key($config, array_fill_keys(array_merge(array_keys($alias), array_values($alias), [ 130 | 'follow', 'xpath', 131 | ]), 1)); 132 | foreach ($config as $method => $value) { 133 | if (isset($alias[$method])) { 134 | $method = $alias[$method]; 135 | } 136 | if (!(in_array($method, ['useProxy', 'putFile', 'putFileRaw']) && is_array($value))) { 137 | $value = [$value]; 138 | } 139 | call_user_func_array([$this, $method], $value); 140 | } 141 | return $this; 142 | } 143 | 144 | /** 145 | * @param string $url 146 | * @return $this 147 | */ 148 | public function setUrl($url) 149 | { 150 | $this->url = $url; 151 | return $this; 152 | } 153 | 154 | /** 155 | * @param bool $follow [optional] 156 | * @return $this 157 | */ 158 | public function follow($follow=true) 159 | { 160 | curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, $follow); 161 | return $this; 162 | } 163 | 164 | /** 165 | * @param string $ua 166 | * @return $this 167 | */ 168 | public function setUserAgent($ua) 169 | { 170 | $this->user_agent = $ua; 171 | return $this; 172 | } 173 | 174 | /** 175 | * @param int $timeout 176 | * @return $this 177 | */ 178 | public function setTimeout($timeout) 179 | { 180 | $this->timeout = $timeout; 181 | return $this; 182 | } 183 | 184 | /** 185 | * @param bool|string $debug [optional] 186 | * @return $this 187 | */ 188 | public function debug($debug=true) 189 | { 190 | curl_setopt($this->ch, CURLOPT_VERBOSE, $debug && true); 191 | if (is_string($debug)) { 192 | curl_setopt($this->ch, CURLOPT_STDERR, $debug); 193 | } 194 | return $this; 195 | } 196 | 197 | /** 198 | * @param string $raw 199 | * @return $this 200 | */ 201 | public function setPostRaw($raw) 202 | { 203 | $this->post_raw = $raw; 204 | return $this; 205 | } 206 | 207 | /** 208 | * @param array $data 209 | * @return $this 210 | */ 211 | public function setPostParams($data) 212 | { 213 | $this->post_data = array_merge($this->post_data, $data); 214 | return $this; 215 | } 216 | 217 | /** 218 | * @param array $data 219 | * @return $this 220 | * @see CurlHelper::setPostParams() :similar: 221 | */ 222 | public function setPostFields($data) 223 | { 224 | return $this->setPostParams($data); 225 | } 226 | 227 | /** 228 | * @param array $data 229 | * @return $this 230 | */ 231 | public function setGetParams($data) 232 | { 233 | $this->get_data = array_merge($this->get_data, $data); 234 | return $this; 235 | } 236 | 237 | /** 238 | * @param array $data 239 | * @return $this 240 | * @see CurlHelper::setGetParams() :similar: 241 | */ 242 | public function setGetFields($data) 243 | { 244 | return $this->setGetParams($data); 245 | } 246 | 247 | /** 248 | * @param array $data 249 | * @return $this 250 | */ 251 | public function setHeaders($data) 252 | { 253 | foreach ($data as $key => $val) { 254 | $this->headers[self::fixStringCase($key)] = $val; 255 | } 256 | return $this; 257 | } 258 | 259 | /** 260 | * @param array $data 261 | * @return $this 262 | */ 263 | public function setCookies($data) 264 | { 265 | $this->cookies = array_merge($this->cookies, $data); 266 | return $this; 267 | } 268 | 269 | /** 270 | * @param string $fieldname 271 | * @param string $filename 272 | * @param string|null $basename 273 | * @param string|null $mime_type 274 | * @return $this 275 | */ 276 | public function putFile($fieldname, $filename, $basename=null, $mime_type=null) 277 | { 278 | $this->files[] = [ 279 | 'type' => 'file', 280 | 'fieldname' => $fieldname, 281 | 'file' => $filename, 282 | 'basename' => $basename, 283 | 'mime_type' => $mime_type, 284 | ]; 285 | return $this; 286 | } 287 | 288 | /** 289 | * @param string $fieldname 290 | * @param string $file_contents 291 | * @param string $basename 292 | * @param string $mime_type 293 | * @return $this 294 | */ 295 | public function putFileRaw($fieldname, $file_contents, $basename, $mime_type) 296 | { 297 | $this->files[] = [ 298 | 'type' => 'raw', 299 | 'fieldname' => $fieldname, 300 | 'file' => $file_contents, 301 | 'basename' => $basename, 302 | 'mime_type' => $mime_type, 303 | ]; 304 | return $this; 305 | } 306 | 307 | /** 308 | * @return string 309 | */ 310 | protected function generateUrl() 311 | { 312 | $parsed_string = ''; 313 | $url = parse_url($this->url); 314 | if (!empty($url['query'])) { 315 | parse_str($url['query'], $get_data); 316 | $url['query'] = http_build_query(array_merge($get_data, $this->get_data)); 317 | } else { 318 | $url['query'] = http_build_query($this->get_data); 319 | } 320 | if (isset($url['scheme'])) { 321 | $parsed_string .= $url['scheme'] . '://'; 322 | } 323 | if (isset($url['user'])) { 324 | $parsed_string .= $url['user']; 325 | if (isset($url['pass'])) { 326 | $parsed_string .= ':' . $url['pass']; 327 | } 328 | $parsed_string .= '@'; 329 | } 330 | if (isset($url['host'])) { 331 | $parsed_string .= $url['host']; 332 | } 333 | if (isset($url['port'])) { 334 | $parsed_string .= ':' . $url['port']; 335 | } 336 | if (!empty($url['path'])) { 337 | $parsed_string .= $url['path']; 338 | } else { 339 | $parsed_string .= '/'; 340 | } 341 | if (!empty($url['query'])) { 342 | $parsed_string .= '?' . $url['query']; 343 | } 344 | if (isset($url['fragment'])) { 345 | $parsed_string .= '#' . $url['fragment']; 346 | } 347 | 348 | return $parsed_string; 349 | } 350 | 351 | /** 352 | * @param $filename 353 | * @return $this 354 | */ 355 | public function setCookieFile($filename) 356 | { 357 | curl_setopt($this->ch, CURLOPT_COOKIEJAR, $filename); 358 | curl_setopt($this->ch, CURLOPT_COOKIEFILE, $filename); 359 | return $this; 360 | } 361 | 362 | /** 363 | * @param string $host 364 | * @param string|null $login [optional] 365 | * @param string|null $password [optional] 366 | * @return $this 367 | */ 368 | public function useProxy($host, $login=null, $password=null) 369 | { 370 | curl_setopt($this->ch, CURLOPT_HTTPPROXYTUNNEL, true); 371 | curl_setopt($this->ch, CURLOPT_PROXY, $host); 372 | if (isset($login)) { 373 | curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, "$login:$password"); 374 | } 375 | return $this; 376 | } 377 | 378 | /** 379 | * Set custom CURL options 380 | * @param array $options 381 | * @return $this 382 | */ 383 | public function setOptions($options) 384 | { 385 | foreach ($options as $key => $value) { 386 | curl_setopt($this->ch, $key, $value); 387 | } 388 | return $this; 389 | } 390 | 391 | /** 392 | * Set extended XPath 393 | * @param string|string[] $expr 394 | * @return $this 395 | */ 396 | public function xpath($expr) 397 | { 398 | $this->xpath = $expr; 399 | return $this; 400 | } 401 | 402 | /** 403 | * Execute 404 | * @param string $returnData [optional] 405 | * @return array 406 | */ 407 | public function exec($returnData = null) 408 | { 409 | if (isset($this->post_raw)) { 410 | curl_setopt($this->ch, CURLOPT_POST, 1); 411 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, $this->post_raw); 412 | $this->headers['Content-Length'] = strlen($this->post_raw); 413 | if (empty($this->headers['Content-Type'])) { 414 | $this->headers['Content-Type'] = 'text/plain'; 415 | } 416 | } 417 | elseif (!empty($this->post_data) || !empty($this->files)) { 418 | curl_setopt($this->ch, CURLOPT_POST, 1); 419 | 420 | if (!empty($this->files)) { 421 | $this->headers['Content-Type'] = self::MIME_FORM_DATA; 422 | } 423 | elseif (empty($this->headers['Content-Type'])) { 424 | $this->headers['Content-Type'] = self::MIME_X_WWW_FORM; 425 | } 426 | 427 | if ($this->headers['Content-Type'] === self::MIME_JSON) { 428 | $data = json_encode($this->post_data); 429 | } 430 | elseif ($this->headers['Content-Type'] === self::MIME_FORM_DATA) { 431 | $data = $this->generateBoundary(); 432 | } 433 | else { 434 | $data = http_build_query($this->post_data); 435 | } 436 | 437 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, $data); 438 | $this->headers['Content-Length'] = strlen($data); 439 | } 440 | 441 | if (!empty($this->headers)) { 442 | $data = []; 443 | foreach ($this->headers as $k => $v) { 444 | if (is_array($v)) { 445 | foreach ($v as $val) { 446 | $data[] = "$k: $val"; 447 | } 448 | } else { 449 | $data[] = "$k: $v"; 450 | } 451 | } 452 | curl_setopt($this->ch, CURLOPT_HTTPHEADER, $data); 453 | } 454 | 455 | if (!empty($this->cookies)) { 456 | $data = []; 457 | foreach ($this->cookies as $k => $v) { 458 | $data[] = "$k=$v"; 459 | } 460 | curl_setopt($this->ch, CURLOPT_COOKIE, implode('; ', $data)); 461 | } 462 | 463 | $url = $this->generateUrl(); 464 | curl_setopt($this->ch, CURLOPT_URL, $url); 465 | curl_setopt($this->ch, CURLOPT_USERAGENT, $this->user_agent); 466 | curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout); 467 | curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, 1); 468 | curl_setopt($this->ch, CURLOPT_HEADER, 1); 469 | 470 | return $this->generateResponse($url, $returnData); 471 | } 472 | 473 | /** 474 | * Generate boundary 475 | * @return string 476 | */ 477 | protected function generateBoundary() 478 | { 479 | $eol = PHP_EOL; 480 | $boundary = '----CurlHelperBoundary' . md5(microtime()); 481 | 482 | $this->headers['Content-Type'] = self::MIME_FORM_DATA . "; boundary=$boundary"; 483 | 484 | $data = []; 485 | $each = function ($field, $value) use (&$data, &$each, $boundary, $eol) { 486 | if (is_array($value)) { 487 | if (empty($value)) { 488 | $each("{$field}[]", ''); 489 | } else { 490 | foreach ($value as $key => $item) { 491 | if (is_int($key)) $key = ''; 492 | $each("{$field}[{$key}]", $item); 493 | } 494 | } 495 | } else { 496 | $data[] = "--$boundary$eol"; 497 | $data[] = "Content-Disposition: form-data; name=\"$field\"$eol$eol"; 498 | $data[] = "$value$eol"; 499 | } 500 | }; 501 | foreach ($this->post_data as $field => $value) { 502 | $each($field, $value); 503 | } 504 | 505 | foreach ($this->files as $file) { 506 | if ($file['type'] === 'file') { 507 | if (empty($file['basename'])) { 508 | $file['basename'] = basename($file['file']); 509 | } 510 | if (empty($file['mime_type'])) { 511 | $finfo = finfo_open(FILEINFO_MIME_TYPE); 512 | $file['mime_type'] = finfo_file($finfo, $file['file']); 513 | finfo_close($finfo); 514 | } 515 | } 516 | if (empty($file['mime_type'])) { 517 | $file['mime_type'] = 'application/octet-stream'; 518 | } 519 | $data[] = "--$boundary$eol"; 520 | $data[] = "Content-Disposition: form-data; name=\"{$file['fieldname']}\"; filename=\"{$file['basename']}\"$eol"; 521 | $data[] = "Content-Type: {$file['mime_type']}$eol$eol"; 522 | $data[] = ($file['type'] === 'file' ? file_get_contents($file['file']) : $file['file']) . $eol; 523 | } 524 | 525 | $data[] = "--$boundary--$eol$eol"; 526 | 527 | return implode('', $data); 528 | } 529 | 530 | /** 531 | * Generate response 532 | * @param string $url 533 | * @param string $returnData [optional] 534 | * @return array 535 | * @throws \RuntimeException On error 536 | */ 537 | protected function generateResponse($url, $returnData = null) 538 | { 539 | $response = curl_exec($this->ch); 540 | $header_size = curl_getinfo($this->ch, CURLINFO_HEADER_SIZE); 541 | $sent = curl_getinfo($this->ch, CURLINFO_HEADER_OUT); 542 | $status = curl_getinfo($this->ch, CURLINFO_HTTP_CODE); 543 | $error = curl_error($this->ch); 544 | $errno = curl_errno($this->ch); 545 | 546 | curl_close($this->ch); 547 | 548 | if ($response === false) { 549 | throw new \RuntimeException($error, $errno); 550 | } 551 | 552 | $header = substr($response, 0, $header_size); 553 | $content = substr($response, $header_size); 554 | $type = $json_data = null; 555 | 556 | $headers = []; 557 | $cookies = []; 558 | 559 | if (!$returnData || in_array($returnData, ['content', 'data', 'xpath', 'headers', 'cookies'])) { 560 | foreach (explode("\n", $header) as $line) { 561 | $line = explode(':', $line, 2); 562 | if (isset($line[1])) { 563 | list($key, $value) = $line; 564 | $key = self::fixStringCase($key); 565 | $value = ($value = trim($value)) && !empty($value) ? $value : null; 566 | if (isset($headers[$key]) && $headers[$key] != $value) { 567 | if (!is_array($headers[$key])) { 568 | $headers[$key] = [$headers[$key]]; 569 | } 570 | $headers[$key][] = $value; 571 | } else { 572 | $headers[$key] = $value; 573 | } 574 | } 575 | } 576 | } 577 | 578 | if (!$returnData || $returnData === 'cookies') { 579 | if (isset($headers['Set-Cookie'])) { 580 | foreach (is_array($headers['Set-Cookie']) ? $headers['Set-Cookie'] : [$headers['Set-Cookie']] as $cookie) { 581 | $cookie = explode('=', explode(';', $cookie, 2)[0], 2); 582 | if (isset($cookie[1])) { 583 | $cookies[$cookie[0]] = $cookie[1]; 584 | } 585 | } 586 | } 587 | } 588 | 589 | if (!$returnData) { 590 | $type = isset($headers['Content-Type']) ? is_array($headers['Content-Type']) ? 591 | $headers['Content-Type'][0] : $headers['Content-Type'] : null; 592 | } 593 | 594 | if (!$returnData || in_array($returnData, ['content', 'data', 'xpath'])) { 595 | $encoding = isset($headers['Content-Encoding']) ? is_array($headers['Content-Encoding']) ? 596 | $headers['Content-Encoding'][0] : $headers['Content-Encoding'] : null; 597 | $content = strtolower($encoding) === 'gzip' ? gzdecode($content) : $content; 598 | } 599 | 600 | if (!$returnData || $returnData === 'data') { 601 | $json_data = !empty($content) && in_array($content{0}, ['{', '[']) ? json_decode($content, true) : false; 602 | } 603 | 604 | if (!$returnData || $returnData === 'headers') { 605 | ksort($headers); 606 | } 607 | 608 | $data = [ 609 | 'status' => $status, 610 | 'url' => $url, 611 | 'type' => $type, 612 | 'headers' => $headers, 613 | 'cookies' => $cookies, 614 | 'headers_raw' => $header, 615 | 'content' => $content, 616 | 'data' => $json_data, 617 | ]; 618 | 619 | if (!empty($this->xpath) && (!$returnData || $returnData === 'xpath')) { 620 | $data['xpath'] = $this->parseXpath($content); 621 | } 622 | 623 | if (!empty($sent)) { 624 | $data['request'] = $sent; 625 | } 626 | 627 | return $returnData ? $data[$returnData] : $data; 628 | } 629 | 630 | /** 631 | * Parse XPath contents 632 | * @param string $content 633 | * @return array|null 634 | */ 635 | protected function parseXpath($content) 636 | { 637 | if (!empty($this->xpath) && !empty($content)) { 638 | libxml_use_internal_errors(true); 639 | $func = [ 640 | 'node' => '/\/node\(\)$/', 641 | 'name' => '/\/name\(\)$/', 642 | 'html' => '/\/html\(\)$/', 643 | 'trim' => '/\/trim\(\)$/', 644 | 'name_value' => '/\/\(name,value\)$/', 645 | ]; 646 | 647 | $aliases = [ 648 | '/@([a-zA-Z0-9\-_]+)~=(["\'])([a-zA-Z0-9\x20\-_]+)\2/' => 'contains(concat(\2 \2, @\1, \2 \2), \2 \3 \2)', 649 | ]; 650 | 651 | $aliases = array_merge(array_fill_keys(array_values($func), ''), $aliases); 652 | 653 | $replace_aliases = function($query) use ($aliases) { 654 | foreach ($aliases as $regexp => $replace) { 655 | $query = preg_replace($regexp, $replace, $query); 656 | } 657 | return $query; 658 | }; 659 | 660 | $getNodeValue = function($doc, $query) use ($replace_aliases, $func){ 661 | /** @var \DOMDocument $doc */ 662 | $result = []; 663 | $xpath = new \DOMXpath($doc); 664 | 665 | $is_node = preg_match($func['node'], $query); 666 | $is_name = preg_match($func['name'], $query); 667 | $is_html = preg_match($func['html'], $query); 668 | $is_trim = preg_match($func['trim'], $query); 669 | $is_nameVal = preg_match($func['name_value'], $query); 670 | 671 | $query = $replace_aliases($query); 672 | $nodes = $xpath->query($query); 673 | 674 | if ($nodes instanceof \DOMNodeList) { 675 | foreach ($nodes as $node) { 676 | /** @var \DOMElement $node */ 677 | if ($is_node) { 678 | $result[] = $node; 679 | } else { 680 | if ($is_html) { 681 | $value = $doc->saveHTML($node); 682 | } 683 | elseif ($is_name) { 684 | $value = $node->nodeName; 685 | } 686 | elseif ($is_nameVal) { 687 | $value = [ 688 | 'name' => $node->nodeName, 689 | 'value' => $node->nodeValue, 690 | ]; 691 | } 692 | else { 693 | $value = $node->nodeValue; 694 | } 695 | 696 | if ($is_trim) { 697 | $value = trim($value); 698 | } 699 | 700 | $result[] = $value; 701 | } 702 | } 703 | } 704 | 705 | return $result; 706 | }; 707 | 708 | $doc = new \DOMDocument(); 709 | $doc->loadHTML($content); 710 | $result = []; 711 | 712 | if (is_array($this->xpath)) { 713 | foreach ($this->xpath as $id => $query) { 714 | if (is_array($query)) { 715 | $res = []; 716 | foreach ($query as $root => $sub) { 717 | if (is_array($sub)) { 718 | $r = $x = []; 719 | $max = 0; 720 | foreach ($sub as $j => $q) { 721 | $r[$j] = $getNodeValue($doc, "$root$q"); 722 | $max = max($max, count($r[$j])); 723 | } 724 | for ($i = 0; $i < $max; $i ++) { 725 | $x[$i] = []; 726 | foreach ($sub as $j => $q) { 727 | $x[$i][$j] = isset($r[$j][$i]) ? $r[$j][$i] : null; 728 | } 729 | } 730 | $res[] = $x; 731 | } else { 732 | $res[] = $getNodeValue($doc, "$root$sub"); 733 | } 734 | } 735 | $result[$id] = count($res) === 1 ? $res[0] : $res; 736 | } else { 737 | $result[$id] = $getNodeValue($doc, $query); 738 | } 739 | } 740 | } else { 741 | $result = $getNodeValue($doc, $this->xpath); 742 | } 743 | libxml_use_internal_errors(false); 744 | return $result; 745 | } 746 | return is_array($this->xpath) ? array_fill_keys(array_keys($this->xpath), []) : []; 747 | } 748 | 749 | /** 750 | * Fix strings to Proper-Case 751 | * @param string $str 752 | * @return string 753 | */ 754 | protected static function fixStringCase($str) 755 | { 756 | $str = explode('-', $str); 757 | foreach ($str as &$word) { 758 | $word = ucfirst($word); 759 | } 760 | return implode('-', $str); 761 | } 762 | } 763 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Andrey Izman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CurlHelper 2 | ========== 3 | 4 | Small useful helper for PHP Curl. 5 | Require PHP >= 5.4 6 | 7 | 8 | ## Installation 9 | Install via `composer`: 10 | ```bash 11 | composer require mervick/curl-helper 12 | ``` 13 | or download `CurlHelper.php` and include it in yours php files. 14 | 15 | 16 | ## Usage 17 | ```php 18 | $url = 'http://example.com/path/script?get1=val1'; 19 | 20 | // Simple GET request 21 | $response = Mervick\CurlHelper::factory($url)->exec(); 22 | 23 | var_dump($response); 24 | // Will be output array with few keys: 25 | // $response['status'] - http code, example "200' on success 26 | // $response['type'] - `Content-Type` from headers, example "application/json; charset=utf-8" 27 | // $response['headers'] - assoc array with all parsed headers 28 | // $response['cookies'] - assoc array with cookies from `Set-Cookie` headers 29 | // $response['headers_raw'] - headers raw 30 | // $response['content'] - content 31 | // $response['data'] - assoc array of json decoded content or false 32 | // $response['xpath'] - array with parsed xpath values or null 33 | 34 | 35 | // Add and/or modify some GET params 36 | $response = Mervick\CurlHelper::factory($url) 37 | ->setGetParams(['get2' => 'val2']) 38 | ->exec(); 39 | 40 | // Follow location 41 | $response = Mervick\CurlHelper::factory($url) 42 | ->follow(true) 43 | ->exec(); 44 | 45 | // Xpath 46 | $response = Mervick\CurlHelper::factory($url) 47 | ->xpath([ 48 | 'title' => '//title', 49 | 'token' => '*/meta[@name="csrf-token"]/@content' 50 | ]) 51 | ->exec(); 52 | 53 | // Writes verbose information to STDERR 54 | $response = Mervick\CurlHelper::factory($url) 55 | ->debug(true) 56 | ->exec(); 57 | 58 | // Writes verbose information to the file 59 | $response = Mervick\CurlHelper::factory($url) 60 | ->debug('/path/to/file') 61 | ->exec(); 62 | 63 | // POST request with headers 64 | $response = Mervick\CurlHelper::factory($url) 65 | // Content-Type: application/x-www-form-urlencoded 66 | // this one is default for POST, so you can skip it 67 | ->setHeaders(['Content-Type' => Mervick\CurlHelper::MIME_X_WWW_FORM]) 68 | ->setHeaders([ 69 | 'Some-Header1' => 'SomeValue1', 70 | 'Some-Header2' => 'SomeValue2', 71 | ]) 72 | ->setPostFields([ 73 | 'somePostField' => 'somePostVal', 74 | 'somePostArray' => [ // POST array 75 | 'item1', 76 | 'item2', 77 | ], 78 | ]) 79 | ->exec(); 80 | 81 | // JSON POST request 82 | $response = Mervick\CurlHelper::factory($url) 83 | // Content-Type: application/json 84 | ->setHeaders(['Content-Type' => Mervick\CurlHelper::MIME_JSON]) 85 | ->setPostFields(['somePostField' => 'somePostVal']) 86 | ->exec(); 87 | 88 | // Set cookies 89 | $response = Mervick\CurlHelper::factory($url) 90 | ->setCookies(['someField' => 'someVal']) 91 | ->exec(); 92 | 93 | // Send file 94 | $response = Mervick\CurlHelper::factory($url) 95 | // Content-Type: multipart/form-data 96 | // this is default for sending files, so you can skip it 97 | ->setHeaders(['Content-Type' => Mervick\CurlHelper::MIME_FORM_DATA]) 98 | ->putFile('fieldName', '/path/to/file') 99 | ->exec(); 100 | 101 | // Send multiple files 102 | $response = Mervick\CurlHelper::factory($url) 103 | ->putFile('fieldName', '/path/to/file1') 104 | ->putFile('fieldNameArr[]', '/path/to/file2') 105 | ->putFile('fieldNameArr[]', '/path/to/file3') 106 | ->exec(); 107 | 108 | // Send raw file 109 | $file_contents = file_get_contents('/file/to/path'); 110 | $response = Mervick\CurlHelper::factory($url) 111 | ->putFileRaw('fieldName', $file_contents, 'some.name', 'mime-type') 112 | ->exec(); 113 | 114 | // Send POST contents raw 115 | $response = Mervick\CurlHelper::factory($url) 116 | ->setPostRaw($postRawContents) 117 | ->exec(); 118 | 119 | // Save and read cookies from/to the file 120 | $response = Mervick\CurlHelper::factory($url) 121 | ->setCookieFile('/path/to/file') 122 | ->exec(); 123 | 124 | // Use proxy 125 | $response = Mervick\CurlHelper::factory($url) 126 | ->useProxy('192.168.1.1:8080', 'login', 'password') 127 | ->exec(); 128 | 129 | // Set custom CURL options 130 | $response = Mervick\CurlHelper::factory($url) 131 | ->setOptions([ 132 | CURLOPT_CONNECTTIMEOUT => 60, 133 | CURLOPT_PROXYTYPE => CURLPROXY_SOCKS5, 134 | ]) 135 | ->exec(); 136 | 137 | ``` 138 | 139 | ## Error 140 | If the curl process get an error it will throw a RuntimeException. 141 | Example: 142 | ```php 143 | $url = 'htp://example.com/path/script?get1=val1'; 144 | 145 | // Simple GET request 146 | $response = Mervick\CurlHelper::factory($url)->exec(); 147 | 148 | // PHP Fatal error: Uncaught RuntimeException: Protocol "htp" not supported or disabled in libcurl in ***/CurlHelper.php:554 149 | ``` 150 | 151 | ## License 152 | MIT 153 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mervick/curl-helper", 3 | "description": "PHP Curl Helper", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Andrey Izman", 8 | "email": "izman.dev@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.4.0", 13 | "ext-curl": "*" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Mervick\\": "" 18 | } 19 | } 20 | } 21 | --------------------------------------------------------------------------------