Codebase list php-react-promise / 5189eb6
Extend _checkTypehint support for PHP8.1's intersection types Benjamin Zikarsky 2 years ago
4 changed file(s) with 93 addition(s) and 18 deletion(s). Raw diff Collapse all Expand all
343343
344344 $expectedException = $parameters[0];
345345
346 // PHP before v8 used an easy API:
347 if (\PHP_VERSION_ID < 70100 || \defined('HHVM_VERSION')) {
348 if (!$expectedException->getClass()) {
349 return true;
350 }
351
352 return $expectedException->getClass()->isInstance($reason);
353 }
354
355346 // Extract the type of the argument and handle different possibilities
356347 $type = $expectedException->getType();
348
349 $isTypeUnion = true;
357350 $types = [];
358351
359352 switch (true) {
362355 case $type instanceof \ReflectionNamedType:
363356 $types = [$type];
364357 break;
358 case $type instanceof \ReflectionIntersectionType:
359 $isTypeUnion = false;
365360 case $type instanceof \ReflectionUnionType;
366361 $types = $type->getTypes();
367362 break;
374369 return true;
375370 }
376371
377 // Search for one matching named-type for success, otherwise return false
378 // A named-type can be either a class-name or a built-in type like string, int, array, etc.
379372 foreach ($types as $type) {
373 if (!$type instanceof \ReflectionNamedType) {
374 throw new \LogicException('This implementation does not support groups of intersection or union types');
375 }
376
377 // A named-type can be either a class-name or a built-in type like string, int, array, etc.
380378 $matches = ($type->isBuiltin() && \gettype($reason) === $type->getName())
381379 || (new \ReflectionClass($type->getName()))->isInstance($reason);
382380
381
382 // If we look for a single match (union), we can return early on match
383 // If we look for a full match (intersection), we can return early on mismatch
383384 if ($matches) {
384 return true;
385 }
386 }
387
388 return false;
389 }
385 if ($isTypeUnion) {
386 return true;
387 }
388 } else {
389 if (!$isTypeUnion) {
390 return false;
391 }
392 }
393 }
394
395 // If we look for a single match (union) and did not return early, we matched no type and are false
396 // If we look for a full match (intersection) and did not return early, we matched all types and are true
397 return $isTypeUnion ? false : true;
398 }
8484 self::assertFalse(_checkTypehint([CallbackWithUnionTypehintClass::class, 'testCallbackStatic'], new Exception()));
8585 }
8686
87 /** @test */
87 /**
88 * @test
89 * @requires PHP 8.1
90 */
91 public function shouldAcceptInvokableObjectCallbackWithIntersectionTypehint()
92 {
93 self::assertFalse(_checkTypehint(new CallbackWithIntersectionTypehintClass(), new \RuntimeException()));
94 self::assertTrue(_checkTypehint(new CallbackWithIntersectionTypehintClass(), new CountableException()));
95 }
96
97 /**
98 * @test
99 * @requires PHP 8.1
100 */
101 public function shouldAcceptObjectMethodCallbackWithIntersectionTypehint()
102 {
103 self::assertFalse(_checkTypehint([new CallbackWithIntersectionTypehintClass(), 'testCallback'], new \RuntimeException()));
104 self::assertTrue(_checkTypehint([new CallbackWithIntersectionTypehintClass(), 'testCallback'], new CountableException()));
105 }
106
107 /**
108 * @test
109 * @requires PHP 8.1
110 */
111 public function shouldAcceptStaticClassCallbackWithIntersectionTypehint()
112 {
113 self::assertFalse(_checkTypehint([CallbackWithIntersectionTypehintClass::class, 'testCallbackStatic'], new \RuntimeException()));
114 self::assertTrue(_checkTypehint([CallbackWithIntersectionTypehintClass::class, 'testCallbackStatic'], new CountableException()));
115 }
116
117 /** @test */
88118 public function shouldAcceptClosureCallbackWithoutTypehint()
89119 {
90120 self::assertTrue(_checkTypehint(function (InvalidArgumentException $e) {
0 <?php
1
2 namespace React\Promise;
3
4 use Countable;
5 use RuntimeException;
6
7 class CallbackWithIntersectionTypehintClass
8 {
9 public function __invoke(RuntimeException&Countable $e)
10 {
11 }
12
13 public function testCallback(RuntimeException&Countable $e)
14 {
15 }
16
17 public static function testCallbackStatic(RuntimeException&Countable $e)
18 {
19 }
20 }
0 <?php
1
2 namespace React\Promise;
3
4 use Countable;
5 use RuntimeException;
6
7 class CountableException extends RuntimeException implements Countable
8 {
9 public function count(): int
10 {
11 return 0;
12 }
13 }
14