diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57422de5..a13804b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ jobs: strategy: matrix: php: + - 8.0 - 7.4 - 7.3 - 7.2 diff --git a/src/functions.php b/src/functions.php index bdbdf52d..68763db3 100644 --- a/src/functions.php +++ b/src/functions.php @@ -341,11 +341,43 @@ function _checkTypehint(callable $callback, $object) return true; } - $expectedException = $parameters[0]; + if (\PHP_VERSION_ID < 70100 || \defined('HHVM_VERSION')) { + $expectedException = $parameters[0]; - if (!$expectedException->getClass()) { - return true; - } + if (!$expectedException->getClass()) { + return true; + } + + return $expectedException->getClass()->isInstance($object); + } else { + $type = $parameters[0]->getType(); - return $expectedException->getClass()->isInstance($object); + if (!$type) { + return true; + } + + $types = [$type]; + + if ($type instanceof \ReflectionUnionType) { + $types = $type->getTypes(); + } + + $mismatched = false; + + foreach ($types as $type) { + if (!$type || $type->isBuiltin()) { + continue; + } + + $expectedClass = $type->getName(); + + if ($object instanceof $expectedClass) { + return true; + } + + $mismatched = true; + } + + return !$mismatched; + } } diff --git a/tests/FunctionCheckTypehintTest.php b/tests/FunctionCheckTypehintTest.php index 8449bc1f..5c78ab36 100644 --- a/tests/FunctionCheckTypehintTest.php +++ b/tests/FunctionCheckTypehintTest.php @@ -23,22 +23,65 @@ public function shouldAcceptFunctionStringCallbackWithTypehint() /** @test */ public function shouldAcceptInvokableObjectCallbackWithTypehint() { - $this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException())); - $this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception())); + $this->assertTrue(_checkTypehint(new CallbackWithTypehintClass(), new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint(new CallbackWithTypehintClass(), new \Exception())); } /** @test */ public function shouldAcceptObjectMethodCallbackWithTypehint() { - $this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException())); - $this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception())); + $this->assertTrue(_checkTypehint([new CallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint([new CallbackWithTypehintClass(), 'testCallback'], new \Exception())); } /** @test */ public function shouldAcceptStaticClassCallbackWithTypehint() { - $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); - $this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception())); + $this->assertTrue(_checkTypehint([new CallbackWithTypehintClass(), 'testCallbackStatic'], new \InvalidArgumentException())); + $this->assertfalse(_checkTypehint([new CallbackWithTypehintClass(), 'testCallbackStatic'], new \Exception())); + } + + /** + * @test + * @requires PHP 8 + */ + public function shouldAcceptClosureCallbackWithUnionTypehint() + { + eval( + 'namespace React\Promise;' . + 'self::assertTrue(_checkTypehint(function (\RuntimeException|\InvalidArgumentException $e) {}, new \InvalidArgumentException()));' . + 'self::assertFalse(_checkTypehint(function (\RuntimeException|\InvalidArgumentException $e) {}, new \Exception()));' + ); + } + + /** + * @test + * @requires PHP 8 + */ + public function shouldAcceptInvokableObjectCallbackWithUnionTypehint() + { + self::assertTrue(_checkTypehint(new CallbackWithUnionTypehintClass(), new \InvalidArgumentException())); + self::assertFalse(_checkTypehint(new CallbackWithUnionTypehintClass(), new \Exception())); + } + + /** + * @test + * @requires PHP 8 + */ + public function shouldAcceptObjectMethodCallbackWithUnionTypehint() + { + self::assertTrue(_checkTypehint([new CallbackWithUnionTypehintClass(), 'testCallback'], new \InvalidArgumentException())); + self::assertFalse(_checkTypehint([new CallbackWithUnionTypehintClass(), 'testCallback'], new \Exception())); + } + + /** + * @test + * @requires PHP 8 + */ + public function shouldAcceptStaticClassCallbackWithUnionTypehint() + { + self::assertTrue(_checkTypehint(['React\Promise\CallbackWithUnionTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); + self::assertFalse(_checkTypehint(['React\Promise\CallbackWithUnionTypehintClass', 'testCallbackStatic'], new \Exception())); } /** @test */ @@ -57,19 +100,19 @@ public function shouldAcceptFunctionStringCallbackWithoutTypehint() /** @test */ public function shouldAcceptInvokableObjectCallbackWithoutTypehint() { - $this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException())); + $this->assertTrue(_checkTypehint(new CallbackWithoutTypehintClass(), new \InvalidArgumentException())); } /** @test */ public function shouldAcceptObjectMethodCallbackWithoutTypehint() { - $this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException())); + $this->assertTrue(_checkTypehint([new CallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException())); } /** @test */ public function shouldAcceptStaticClassCallbackWithoutTypehint() { - $this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); + $this->assertTrue(_checkTypehint(['React\Promise\CallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException())); } } @@ -80,39 +123,3 @@ function testCallbackWithTypehint(\InvalidArgumentException $e) function testCallbackWithoutTypehint() { } - -class TestCallbackWithTypehintClass -{ - public function __invoke(\InvalidArgumentException $e) - { - - } - - public function testCallback(\InvalidArgumentException $e) - { - - } - - public static function testCallbackStatic(\InvalidArgumentException $e) - { - - } -} - -class TestCallbackWithoutTypehintClass -{ - public function __invoke() - { - - } - - public function testCallback() - { - - } - - public static function testCallbackStatic() - { - - } -} diff --git a/tests/fixtures/CallbackWithTypehintClass.php b/tests/fixtures/CallbackWithTypehintClass.php new file mode 100644 index 00000000..12dd0da4 --- /dev/null +++ b/tests/fixtures/CallbackWithTypehintClass.php @@ -0,0 +1,20 @@ +