From 17ba86df255d4fa008df7297b188508627f4200b Mon Sep 17 00:00:00 2001 From: "dinu.marina" Date: Fri, 5 Aug 2022 18:24:46 +0300 Subject: [PATCH 1/3] Upgrade Symfony dependency to ^5 || ^6 --- composer.json | 38 +-- src/Collection.php | 8 +- src/ConstantsTable.php | 6 +- src/Event.php | 2 +- src/Options.php | 290 ++++++++++++++++++++--- src/Request.php | 254 ++++++++++---------- src/RequestInterface.php | 20 +- src/RequestsQueue.php | 415 +++++++++++++++++---------------- src/RequestsQueueInterface.php | 14 +- src/Response.php | 27 ++- tests/ConstantsTableTest.php | 4 + tests/OptionsTest.php | 22 +- tests/RequestTest.php | 269 +++++++++++---------- tests/RequestsQueueTest.php | 369 +++++++++++++++-------------- tests/TestCase.php | 60 ++--- tools/phpdoc.php | 28 +++ 16 files changed, 1074 insertions(+), 752 deletions(-) create mode 100644 tools/phpdoc.php diff --git a/composer.json b/composer.json index 43182d7..bf8cbd8 100644 --- a/composer.json +++ b/composer.json @@ -1,21 +1,23 @@ { - "name": "stil/curl-easy", - "description": "cURL wrapper for PHP. Supports parallel and non-blocking requests. For high speed crawling, see stil/curl-robot.", - "license": "MIT", - "require": { - "symfony/event-dispatcher": "^3.2" - }, - "require-dev": { - "phpunit/phpunit": "^6.1" - }, - "autoload": { - "psr-4": { - "cURL\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "cURL\\Tests\\": "tests/" - } + "name": "stil/curl-easy", + "description": "cURL wrapper for PHP. Supports parallel and non-blocking requests. For high speed crawling, see stil/curl-robot.", + "license": "MIT", + "require": { + "symfony/event-dispatcher": "^5.0", + "ext-curl": "*", + "ext-json": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "autoload": { + "psr-4": { + "cURL\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "cURL\\Tests\\": "tests/" + } + } } diff --git a/src/Collection.php b/src/Collection.php index c0f878f..1badbe6 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -14,7 +14,7 @@ class Collection * * @return array */ - public function toArray() + public function toArray(): array { return $this->data; } @@ -26,7 +26,7 @@ public function toArray() * @param mixed $value Value * @return self */ - public function set($key, $value = null) + public function set($key, $value = null): self { if (is_array($key)) { foreach ($key as $k => $v) { @@ -44,7 +44,7 @@ public function set($key, $value = null) * @param mixed $key Key * @return bool TRUE if exists, FALSE otherwise */ - public function has($key) + public function has($key): bool { return isset($this->data[$key]); } @@ -71,7 +71,7 @@ public function get($key) * @param mixed $key Key to remove * @return self */ - public function remove($key) + public function remove($key): self { unset($this->data[$key]); return $this; diff --git a/src/ConstantsTable.php b/src/ConstantsTable.php index 4a73192..be5342e 100644 --- a/src/ConstantsTable.php +++ b/src/ConstantsTable.php @@ -5,14 +5,14 @@ class ConstantsTable { /** - * @var int[] Array of cURL constants required for intelligent setters + * @var array Array of cURL constants required for intelligent setters */ protected static $curlConstantsTable = array(); /** * @return array */ - public static function loadCurlConstantsTable() + public static function loadCurlConstantsTable(): array { if (empty(self::$curlConstantsTable)) { $constants = get_defined_constants(true); @@ -32,7 +32,7 @@ public static function loadCurlConstantsTable() * @return int * @throws Exception */ - public static function findNumericValue($const) + public static function findNumericValue(string $const): int { $table = self::loadCurlConstantsTable(); diff --git a/src/Event.php b/src/Event.php index 44e2990..f942918 100644 --- a/src/Event.php +++ b/src/Event.php @@ -2,7 +2,7 @@ namespace cURL; -use Symfony\Component\EventDispatcher\Event as SymfonyEvent; +use Symfony\Contracts\EventDispatcher\Event as SymfonyEvent; class Event extends SymfonyEvent { diff --git a/src/Options.php b/src/Options.php index f0049ca..371f0f5 100644 --- a/src/Options.php +++ b/src/Options.php @@ -1,39 +1,271 @@ data)) { - curl_setopt_array($request->getHandle(), $this->data); - } + /** + * Applies options to Request object + * + * @param Request $request + * @return self + */ + public function applyTo(Request $request): self + { + if (!empty($this->data)) { + curl_setopt_array($request->getHandle(), $this->data); + } - return $this; - } + return $this; + } - /** - * Intelligent setters - * - * @param string $name Function name - * @param array $args Arguments - * @throws Exception Invalid CURLOPT_ constant has been specified - * @return self - */ - public function __call($name, $args) - { - if (substr($name, 0, 3) == 'set' && isset($args[0])) { - $const = strtoupper(substr($name, 3)); - $numericValue = ConstantsTable::findNumericValue($const); - $this->set($numericValue, $args[0]); + /** + * Intelligent setters + * + * @param string $name Function name + * @param array $args Arguments + * @return self + * @throws Exception Invalid CURLOPT_ constant has been specified + */ + public function __call(string $name, array $args): self + { + if (substr($name, 0, 3) == 'set' && isset($args[0])) { + $const = strtoupper(substr($name, 3)); + $numericValue = ConstantsTable::findNumericValue($const); + $this->set($numericValue, $args[0]); + } + return $this; } - return $this; } -} diff --git a/src/Request.php b/src/Request.php index 687eca0..9ab862c 100644 --- a/src/Request.php +++ b/src/Request.php @@ -1,147 +1,151 @@ getOptions()->set(CURLOPT_URL, $url); + /** + * Create new cURL handle + * + * @param string|null $url The URL to fetch. + */ + public function __construct(string $url = null) + { + parent::__construct(); + if ($url !== null) { + $this->getOptions()->set(CURLOPT_URL, $url); + } + $this->ch = curl_init(); } - $this->ch = curl_init(); - } - /** - * Closes cURL resource and frees the memory. - * It is neccessary when you make a lot of requests - * and you want to avoid fill up the memory. - */ - public function __destruct() - { - if (isset($this->ch)) { - curl_close($this->ch); + /** + * Closes cURL resource and frees the memory. + * It is neccessary when you make a lot of requests + * and you want to avoid fill up the memory. + */ + public function __destruct() + { + if (isset($this->ch)) { + curl_close($this->ch); + } } - } - /** - * Get the cURL\Options instance - * Creates empty one if does not exist - * - * @return Options - */ - public function getOptions() - { - if (!isset($this->options)) { - $this->options = new Options(); + /** + * Get the cURL\Options instance + * Creates empty one if does not exist + * + * @return Options + */ + public function getOptions(): Options + { + if (!isset($this->options)) { + $this->options = new Options(); + } + return $this->options; } - return $this->options; - } - - /** - * Sets Options - * - * @param Options $options Options - * @return void - */ - public function setOptions(Options $options) - { - $this->options = $options; - } - /** - * Returns cURL raw resource - * - * @return resource cURL handle - */ - public function getHandle() - { - return $this->ch; - } + /** + * Sets Options + * + * @param Options $options Options + * @return void + */ + public function setOptions(Options $options): void + { + $this->options = $options; + } - /** - * Get unique id of cURL handle - * Useful for debugging or logging. - * - * @return int - */ - public function getUID() - { - return (int)$this->ch; - } + /** + * Returns cURL raw resource + * + * @return resource|CurlHandle cURL handle + * @noinspection PhpMissingReturnTypeInspection + */ + public function getHandle() + { + return $this->ch; + } - /** - * Perform a cURL session. - * Equivalent to curl_exec(). - * This function should be called after initializing a cURL - * session and all the options for the session are set. - * - * Warning: it doesn't fire 'complete' event. - * - * @return Response - */ - public function send() - { - if ($this->options instanceof Options) { - $this->options->applyTo($this); + /** + * Get unique id of cURL handle + * Useful for debugging or logging. + * + * @return int + */ + public function getUID(): int + { + return (int)$this->ch; } - $content = curl_exec($this->ch); - $response = new Response($this, $content); - $errorCode = curl_errno($this->ch); - if ($errorCode !== CURLE_OK) { - $response->setError(new Error(curl_error($this->ch), $errorCode)); + /** + * Perform a cURL session. + * Equivalent to curl_exec(). + * This function should be called after initializing a cURL + * session and all the options for the session are set. + * + * Warning: it doesn't fire 'complete' event. + * + * @return Response + */ + public function send(): Response + { + if ($this->options instanceof Options) { + $this->options->applyTo($this); + } + $content = curl_exec($this->ch); + + $response = new Response($this, $content); + $errorCode = curl_errno($this->ch); + if ($errorCode !== CURLE_OK) { + $response->setError(new Error(curl_error($this->ch), $errorCode)); + } + return $response; } - return $response; - } - /** - * Creates new RequestsQueue with single Request attached to it - * and calls RequestsQueue::socketPerform() method. - * - * @see RequestsQueue::socketPerform() - */ - public function socketPerform() - { - if (!isset($this->queue)) { - $this->queue = new RequestsQueue(); - $this->queue->attach($this); + /** + * Creates new RequestsQueue with single Request attached to it + * and calls RequestsQueue::socketPerform() method. + * + * @throws Exception + * @see RequestsQueue::socketPerform() + */ + public function socketPerform(): bool + { + if (!isset($this->queue)) { + $this->queue = new RequestsQueue(); + $this->queue->attach($this); + } + return $this->queue->socketPerform(); } - return $this->queue->socketPerform(); - } - /** - * Calls socketSelect() on previously created RequestsQueue - * - * @see RequestsQueue::socketSelect() - */ - public function socketSelect($timeout = 1) - { - if (!isset($this->queue)) { - throw new Exception('You need to call socketPerform() before.'); + /** + * Calls socketSelect() on previously created RequestsQueue + * + * @throws Exception + * @see RequestsQueue::socketSelect() + */ + public function socketSelect($timeout = 1): bool + { + if (!isset($this->queue)) { + throw new Exception('You need to call socketPerform() before.'); + } + return $this->queue->socketSelect($timeout); } - return $this->queue->socketSelect($timeout); } -} diff --git a/src/RequestInterface.php b/src/RequestInterface.php index 7581280..f3962b3 100644 --- a/src/RequestInterface.php +++ b/src/RequestInterface.php @@ -1,18 +1,18 @@ mh = curl_multi_init(); - } + namespace cURL; - /** - * Destructor, closes curl_multi handler - * - * @return void - */ - public function __destruct() - { - if (isset($this->mh)) { - curl_multi_close($this->mh); - } - } + use Countable; + use CurlMultiHandle; + use Symfony\Component\EventDispatcher\EventDispatcher; - /** - * Returns cURL\Options object with default request's options - * - * @return Options - */ - public function getDefaultOptions() + class RequestsQueue extends EventDispatcher implements RequestsQueueInterface, Countable { - if (!isset($this->defaultOptions)) { - $this->defaultOptions = new Options(); + /** + * @var Options Default options for new Requests attached to RequestsQueue + */ + protected $defaultOptions = null; + + /** + * @var resource|CurlMultiHandle cURL multi handler + */ + protected $mh; + + /** + * @var Request[] Array of requests attached + */ + protected $queue = []; + + /** + * @var Request[] Array of requests added to curl multi handle + */ + protected $running = []; + + /** + * Initializes curl_multi handler + */ + public function __construct() + { + parent::__construct(); + $this->mh = curl_multi_init(); } - return $this->defaultOptions; - } - /** - * Overrides default options with given Options object - * - * @param Options $defaultOptions New options - * @return void - */ - public function setDefaultOptions(Options $defaultOptions) - { - $this->defaultOptions = $defaultOptions; - } + /** + * Destructor, closes curl_multi handler + * + * @return void + */ + public function __destruct() + { + if (isset($this->mh)) { + curl_multi_close($this->mh); + } + } - /** - * Get cURL multi handle - * - * @return resource - */ - public function getHandle() - { - return $this->mh; - } + /** + * Returns cURL\Options object with default request's options + * + * @return Options + */ + public function getDefaultOptions(): Options + { + if (!isset($this->defaultOptions)) { + $this->defaultOptions = new Options(); + } + return $this->defaultOptions; + } - /** - * Attach request to queue. - * - * @param Request $request Request to add - * @return self - */ - public function attach(Request $request) - { - $this->queue[$request->getUID()] = $request; - return $this; - } + /** + * Overrides default options with given Options object + * + * @param Options $defaultOptions New options + * @return void + */ + public function setDefaultOptions(Options $defaultOptions): void + { + $this->defaultOptions = $defaultOptions; + } - /** - * Detach request from queue. - * - * @param Request $request Request to remove - * @return self - */ - public function detach(Request $request) - { - unset($this->queue[$request->getUID()]); - return $this; - } + /** + * Get cURL multi handle + * + * @return resource|CurlMultiHandle + * @noinspection PhpMissingReturnTypeInspection + */ + public function getHandle() + { + return $this->mh; + } - /** - * Processes handles which are ready and removes them from pool. - * - * @return int Amount of requests completed - */ - protected function read() - { - $n = 0; - while ($info = curl_multi_info_read($this->mh)) { - $n++; - $request = $this->queue[(int)$info['handle']]; - $result = $info['result']; - - curl_multi_remove_handle($this->mh, $request->getHandle()); - unset($this->running[$request->getUID()]); - $this->detach($request); - - $event = new Event(); - $event->request = $request; - $event->response = new Response($request, curl_multi_getcontent($request->getHandle())); - if ($result !== CURLE_OK) { - $event->response->setError(new Error(curl_error($request->getHandle()), $result)); - } - $event->queue = $this; - $this->dispatch('complete', $event); - $request->dispatch('complete', $event); + /** + * Attach request to queue. + * + * @param Request $request Request to add + * @return self + */ + public function attach(Request $request): RequestsQueueInterface + { + $this->queue[$request->getUID()] = $request; + return $this; } - return $n; - } + /** + * Detach request from queue. + * + * @param Request $request Request to remove + * @return self + */ + public function detach(Request $request): RequestsQueueInterface + { + unset($this->queue[$request->getUID()]); + return $this; + } - /** - * Returns count of handles in queue - * - * @return int Handles count - */ - public function count() - { - return count($this->queue); - } + /** + * Processes handles which are ready and removes them from pool. + * + * @return int Amount of requests completed + */ + protected function read(): int + { + $n = 0; + while ($info = curl_multi_info_read($this->mh)) { + $n++; + $request = $this->queue[(int)$info['handle']]; + $result = $info['result']; + + curl_multi_remove_handle($this->mh, $request->getHandle()); + unset($this->running[$request->getUID()]); + $this->detach($request); + + $event = new Event(); + $event->request = $request; + $event->response = new Response($request, curl_multi_getcontent($request->getHandle())); + if ($result !== CURLE_OK) { + $event->response->setError(new Error(curl_error($request->getHandle()), $result)); + } + $event->queue = $this; + $this->dispatch($event, 'complete'); + $request->dispatch($event, 'complete'); + } - /** - * Executes requests in parallel - * - * @return void - */ - public function send() - { - while ($this->socketPerform()) { - $this->socketSelect(); + return $n; } - } - /** - * Returns requests present in $queue but not in $running - * - * @return Request[] Array of requests - */ - protected function getRequestsNotRunning() - { - $map = $this->queue; - foreach ($this->running as $k => $v) unset($map[$k]); - return $map; - } - - /** - * Download available data on socket. - * - * @throws Exception - * @return bool TRUE when there are any requests on queue, FALSE when finished - */ - public function socketPerform() - { - if ($this->count() == 0) { - throw new Exception('Cannot perform if there are no requests in queue.'); + /** + * Returns count of handles in queue + * + * @return int Handles count + */ + public function count(): int + { + return count($this->queue); } - $notRunning = $this->getRequestsNotRunning(); - do { - /** - * Apply cURL options to new requests - */ - foreach ($notRunning as $request) { - $this->getDefaultOptions()->applyTo($request); - $request->getOptions()->applyTo($request); - curl_multi_add_handle($this->mh, $request->getHandle()); - $this->running[$request->getUID()] = $request; + /** + * Executes requests in parallel + * + * @return void + * @throws Exception + */ + public function send(): void + { + while ($this->socketPerform()) { + $this->socketSelect(); } + } - $runningHandles = null; - do { - // http://curl.haxx.se/libcurl/c/curl_multi_perform.html - // If an added handle fails very quickly, it may never be counted as a running_handle. - $mrc = curl_multi_exec($this->mh, $runningHandles); - } while ($mrc === CURLM_CALL_MULTI_PERFORM); + /** + * Returns requests present in $queue but not in $running + * + * @return Request[] Array of requests + */ + protected function getRequestsNotRunning(): array + { + $map = $this->queue; + foreach ($this->running as $k => $v) { + unset($map[$k]); + } + return $map; + } - if ($runningHandles < count($this->running)) { - $this->read(); + /** + * Download available data on socket. + * + * @return bool TRUE when there are any requests on queue, FALSE when finished + * @throws Exception + */ + public function socketPerform(): bool + { + if ($this->count() == 0) { + throw new Exception('Cannot perform if there are no requests in queue.'); } $notRunning = $this->getRequestsNotRunning(); - } while (count($notRunning) > 0); - // Why the loop? New requests might be added at runtime on 'complete' event. - // So we need to attach them to curl_multi handle immediately. - - return $this->count() > 0; - } + do { + /** + * Apply cURL options to new requests + */ + foreach ($notRunning as $request) { + $this->getDefaultOptions()->applyTo($request); + $request->getOptions()->applyTo($request); + curl_multi_add_handle($this->mh, $request->getHandle()); + $this->running[$request->getUID()] = $request; + } + + $runningHandles = null; + do { + // http://curl.haxx.se/libcurl/c/curl_multi_perform.html + // If an added handle fails very quickly, it may never be counted as a running_handle. + $mrc = curl_multi_exec($this->mh, $runningHandles); + } while ($mrc === CURLM_CALL_MULTI_PERFORM); + + if ($runningHandles < count($this->running)) { + $this->read(); + } + + $notRunning = $this->getRequestsNotRunning(); + } while (count($notRunning) > 0); + // Why the loop? New requests might be added at runtime on 'complete' event. + // So we need to attach them to curl_multi handle immediately. + + return $this->count() > 0; + } - /** - * Waits until activity on socket - * On success, returns TRUE. On failure, this function will - * return FALSE on a select failure or timeout (from the underlying - * select system call) - * - * @param float|int $timeout Maximum time to wait - * @throws Exception - * @return bool - */ - public function socketSelect($timeout = 1) - { - if ($this->count() == 0) { - throw new Exception('Cannot select if there are no requests in queue.'); + /** + * Waits until activity on socket + * On success, returns TRUE. On failure, this function will + * return FALSE on a select failure or timeout (from the underlying + * select system call) + * + * @param float|int $timeout Maximum time to wait + * @return bool + * @throws Exception + */ + public function socketSelect($timeout = 1): bool + { + if ($this->count() == 0) { + throw new Exception('Cannot select if there are no requests in queue.'); + } + return curl_multi_select($this->mh, $timeout) !== -1; } - return curl_multi_select($this->mh, $timeout) !== -1; } -} diff --git a/src/RequestsQueueInterface.php b/src/RequestsQueueInterface.php index 127e8f9..4ba0d73 100644 --- a/src/RequestsQueueInterface.php +++ b/src/RequestsQueueInterface.php @@ -4,17 +4,17 @@ interface RequestsQueueInterface { - public function getDefaultOptions(); + public function getDefaultOptions(): Options; - public function setDefaultOptions(Options $defaultOptions); + public function setDefaultOptions(Options $defaultOptions): void; - public function attach(Request $request); + public function attach(Request $request): self; - public function detach(Request $request); + public function detach(Request $request): self; - public function send(); + public function send(): void; - public function socketPerform(); + public function socketPerform(): bool; - public function socketSelect($timeout); + public function socketSelect($timeout): bool; } diff --git a/src/Response.php b/src/Response.php index 0370db4..edbbb0b 100644 --- a/src/Response.php +++ b/src/Response.php @@ -2,19 +2,26 @@ namespace cURL; +use CurlHandle; + class Response { + /** @var resource|CurlHandle */ protected $ch; + + /** @var Error|null */ protected $error; + + /** @var string|null */ protected $content = null; /** * Constructs response * * @param Request $request Request - * @param string $content Content of reponse + * @param string|null $content Content of reponse */ - public function __construct(Request $request, $content = null) + public function __construct(Request $request, string $content = null) { $this->ch = $request->getHandle(); @@ -29,10 +36,10 @@ public function __construct(Request $request, $content = null) * Otherwise, returns an associative array with * the following elements (which correspond to opt), or FALSE on failure. * - * @param int $key One of the CURLINFO_* constants + * @param int|null $key One of the CURLINFO_* constants * @return mixed */ - public function getInfo($key = null) + public function getInfo(int $key = null) { return $key === null ? curl_getinfo($this->ch) : curl_getinfo($this->ch, $key); } @@ -42,7 +49,7 @@ public function getInfo($key = null) * * @return string Content */ - public function getContent() + public function getContent(): ?string { return $this->content; } @@ -63,17 +70,15 @@ public function setError(Error $error) * * @return Error|null */ - public function getError() + public function getError(): ?Error { - return isset($this->error) ? $this->error : null; + return $this->error; } /** - * Returns the error number for the last cURL operation. - * - * @return int Returns the error number or 0 (zero) if no error occurred. + * @return bool */ - public function hasError() + public function hasError(): bool { return isset($this->error); } diff --git a/tests/ConstantsTableTest.php b/tests/ConstantsTableTest.php index 7d1ed9d..4a214b8 100644 --- a/tests/ConstantsTableTest.php +++ b/tests/ConstantsTableTest.php @@ -3,9 +3,13 @@ namespace cURL\Tests; use cURL\ConstantsTable; +use cURL\Exception; class ConstantsTableTest extends TestCase { + /** + * @throws Exception + */ public function testFindNumericValue() { $this->assertEquals(CURLOPT_TIMEOUT, ConstantsTable::findNumericValue('TiMEOUT')); diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php index 6b1f416..bdc89de 100644 --- a/tests/OptionsTest.php +++ b/tests/OptionsTest.php @@ -18,14 +18,17 @@ public function testToArray() $opts = new Options(); $array = $opts->toArray(); - $this->assertInternalType('array', $array); + $this->assertIsArray($array); $this->assertEmpty($array); } + /** + * @throws Exception + */ protected function assertsForSet(Options $opts) { $array = $opts->toArray(); - $this->assertEquals(2, count($array)); + $this->assertCount(2, $array); $values = array( CURLOPT_TIMEOUT => 123, @@ -52,6 +55,9 @@ public function testMissingOption() $this->assertInstanceOf('cURL\Exception', $e); } + /** + * @throws Exception + */ public function testSingleSet() { $opts = new Options(); @@ -60,6 +66,9 @@ public function testSingleSet() $this->assertsForSet($opts); } + /** + * @throws Exception + */ public function testArraySet() { $opts = new Options(); @@ -72,6 +81,9 @@ public function testArraySet() $this->assertsForSet($opts); } + /** + * @throws Exception + */ public function testIntelligentSet() { $opts = new Options(); @@ -80,13 +92,17 @@ public function testIntelligentSet() $e = null; try { - $opts->setUserAgentt('browser'); + /** @noinspection PhpUndefinedMethodInspection */ + $opts->setUserAgentt('something'); } catch (Exception $e) { } $this->assertInstanceOf('cURL\Exception', $e); $this->assertsForSet($opts); } + /** + * @throws Exception + */ public function testFluentSetters() { $opts = new Options(); diff --git a/tests/RequestTest.php b/tests/RequestTest.php index 02769a4..edf7f12 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -1,150 +1,161 @@ getOptions(); - $this->assertInstanceOf('cURL\Options', $opts); - $this->assertEmpty($opts->toArray()); - - $opts = new cURL\Options(); - $opts->set(CURLOPT_RETURNTRANSFER, true); - $req->setOptions($opts); - $this->assertEquals($opts, $req->getOptions()); - - } - - /** - * Test synchronous request through send() - */ - public function testRequestSynchronous() + class RequestTest extends TestCase { /** - * Successful request + * Test setOptions() and getOptions() methods */ - $req = new cURL\Request(); - $req->getOptions() - ->set(CURLOPT_URL, $this->createRequestUrl()) - ->set(CURLOPT_RETURNTRANSFER, true); - $this->assertInternalType('resource', $req->getHandle()); - $this->validateSuccesfulResponse($req->send()); + public function testSetGetOptions() + { + $req = new cURL\Request(); + $opts = $req->getOptions(); + $this->assertInstanceOf('cURL\Options', $opts); + $this->assertEmpty($opts->toArray()); + + $opts = new cURL\Options(); + $opts->set(CURLOPT_RETURNTRANSFER, true); + $req->setOptions($opts); + $this->assertEquals($opts, $req->getOptions()); + } /** - * Timeouted request + * Test synchronous request through send() */ - $req = new cURL\Request(); - $req->getOptions() - ->set(CURLOPT_URL, $this->timeoutTestUrl) - ->set(CURLOPT_TIMEOUT, 3) - ->set(CURLOPT_RETURNTRANSFER, true); - $this->validateTimeoutedResponse($req->send()); - } - - /** - * Test asynchronous request through socketPerform() and socketSelect() - */ - public function testRequestAsynchronous() - { - $test = $this; - - $req = new cURL\Request(); - $req->getOptions() - ->set(CURLOPT_URL, $this->createRequestUrl()) - ->set(CURLOPT_RETURNTRANSFER, true); - $req->addListener( - 'complete', - function ($event) use ($test) { - $test->validateSuccesfulResponse($event->response); - } - ); - - $n = 0; - while ($req->socketPerform()) { - $n++; - $req->socketSelect(); + public function testRequestSynchronous() + { + /** + * Successful request + */ + $req = new cURL\Request(); + $req->getOptions() + ->set(CURLOPT_URL, $this->createRequestUrl()) + ->set(CURLOPT_RETURNTRANSFER, true); + $this->assertTrue( + is_resource($req->getHandle()) || + $req->getHandle() instanceof CurlHandle + ); + $this->validateSuccesfulResponse($req->send()); + + /** + * Timeouted request + */ + $req = new cURL\Request(); + $req->getOptions() + ->set(CURLOPT_URL, $this->timeoutTestUrl) + ->set(CURLOPT_TIMEOUT, 3) + ->set(CURLOPT_RETURNTRANSFER, true); + $this->validateTimeoutedResponse($req->send()); } - $e = null; - try { - $req->socketPerform(); - } catch (cURL\Exception $e) { - } - - $this->assertInstanceOf('cURL\Exception', $e); - $this->assertGreaterThan(0, $n); - - $req = new cURL\Request(); - $req->getOptions() - ->set(CURLOPT_URL, $this->timeoutTestUrl) - ->set(CURLOPT_TIMEOUT, 3) - ->set(CURLOPT_RETURNTRANSFER, true); - $req->addListener( - 'complete', - function ($event) use ($test) { - $test->validateTimeoutedResponse($event->response); + /** + * Test asynchronous request through socketPerform() and socketSelect() + * @throws Exception + */ + public function testRequestAsynchronous() + { + $test = $this; + + $req = new cURL\Request(); + $req->getOptions() + ->set(CURLOPT_URL, $this->createRequestUrl()) + ->set(CURLOPT_RETURNTRANSFER, true); + $req->addListener( + 'complete', + function ($event) use ($test) { + $test->validateSuccesfulResponse($event->response); + } + ); + + $n = 0; + while ($req->socketPerform()) { + $n++; + $req->socketSelect(); } - ); - - while ($req->socketPerform()) { - $req->socketSelect(); - } - } - /** - * Tests whether 'complete' event on individual Request has been fired - * once when using RequestsQueue - */ - public function testRequestCompleteEventAsynchronous() - { - $eventFired = 0; - - $req = new cURL\Request(); - $req->getOptions() - ->set(CURLOPT_URL, $this->createRequestUrl()) - ->set(CURLOPT_RETURNTRANSFER, true); - $req->addListener( - 'complete', - function ($event) use (&$eventFired) { - $this->validateSuccesfulResponse($event->response); - $eventFired++; + $e = null; + try { + $req->socketPerform(); + } catch (cURL\Exception $e) { } - ); - while ($req->socketPerform()) { - $req->socketSelect(); + $this->assertInstanceOf('cURL\Exception', $e); + $this->assertGreaterThan(0, $n); + + $req = new cURL\Request(); + $req->getOptions() + ->set(CURLOPT_URL, $this->timeoutTestUrl) + ->set(CURLOPT_TIMEOUT, 3) + ->set(CURLOPT_RETURNTRANSFER, true); + $req->addListener( + 'complete', + function ($event) use ($test) { + $test->validateTimeoutedResponse($event->response); + } + ); + + while ($req->socketPerform()) { + $req->socketSelect(); + } } - $this->assertEquals(1, $eventFired); - } - /** - * Tests whether 'complete' event on individual Request has not been fired - * when Request::send() was used. - */ - public function testRequestCompleteEventSynchronous() - { - $eventFired = 0; - - $req = new cURL\Request(); - $req->getOptions() - ->set(CURLOPT_URL, $this->createRequestUrl()) - ->set(CURLOPT_RETURNTRANSFER, true); - $req->addListener( - 'complete', - function (cURL\Event $event) use (&$eventFired) { - $eventFired++; + /** + * Tests whether 'complete' event on individual Request has been fired + * once when using RequestsQueue + * @throws Exception + */ + public function testRequestCompleteEventAsynchronous() + { + $eventFired = 0; + + $req = new cURL\Request(); + $req->getOptions() + ->set(CURLOPT_URL, $this->createRequestUrl()) + ->set(CURLOPT_RETURNTRANSFER, true); + $req->addListener( + 'complete', + function ($event) use (&$eventFired) { + $this->validateSuccesfulResponse($event->response); + $eventFired++; + } + ); + + while ($req->socketPerform()) { + $req->socketSelect(); } - ); + $this->assertEquals(1, $eventFired); + } - $req->send(); - $this->assertEquals(0, $eventFired); + /** + * Tests whether 'complete' event on individual Request has not been fired + * when Request::send() was used. + */ + public function testRequestCompleteEventSynchronous() + { + $eventFired = 0; + + $req = new cURL\Request(); + $req->getOptions() + ->set(CURLOPT_URL, $this->createRequestUrl()) + ->set(CURLOPT_RETURNTRANSFER, true); + $req->addListener( + 'complete', + /** + * @param Event $event + * @return void + */ + function (cURL\Event $event) use (&$eventFired) { + $eventFired++; + } + ); + + $req->send(); + $this->assertEquals(0, $eventFired); + } } -} diff --git a/tests/RequestsQueueTest.php b/tests/RequestsQueueTest.php index 1545ecb..7ba3068 100644 --- a/tests/RequestsQueueTest.php +++ b/tests/RequestsQueueTest.php @@ -1,205 +1,218 @@ send(); - } catch (cURL\Exception $ex) { - $bool = true; + /** + * Test run queue without any requests + */ + public function testSendNoRequests() + { + $bool = false; + try { + $q = new RequestsQueue(); + $q->send(); + } catch (cURL\Exception $ex) { + $bool = true; + } + $this->assertTrue($bool); } - $this->assertTrue($bool); - } - /** - * Test setDefaultOptions() and getDefaultOptions() - */ - public function testOptions() - { - $q = new cURL\RequestsQueue(); - $opts = $q->getDefaultOptions(); - $this->assertInstanceOf('cURL\Options', $opts); - $this->assertEmpty($opts->toArray()); - - $opts = new cURL\Options(); - $opts->set(CURLOPT_URL, 'http://example-1/'); - $opts->set(CURLOPT_USERAGENT, 'browser'); - $q->setDefaultOptions($opts); - $this->assertEquals($opts, $q->getDefaultOptions()); - } + /** + * Test setDefaultOptions() and getDefaultOptions() + */ + public function testOptions() + { + $q = new RequestsQueue(); + $opts = $q->getDefaultOptions(); + $this->assertInstanceOf('cURL\Options', $opts); + $this->assertEmpty($opts->toArray()); + + $opts = new cURL\Options(); + $opts->set(CURLOPT_URL, 'http://example-1/'); + $opts->set(CURLOPT_USERAGENT, 'browser'); + $q->setDefaultOptions($opts); + $this->assertEquals($opts, $q->getDefaultOptions()); + } - /** - * Returns RequestsQueue for tests - * - * @return cURL\RequestsQueue Queue for tests - */ - protected function prepareTestQueue() - { - $test = $this; - $queue = new cURL\RequestsQueue(); - $queue->getDefaultOptions() - ->set(CURLOPT_RETURNTRANSFER, true) - ->set(CURLOPT_ENCODING, ''); - $queue->addListener( - 'complete', - function (cURL\Event $event) use ($test) { - $test->validateSuccesfulResponse($event->response, $event->request->_param); + /** + * Returns RequestsQueue for tests + * + * @return RequestsQueue Queue for tests + */ + protected function prepareTestQueue(): RequestsQueue + { + $test = $this; + $queue = new RequestsQueue(); + $queue->getDefaultOptions() + ->set(CURLOPT_RETURNTRANSFER, true) + ->set(CURLOPT_ENCODING, ''); + + $s = new SplObjectStorage(); + + $queue->addListener( + 'complete', + function (cURL\Event $event) use ($test, $s) { + $test->validateSuccesfulResponse($event->response, $s[$event->request]); + } + ); + + for ($i = 0; $i < 5; $i++) { + $request = new cURL\Request(); + $s[$request] = $i; + $request->getOptions()->set(CURLOPT_URL, $this->createRequestUrl($i)); + $queue->attach($request); } - ); - for ($i = 0; $i < 5; $i++) { - $request = new cURL\Request(); - $request->_param = $i; - $request->getOptions()->set(CURLOPT_URL, $this->createRequestUrl($i)); - $queue->attach($request); + $this->assertEquals(5, $queue->count()); + $this->assertCount(5, $queue); + + return $queue; } - $this->assertEquals(5, $queue->count()); - $this->assertEquals(5, count($queue)); + /** + * Test request synchronous + * @throws Exception + */ + public function testQueueSynchronous() + { + $queue = $this->prepareTestQueue(); + $queue->send(); + } - return $queue; - } + /** + * Test request asynchronous + * @throws Exception + */ + public function testQueueAsynchronous() + { + $queue = $this->prepareTestQueue(); - /** - * Test request synchronous - */ - public function testQueueSynchronous() - { - $queue = $this->prepareTestQueue(); - $queue->send(); - } + while ($queue->socketPerform()) { + $queue->socketSelect(); + } - /** - * Test request asynchronous - */ - public function testQueueAsynchronous() - { - $queue = $this->prepareTestQueue(); + $e = null; + try { + $queue->socketPerform(); + } catch (cURL\Exception $e) { + } - while ($queue->socketPerform()) { - $queue->socketSelect(); + $this->assertInstanceOf('cURL\Exception', $e); } - $e = null; - try { - $queue->socketPerform(); - } catch (cURL\Exception $e) { + /** + * Test requests attaching on run time + * @throws Exception + */ + public function testRepeatOnRuntime() + { + $n = 0; + $queue = $this->prepareTestQueue(); + $queue->addListener( + 'complete', + function (cURL\Event $event) use (&$n) { + $n++; + $request = $event->request; + $queue = $event->queue; + if (!isset($request->repeat)) { + $request->repeat = true; + $queue->attach($request); + } + } + ); + $queue->send(); + $this->assertEquals(10, $n); } - $this->assertInstanceOf('cURL\Exception', $e); - } - - /** - * Test requests attaching on run time - */ - public function testRepeatOnRuntime() - { - $n = 0; - $queue = $this->prepareTestQueue(); - $queue->addListener( - 'complete', - function (cURL\Event $event) use (&$n) { - $n++; - $request = $event->request; - $queue = $event->queue; - if (!isset($request->repeat)) { - $request->repeat = true; + /** + * Test requests attaching on run time + * @throws Exception + */ + public function testAttachNewOnRuntime() + { + $total = 10; + $test = $this; + $queue = new RequestsQueue(); + $queue->getDefaultOptions() + ->set(CURLOPT_RETURNTRANSFER, true) + ->set(CURLOPT_ENCODING, ''); + + $s = new SplObjectStorage(); + + $n = 0; + $attachNew = function () use ($queue, &$n, $total, $s) { + if ($n < $total) { + $n++; + $request = new cURL\Request(); + $s[$request] = $n; + $request->getOptions()->set(CURLOPT_URL, $this->createRequestUrl($n)); $queue->attach($request); } - } - ); - $queue->send(); - $this->assertEquals(10, $n); - } - - /** - * Test requests attaching on run time - */ - public function testAttachNewOnRuntime() - { - $total = 10; - $test = $this; - $queue = new cURL\RequestsQueue(); - $queue->getDefaultOptions() - ->set(CURLOPT_RETURNTRANSFER, true) - ->set(CURLOPT_ENCODING, ''); - - - $n = 0; - $attachNew = function () use ($queue, &$n, $total) { - if ($n < $total) { - $n++; - $request = new cURL\Request(); - $request->_param = $n; - $request->getOptions()->set(CURLOPT_URL, $this->createRequestUrl($n)); - $queue->attach($request); - } - }; - - $attachNew(); - $queue->addListener( - 'complete', - function (cURL\Event $event) use (&$requests, $test, $attachNew) { - $test->validateSuccesfulResponse($event->response, $event->request->_param); - $attachNew(); - } - ); - $queue->send(); - $this->assertEquals($total, $n); - } + }; + + $attachNew(); + $queue->addListener( + 'complete', + function (cURL\Event $event) use (&$requests, $test, $attachNew, $s) { + $test->validateSuccesfulResponse($event->response, $s[$event->request]); + $attachNew(); + } + ); + $queue->send(); + $this->assertEquals($total, $n); + } - /** - * Tests whether 'complete' event on individual Request has been fired - * when using RequestsQueue - */ - public function testRequestCompleteEvent() - { - $eventFired = 0; - - $req = new cURL\Request(); - $req->getOptions() - ->set(CURLOPT_URL, $this->createRequestUrl()) - ->set(CURLOPT_RETURNTRANSFER, true); - $req->addListener( - 'complete', - function ($event) use (&$eventFired) { - $this->validateSuccesfulResponse($event->response); - $eventFired++; - } - ); + /** + * Tests whether 'complete' event on individual Request has been fired + * when using RequestsQueue + * @throws Exception + */ + public function testRequestCompleteEvent() + { + $eventFired = 0; + + $req = new cURL\Request(); + $req->getOptions() + ->set(CURLOPT_URL, $this->createRequestUrl()) + ->set(CURLOPT_RETURNTRANSFER, true); + $req->addListener( + 'complete', + function ($event) use (&$eventFired) { + $this->validateSuccesfulResponse($event->response); + $eventFired++; + } + ); - $queue = new cURL\RequestsQueue(); - $queue->attach($req); - $queue->send(); + $queue = new RequestsQueue(); + $queue->attach($req); + $queue->send(); - $this->assertEquals(1, $eventFired); - } + $this->assertEquals(1, $eventFired); + } - /** - * Requests which fail very quickly might cause infinite loop and return no response. - * http://curl.haxx.se/libcurl/c/curl_multi_perform.html - * "If an added handle fails very quickly, it may never be counted as a running_handle." - * This test ensures that it won't loop and will return properly error code. - */ - public function testErrorCode() - { - $request = new cURL\Request(''); - $request->addListener('complete', function (cURL\Event $e) { - $this->assertEquals(' malformed', $e->response->getError()->getMessage()); - }); - - $queue = new cURL\RequestsQueue(); - $queue->attach($request); - $queue->send(); + /** + * Requests which fail very quickly might cause infinite loop and return no response. + * http://curl.haxx.se/libcurl/c/curl_multi_perform.html + * "If an added handle fails very quickly, it may never be counted as a running_handle." + * This test ensures that it won't loop and will return properly error code. + * @throws Exception + */ + public function testErrorCode() + { + $request = new cURL\Request(''); + $request->addListener('complete', function (cURL\Event $e) { + $this->assertEquals(3, $e->response->getError()->getCode()); + }); + + $queue = new RequestsQueue(); + $queue->attach($request); + $queue->send(); + } } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index a77be95..0994e3b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,38 +1,38 @@ okTestUrl . '?' . http_build_query(array('curl-easy' => $param)); - } + protected $okTestUrl = 'https://httpbin.org/get'; + protected $timeoutTestUrl = 'https://httpbin.org/delay/10'; - public function validateSuccesfulResponse(Response $response, $param = 'ok') - { - $content = $response->getContent(); - $data = json_decode($content, true); - $this->assertInternalType('array', $data); - $info = $response->getInfo(); - $this->assertInternalType('array', $info); - $this->assertEquals(200, $info['http_code']); - $this->assertEquals($param, $data['args']['curl-easy']); - $this->assertEquals(200, $response->getInfo(CURLINFO_HTTP_CODE)); - $this->assertFalse($response->hasError()); - } + public function createRequestUrl($param = 'ok'): string + { + return $this->okTestUrl . '?' . http_build_query(['curl-easy' => $param]); + } - public function validateTimeoutedResponse(Response $response) - { - $this->assertEmpty($response->getContent()); - $this->assertTrue($response->hasError()); - $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $response->getError()->getCode()); - $this->assertNotEmpty($response->getError()->getMessage()); + public function validateSuccesfulResponse(Response $response, $param = 'ok') + { + $content = $response->getContent(); + $data = json_decode($content, true); + $this->assertIsArray($data); + $info = $response->getInfo(); + $this->assertIsArray($info); + $this->assertEquals(200, $info['http_code']); + $this->assertEquals($param, $data['args']['curl-easy']); + $this->assertEquals(200, $response->getInfo(CURLINFO_HTTP_CODE)); + $this->assertFalse($response->hasError()); + } + + public function validateTimeoutedResponse(Response $response) + { + $this->assertEmpty($response->getContent()); + $this->assertTrue($response->hasError()); + $this->assertEquals(CURLE_OPERATION_TIMEOUTED, $response->getError()->getCode()); + $this->assertNotEmpty($response->getError()->getMessage()); + } } -} diff --git a/tools/phpdoc.php b/tools/phpdoc.php new file mode 100644 index 0000000..95e43f0 --- /dev/null +++ b/tools/phpdoc.php @@ -0,0 +1,28 @@ + $value) { + if (strpos($key, 'CURLOPT_') === 0) { + $key = substr($key, 8); + $camelCase = implode( + '', + array_map( + function (string $keyFrag) { + return strtoupper($keyFrag[0]) . strtolower(substr($keyFrag, 1)); + }, + preg_split('`_+`', $key) + ) + ); + $camelCasedConstants[] = $camelCase; + } + } + echo implode( + "\n", + array_map( + function(string $constant): string { + return " * @method set$constant(mixed \$value)"; + }, + $camelCasedConstants + ) + ); + echo "\n"; \ No newline at end of file From f6b9a361078e2955574d7c2dcdb4c72d06186a80 Mon Sep 17 00:00:00 2001 From: "dinu.marina" Date: Fri, 5 Aug 2022 18:36:34 +0300 Subject: [PATCH 2/3] Changelog --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d544515 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# 2.0.0 + +#### Changes + +* Requires PHP >= 7.2 +* Static signature typing to the level of PHP 7.2 +* Nothing else changed + +#### New features/improvements + +* Works with `symfony/event-dispatcher` `^5.0`, `^6.0` +* Improved static typing and phpdoc annotations + +#### Dev + +* Upgraded `phpunit/phpunit` \ No newline at end of file From 8cdadad8138220c643c9aa2a6ce07cc8bc05c395 Mon Sep 17 00:00:00 2001 From: "dinu.marina" Date: Fri, 5 Aug 2022 18:53:12 +0300 Subject: [PATCH 3/3] Fix `ext-json` dependency to testing --- composer.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index bf8cbd8..d515cbe 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,15 @@ { "name": "stil/curl-easy", + "version": "2.0.0", "description": "cURL wrapper for PHP. Supports parallel and non-blocking requests. For high speed crawling, see stil/curl-robot.", "license": "MIT", "require": { - "symfony/event-dispatcher": "^5.0", - "ext-curl": "*", - "ext-json": "*" + "symfony/event-dispatcher": "^5.0 || ^6.0", + "ext-curl": "*" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "ext-json": "*" }, "autoload": { "psr-4": {