diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4bf2b84..fb6353bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,3 +120,36 @@ jobs: uses: docker://hhvm/hhvm:3.30-lts-latest with: args: hhvm vendor/bin/phpunit + + PHPStan: + name: PHPStan (PHP ${{ matrix.php }}) + runs-on: ubuntu-22.04 + strategy: + matrix: + php: + - 8.3 + - 8.2 + - 8.1 + - 8.0 + - 7.4 + - 7.3 + - 7.2 + - 7.1 + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + ini-file: development + ini-values: disable_functions='' # do not disable PCNTL functions on PHP < 8.1 + extensions: sockets, pcntl ${{ matrix.php >= 5.6 && ', event' || '' }} ${{ matrix.php >= 5.4 && ', ev' || '' }} + env: + fail-fast: true # fail step if any extension can not be installed + - name: Install ext-uv on PHP 7+ + run: | + sudo apt-get update -q && sudo apt-get install libuv1-dev + echo "yes" | sudo pecl install ${{ matrix.php >= 8.0 && 'uv-0.3.0' || 'uv-0.2.4' }} + php -m | grep -q uv || echo "extension=uv.so" >> "$(php -r 'echo php_ini_loaded_file();')" + - run: composer install + - run: vendor/bin/phpstan diff --git a/composer.json b/composer.json index 25a41fe1..c840d529 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,7 @@ "php": ">=5.3.0" }, "require-dev": { + "phpstan/phpstan": "^1", "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "suggest": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..836d5790 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,6 @@ +parameters: + level: 5 + + paths: + - src/ + - tests/ diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php index a3fcec68..3072bc1b 100644 --- a/src/ExtEvLoop.php +++ b/src/ExtEvLoop.php @@ -192,6 +192,10 @@ public function run() $this->futureTickQueue->tick(); $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + /** + * @link https://github.com/phpstan/phpstan/issues/10566 + * @phpstan-ignore-next-line + */ $wasJustStopped = !$this->running; $nothingLeftToDo = !$this->readStreams && !$this->writeStreams @@ -199,6 +203,10 @@ public function run() && $this->signals->isEmpty(); $flags = Ev::RUN_ONCE; + /** + * @link https://github.com/phpstan/phpstan/issues/10566 + * @phpstan-ignore-next-line + */ if ($wasJustStopped || $hasPendingCallbacks) { $flags |= Ev::RUN_NOWAIT; } elseif ($nothingLeftToDo) { @@ -222,11 +230,13 @@ public function __destruct() } foreach ($this->readStreams as $key => $stream) { - $this->removeReadStream($key); + $this->readStreams[$key]->stop(); + unset($this->readStreams[$key]); } foreach ($this->writeStreams as $key => $stream) { - $this->removeWriteStream($key); + $this->readStreams[$key]->stop(); + unset($this->readStreams[$key]); } } diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index b162a402..3e720628 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -196,6 +196,10 @@ public function run() $this->futureTickQueue->tick(); $flags = EventBase::LOOP_ONCE; + /** + * @link https://github.com/phpstan/phpstan/issues/10566 + * @phpstan-ignore-next-line + */ if (!$this->running || !$this->futureTickQueue->isEmpty()) { $flags |= EventBase::LOOP_NONBLOCK; } elseif (!$this->readEvents && !$this->writeEvents && !$this->timerEvents->count() && $this->signals->isEmpty()) { diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index 4434720d..3a20265d 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -213,6 +213,10 @@ public function run() $this->futureTickQueue->tick(); $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + /** + * @link https://github.com/phpstan/phpstan/issues/10566 + * @phpstan-ignore-next-line + */ $wasJustStopped = !$this->running; $nothingLeftToDo = !$this->readStreams && !$this->writeStreams @@ -223,12 +227,20 @@ public function run() // otherwise use UV::RUN_NOWAIT. // @link http://docs.libuv.org/en/v1.x/loop.html#c.uv_run $flags = \UV::RUN_ONCE; + /** + * @link https://github.com/phpstan/phpstan/issues/10566 + * @phpstan-ignore-next-line + */ if ($wasJustStopped || $hasPendingCallbacks) { $flags = \UV::RUN_NOWAIT; } elseif ($nothingLeftToDo) { break; } + /** + * @link https://github.com/JetBrains/phpstorm-stubs/pull/1614 + * @phpstan-ignore-next-line + */ \uv_run($this->uv, $flags); } } @@ -261,6 +273,10 @@ private function removeStream($stream) if (!isset($this->readStreams[(int) $stream]) && !isset($this->writeStreams[(int) $stream])) { \uv_poll_stop($this->streamEvents[(int) $stream]); + /** + * @link https://github.com/JetBrains/phpstorm-stubs/pull/1615 + * @phpstan-ignore-next-line + */ \uv_close($this->streamEvents[(int) $stream]); unset($this->streamEvents[(int) $stream]); return; diff --git a/src/SignalsHandler.php b/src/SignalsHandler.php index 10d125df..c3c39040 100644 --- a/src/SignalsHandler.php +++ b/src/SignalsHandler.php @@ -7,6 +7,9 @@ */ final class SignalsHandler { + /** + * @var array> + */ private $signals = array(); public function add($signal, $listener) @@ -47,6 +50,9 @@ public function call($signal) } } + /** + * @return int + */ public function count($signal) { if (!isset($this->signals[$signal])) { diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 943a81aa..cdc0e6ca 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -63,6 +63,9 @@ final class StreamSelectLoop implements LoopInterface private $running; private $pcntl = false; private $pcntlPoll = false; + /** + * @var SignalsHandler + */ private $signals; public function __construct() @@ -169,6 +172,10 @@ public function removeSignal($signal, $listener) $this->signals->remove($signal, $listener); + /** + * @link https://github.com/phpstan/phpstan/issues/10576 + * @phpstan-ignore-next-line + */ if ($this->signals->count($signal) === 0) { \pcntl_signal($signal, \SIG_DFL); } @@ -183,7 +190,13 @@ public function run() $this->timers->tick(); - // Future-tick queue has pending callbacks ... + /** + * Future-tick queue has pending callbacks ... + * + * + * @link https://github.com/phpstan/phpstan/issues/10566 + * @phpstan-ignore-next-line + */ if (!$this->running || !$this->futureTickQueue->isEmpty()) { $timeout = 0; @@ -305,7 +318,7 @@ private function streamSelect(array &$read, array &$write, $timeout) } catch (\Throwable $e) { // @codeCoverageIgnoreStart \restore_error_handler(); throw $e; - } catch (\Exception $e) { + } catch (\Exception $e) { /** @phpstan-ignore-line */ \restore_error_handler(); throw $e; } // @codeCoverageIgnoreEnd diff --git a/tests/ExtEventLoopTest.php b/tests/ExtEventLoopTest.php index af4caa13..2d107642 100644 --- a/tests/ExtEventLoopTest.php +++ b/tests/ExtEventLoopTest.php @@ -62,15 +62,6 @@ public function createStream() return $stream; } - public function writeToStream($stream, $content) - { - if ('Linux' !== PHP_OS) { - return parent::writeToStream($stream, $content); - } - - fwrite($stream, $content); - } - /** * @group epoll-readable-error */ diff --git a/tests/StreamSelectLoopTest.php b/tests/StreamSelectLoopTest.php index 7e2435a8..d4cf072c 100644 --- a/tests/StreamSelectLoopTest.php +++ b/tests/StreamSelectLoopTest.php @@ -61,6 +61,7 @@ public function testStreamSelectReportsWarningForStreamWithFilter() $error = null; $previous = set_error_handler(function ($_, $errstr) use (&$error) { $error = $errstr; + return true; }); try { @@ -73,7 +74,9 @@ public function testStreamSelectReportsWarningForStreamWithFilter() $this->assertNotNull($error); - $now = set_error_handler(function () { }); + $now = set_error_handler(function () { + return true; + }); restore_error_handler(); $this->assertEquals($previous, $now); } @@ -114,7 +117,9 @@ public function testStreamSelectThrowsWhenCustomErrorHandlerThrowsForStreamWithF $this->assertInstanceOf('RuntimeException', $e); - $now = set_error_handler(function () { }); + $now = set_error_handler(function () { + return true; + }); restore_error_handler(); $this->assertEquals($previous, $now); } @@ -176,7 +181,6 @@ public function testSignalInterruptWithStream($signal) $loop = $this->loop; list($writeStream, $readStream) = $this->createSocketPair(); $loop->addReadStream($readStream, function ($stream) use ($loop) { - /** @var $loop LoopInterface */ $read = fgets($stream); if ($read === "end loop\n") { $loop->stop(); diff --git a/tests/bin/12-undefined.php b/tests/bin/12-undefined.php index c45cc0f4..339ea7f3 100644 --- a/tests/bin/12-undefined.php +++ b/tests/bin/12-undefined.php @@ -9,4 +9,9 @@ echo 'never'; }); +/** + * We're ignore this line because the test using this file relies on the error caused by it. + * + * @phpstan-ignore-next-line + */ $undefined->foo('bar'); diff --git a/tests/bin/22-stop-uncaught.php b/tests/bin/22-stop-uncaught.php index 5b6142ed..a2654f12 100644 --- a/tests/bin/22-stop-uncaught.php +++ b/tests/bin/22-stop-uncaught.php @@ -9,6 +9,11 @@ echo 'never'; }); +/** + * Ignoring the next line until we raise the minimum PHP version to 7.1 + * + * @phpstan-ignore-next-line + */ set_exception_handler(function (Exception $e) { echo 'Uncaught error occured' . PHP_EOL; Loop::stop();