From 4ec89be9bce60ff43258b005ff08618d5274b040 Mon Sep 17 00:00:00 2001 From: Zach Borboa Date: Sat, 2 Sep 2017 02:23:40 -0700 Subject: [PATCH] Allow setRetry() methods to use callables --- README.md | 6 +- src/Curl/Curl.php | 64 ++++++++++++---- src/Curl/MultiCurl.php | 19 ++--- tests/PHPCurlClass/PHPCurlClassTest.php | 77 +++++++++++++++++++ tests/PHPCurlClass/PHPMultiCurlClassTest.php | 80 ++++++++++++++++++++ 5 files changed, 219 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 4382d9f..0c2660e 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ More examples are available under [/examples](https://github.com/php-curl-class/ Curl::__construct($base_url = null) Curl::__destruct() Curl::__get($name) +Curl::attemptRetry() Curl::beforeSend($callback) Curl::buildPostData($data) Curl::call() @@ -203,6 +204,7 @@ Curl::delete($url, $query_parameters = array(), $data = array()) Curl::download($url, $mixed_filename) Curl::error($callback) Curl::exec($ch = null) +Curl::execDone() Curl::get($url, $data = array()) Curl::getCookie($key) Curl::getInfo($opt = null) @@ -238,7 +240,7 @@ Curl::setOpts($options) Curl::setPort($port) Curl::setReferer($referer) Curl::setReferrer($referrer) -Curl::setRetry($maximum_number_of_retries = 0) +Curl::setRetry($mixed) Curl::setTimeout($seconds) Curl::setUrl($url, $mixed_data = '') Curl::setUserAgent($user_agent) @@ -281,7 +283,7 @@ MultiCurl::setOpts($options) MultiCurl::setPort($port) MultiCurl::setReferer($referer) MultiCurl::setReferrer($referrer) -MultiCurl::setRetry($maximum_number_of_retries = 0) +MultiCurl::setRetry($mixed) MultiCurl::setTimeout($seconds) MultiCurl::setUrl($url) MultiCurl::setUserAgent($user_agent) diff --git a/src/Curl/Curl.php b/src/Curl/Curl.php index 5a5ea54..d02abd2 100644 --- a/src/Curl/Curl.php +++ b/src/Curl/Curl.php @@ -45,7 +45,7 @@ class Curl public $retries = 0; public $isChildOfMultiCurl = false; public $remainingRetries = 0; - private $maximumNumberOfRetries = 0; + public $retryDecider = null; private $cookies = array(); private $headers = array(); @@ -390,18 +390,22 @@ class Curl } $this->errorMessage = $this->curlError ? $this->curlErrorMessage : $this->httpErrorMessage; - if ($this->error && $this->remainingRetries >= 1) { - $this->retries += 1; + // Allow multicurl to attempt retry as needed. + if ($this->isChildOfMultiCurl) { + return; + } - // Allow multicurl to update $remainingRetries and retry. - if ($this->isChildOfMultiCurl) { - return; - } - - $this->remainingRetries -= 1; + if ($this->attemptRetry()) { return $this->exec($ch); } + $this->execDone(); + + return $this->response; + } + + public function execDone() + { if ($this->error) { $this->call($this->errorFunction); } else { @@ -414,8 +418,6 @@ class Curl if (!($this->fileHandle === null)) { $this->downloadComplete($this->fileHandle); } - - return $this->response; } /** @@ -1033,15 +1035,20 @@ class Curl /** * Set Retry * - * Number of retries to attempt. Maximum number of attempts is $maximum_number_of_retries + 1. + * Number of retries to attempt or decider callable. Maximum number of + * attempts is $maximum_number_of_retries + 1. * * @access public - * @param $maximum_number_of_retries + * @param $mixed */ - public function setRetry($maximum_number_of_retries = 0) + public function setRetry($mixed) { - $this->maximumNumberOfRetries = $maximum_number_of_retries; - $this->remainingRetries = $this->maximumNumberOfRetries; + if (is_callable($mixed)) { + $this->retryDecider = $mixed; + } elseif (is_int($mixed)) { + $maximum_number_of_retries = $mixed; + $this->remainingRetries = $maximum_number_of_retries; + } } /** @@ -1080,6 +1087,31 @@ class Curl $this->setOpt(CURLOPT_USERAGENT, $user_agent); } + /** + * Attempt Retry + * + * @access public + */ + public function attemptRetry() + { + $attempt_retry = false; + if ($this->error) { + if ($this->retryDecider === null) { + $attempt_retry = $this->remainingRetries >= 1; + } else { + $func = $this->retryDecider; + $attempt_retry = $func($this); + } + if ($attempt_retry) { + $this->retries += 1; + if ($this->remainingRetries) { + $this->remainingRetries -= 1; + } + } + } + return $attempt_retry; + } + /** * Success * diff --git a/src/Curl/MultiCurl.php b/src/Curl/MultiCurl.php index d2e3540..ea24d46 100644 --- a/src/Curl/MultiCurl.php +++ b/src/Curl/MultiCurl.php @@ -18,7 +18,7 @@ class MultiCurl private $errorFunction = null; private $completeFunction = null; - private $maximumNumberOfRetries = 0; + private $retry = null; private $cookies = array(); private $headers = array(); @@ -592,14 +592,15 @@ class MultiCurl /** * Set Retry * - * Number of retries to attempt. Maximum number of attempts is $maximum_number_of_retries + 1. + * Number of retries to attempt or decider callable. Maximum number of + * attempts is $maximum_number_of_retries + 1. * * @access public - * @param $maximum_number_of_retries + * @param $mixed */ - public function setRetry($maximum_number_of_retries = 0) + public function setRetry($mixed) { - $this->maximumNumberOfRetries = $maximum_number_of_retries; + $this->retry = $mixed; } /** @@ -676,9 +677,7 @@ class MultiCurl $ch->curlErrorCode = $info_array['result']; $ch->exec($ch->curl); - if ($ch->error && $ch->remainingRetries >= 1) { - $ch->remainingRetries -= 1; - + if ($ch->attemptRetry()) { // Remove completed handle before adding again in order to retry request. curl_multi_remove_handle($this->multiCurl, $ch->curl); @@ -689,6 +688,8 @@ class MultiCurl ); } } else { + $ch->execDone(); + // Remove completed instance from active curls. unset($this->activeCurls[$key]); @@ -821,7 +822,7 @@ class MultiCurl $curl->setOpts($this->options); $curl->setHeaders($this->headers); - $curl->setRetry($this->maximumNumberOfRetries); + $curl->setRetry($this->retry); foreach ($this->cookies as $key => $value) { $curl->setCookie($key, $value); diff --git a/tests/PHPCurlClass/PHPCurlClassTest.php b/tests/PHPCurlClass/PHPCurlClassTest.php index d533c84..700d5ff 100644 --- a/tests/PHPCurlClass/PHPCurlClassTest.php +++ b/tests/PHPCurlClass/PHPCurlClassTest.php @@ -3321,4 +3321,81 @@ class CurlTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expect_retries, $test->curl->retries); } } + + public function testRetryCallable() + { + $tests = array( + array( + 'maximum_number_of_retries' => null, + 'failures' => 0, + 'expect_success' => true, + 'expect_attempts' => 1, + 'expect_retries' => 0, + ), + array( + 'maximum_number_of_retries' => 0, + 'failures' => 0, + 'expect_success' => true, + 'expect_attempts' => 1, + 'expect_retries' => 0, + ), + array( + 'maximum_number_of_retries' => 0, + 'failures' => 1, + 'expect_success' => false, + 'expect_attempts' => 1, + 'expect_retries' => 0, + ), + array( + 'maximum_number_of_retries' => 1, + 'failures' => 1, + 'expect_success' => true, + 'expect_attempts' => 2, + 'expect_retries' => 1, + ), + array( + 'maximum_number_of_retries' => 1, + 'failures' => 2, + 'expect_success' => false, + 'expect_attempts' => 2, + 'expect_retries' => 1, + ), + array( + 'maximum_number_of_retries' => 2, + 'failures' => 2, + 'expect_success' => true, + 'expect_attempts' => 3, + 'expect_retries' => 2, + ), + array( + 'maximum_number_of_retries' => 3, + 'failures' => 3, + 'expect_success' => true, + 'expect_attempts' => 4, + 'expect_retries' => 3, + ), + ); + foreach ($tests as $test) { + $maximum_number_of_retries = $test['maximum_number_of_retries']; + $failures = $test['failures']; + $expect_success = $test['expect_success']; + $expect_attempts = $test['expect_attempts']; + $expect_retries = $test['expect_retries']; + + $test = new Test(); + $test->curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + + if (!($maximum_number_of_retries === null)) { + $test->curl->setRetry(function ($instance) use ($maximum_number_of_retries) { + $return = $instance->retries < $maximum_number_of_retries; + return $return; + }); + } + + $test->server('retry', 'GET', array('failures' => $failures)); + $this->assertEquals($expect_success, !$test->curl->error); + $this->assertEquals($expect_attempts, $test->curl->attempts); + $this->assertEquals($expect_retries, $test->curl->retries); + } + } } diff --git a/tests/PHPCurlClass/PHPMultiCurlClassTest.php b/tests/PHPCurlClass/PHPMultiCurlClassTest.php index 4f0995c..1f248c9 100644 --- a/tests/PHPCurlClass/PHPMultiCurlClassTest.php +++ b/tests/PHPCurlClass/PHPMultiCurlClassTest.php @@ -2667,4 +2667,84 @@ class MultiCurlTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expect_retries, $instance->retries); } } + + public function testRetryCallableMulti() + { + $tests = array( + array( + 'maximum_number_of_retries' => null, + 'failures' => 0, + 'expect_success' => true, + 'expect_attempts' => 1, + 'expect_retries' => 0, + ), + array( + 'maximum_number_of_retries' => 0, + 'failures' => 0, + 'expect_success' => true, + 'expect_attempts' => 1, + 'expect_retries' => 0, + ), + array( + 'maximum_number_of_retries' => 0, + 'failures' => 1, + 'expect_success' => false, + 'expect_attempts' => 1, + 'expect_retries' => 0, + ), + array( + 'maximum_number_of_retries' => 1, + 'failures' => 1, + 'expect_success' => true, + 'expect_attempts' => 2, + 'expect_retries' => 1, + ), + array( + 'maximum_number_of_retries' => 1, + 'failures' => 2, + 'expect_success' => false, + 'expect_attempts' => 2, + 'expect_retries' => 1, + ), + array( + 'maximum_number_of_retries' => 2, + 'failures' => 2, + 'expect_success' => true, + 'expect_attempts' => 3, + 'expect_retries' => 2, + ), + array( + 'maximum_number_of_retries' => 3, + 'failures' => 3, + 'expect_success' => true, + 'expect_attempts' => 4, + 'expect_retries' => 3, + ), + ); + foreach ($tests as $test) { + $maximum_number_of_retries = $test['maximum_number_of_retries']; + $failures = $test['failures']; + $expect_success = $test['expect_success']; + $expect_attempts = $test['expect_attempts']; + $expect_retries = $test['expect_retries']; + + $multi_curl = new MultiCurl(); + $multi_curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $multi_curl->setHeader('X-DEBUG-TEST', 'retry'); + + if (!($maximum_number_of_retries === null)) { + $multi_curl->setRetry(function ($instance) use ($maximum_number_of_retries) { + $return = $instance->retries < $maximum_number_of_retries; + return $return; + }); + } + + $instance = $multi_curl->addGet(Test::TEST_URL, array('failures' => $failures)); + $multi_curl->start(); + + $this->assertEquals($expect_success, !$instance->error); + $this->assertEquals($expect_attempts, $instance->attempts); + $this->assertEquals($expect_retries, $instance->retries); + } + } }