From 83a133d8b5d3aed468ed8bea56023dcde6e6a6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 14 May 2024 12:20:00 +0200 Subject: [PATCH] Improve PHP 8.4+ support by avoiding implicitly nullable types --- composer.json | 2 +- src/functions.php | 20 ++++++++++++++------ tests/FunctionRejectTest.php | 6 ++++++ tests/FunctionResolveTest.php | 6 ++++++ tests/FunctionSleepTest.php | 6 ++++++ tests/FunctionTimeoutTest.php | 6 ++++++ tests/TestCase.php | 15 +++++++++++++++ 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 5a4d062..3ffda54 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "require": { "php": ">=5.3", "react/event-loop": "^1.2", - "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" + "react/promise": "^3.2 || ^2.7.0 || ^1.2.1" }, "require-dev": { "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" diff --git a/src/functions.php b/src/functions.php index 066b44c..7710e63 100644 --- a/src/functions.php +++ b/src/functions.php @@ -135,8 +135,12 @@ * @param ?LoopInterface $loop * @return PromiseInterface */ -function timeout(PromiseInterface $promise, $time, LoopInterface $loop = null) +function timeout(PromiseInterface $promise, $time, $loop = null) { + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #3 ($loop) expected null|React\EventLoop\LoopInterface'); + } + // cancelling this promise will only try to cancel the input promise, // thus leaving responsibility to the input promise. $canceller = null; @@ -222,8 +226,12 @@ function timeout(PromiseInterface $promise, $time, LoopInterface $loop = null) * @param ?LoopInterface $loop * @return PromiseInterface */ -function sleep($time, LoopInterface $loop = null) +function sleep($time, $loop = null) { + if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1 + throw new \InvalidArgumentException('Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + } + if ($loop === null) { $loop = Loop::get(); } @@ -280,7 +288,7 @@ function sleep($time, LoopInterface $loop = null) * @deprecated 1.8.0 See `sleep()` instead * @see sleep() */ -function resolve($time, LoopInterface $loop = null) +function resolve($time, $loop = null) { return sleep($time, $loop)->then(function() use ($time) { return $time; @@ -317,13 +325,13 @@ function resolve($time, LoopInterface $loop = null) * $timer->cancel(); * ``` * - * @param float $time - * @param LoopInterface $loop + * @param float $time + * @param ?LoopInterface $loop * @return PromiseInterface * @deprecated 1.8.0 See `sleep()` instead * @see sleep() */ -function reject($time, LoopInterface $loop = null) +function reject($time, $loop = null) { return sleep($time, $loop)->then(function () use ($time) { throw new TimeoutException($time, 'Timer expired after ' . $time . ' seconds'); diff --git a/tests/FunctionRejectTest.php b/tests/FunctionRejectTest.php index 676c0b1..92a66f5 100644 --- a/tests/FunctionRejectTest.php +++ b/tests/FunctionRejectTest.php @@ -48,6 +48,12 @@ public function testCancellingPromiseWillRejectTimer() $this->expectPromiseRejected($promise); } + public function testRejectWithInvalidLoopThrows() + { + $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + Timer\reject(1.0, 42); + } + public function testWaitingForPromiseToRejectDoesNotLeaveGarbageCycles() { if (class_exists('React\Promise\When')) { diff --git a/tests/FunctionResolveTest.php b/tests/FunctionResolveTest.php index b6e0187..554dd82 100644 --- a/tests/FunctionResolveTest.php +++ b/tests/FunctionResolveTest.php @@ -70,6 +70,12 @@ public function testCancellingPromiseWillRejectTimer() $this->expectPromiseRejected($promise); } + public function testResolveWithInvalidLoopThrows() + { + $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + Timer\resolve(1.0, 42); + } + public function testWaitingForPromiseToResolveDoesNotLeaveGarbageCycles() { if (class_exists('React\Promise\When')) { diff --git a/tests/FunctionSleepTest.php b/tests/FunctionSleepTest.php index d39c95a..36d10e3 100644 --- a/tests/FunctionSleepTest.php +++ b/tests/FunctionSleepTest.php @@ -70,6 +70,12 @@ public function testCancellingPromiseWillRejectTimer() $this->expectPromiseRejected($promise); } + public function testSleepWithInvalidLoopThrows() + { + $this->setExpectedException('InvalidArgumentException', 'Argument #2 ($loop) expected null|React\EventLoop\LoopInterface'); + Timer\sleep(1.0, 42); + } + public function testWaitingForPromiseToResolveDoesNotLeaveGarbageCycles() { if (class_exists('React\Promise\When')) { diff --git a/tests/FunctionTimeoutTest.php b/tests/FunctionTimeoutTest.php index af73a42..e58d82f 100644 --- a/tests/FunctionTimeoutTest.php +++ b/tests/FunctionTimeoutTest.php @@ -184,6 +184,12 @@ public function testCancelTimeoutWillResolveIfGivenPromiseWillResolve() $this->expectPromiseResolved($timeout); } + public function testTimeoutWithInvalidLoopThrows() + { + $this->setExpectedException('InvalidArgumentException', 'Argument #3 ($loop) expected null|React\EventLoop\LoopInterface'); + Timer\timeout(Promise\resolve(null), 1.0, 42); + } + public function testWaitingForPromiseToResolveBeforeTimeoutDoesNotLeaveGarbageCycles() { if (class_exists('React\Promise\When')) { diff --git a/tests/TestCase.php b/tests/TestCase.php index 6866124..9a8de07 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -42,6 +42,21 @@ protected function createCallableMock() } } + public function setExpectedException($exception, $exceptionMessage = '', $exceptionCode = null) + { + if (method_exists($this, 'expectException')) { + // PHPUnit 5+ + $this->expectException($exception); + $this->expectExceptionMessage($exceptionMessage); + if ($exceptionCode !== null) { + $this->expectExceptionCode($exceptionCode); + } + } else { + // legacy PHPUnit + parent::setExpectedException($exception, $exceptionMessage, $exceptionCode); + } + } + protected function expectPromiseRejected($promise) { return $promise->then($this->expectCallableNever(), $this->expectCallableOnce());