@@ -341,43 +341,67 @@ function _checkTypehint(callable $callback, $object)
341
341
return true ;
342
342
}
343
343
344
- if (\PHP_VERSION_ID < 70100 || \defined ('HHVM_VERSION ' )) {
345
- $ expectedException = $ parameters [0 ];
344
+ $ expectedException = $ parameters [0 ];
346
345
346
+ // PHP before v8 used an easy API:
347
+ if (\PHP_VERSION_ID < 70100 || \defined ('HHVM_VERSION ' )) {
347
348
if (!$ expectedException ->getClass ()) {
348
349
return true ;
349
350
}
350
351
351
352
return $ expectedException ->getClass ()->isInstance ($ object );
352
- } else {
353
- $ type = $ parameters [0 ]->getType ();
353
+ }
354
354
355
- if (!$ type ) {
356
- return true ;
357
- }
355
+ // Extract the type of the argument and handle different possibilities
356
+ $ type = $ expectedException ->getType ();
357
+
358
+ $ isTypeUnion = true ;
359
+ $ types = [];
360
+
361
+ switch (true ) {
362
+ case $ type === null :
363
+ break ;
364
+ case $ type instanceof \ReflectionNamedType:
365
+ $ types = [$ type ];
366
+ break ;
367
+ case $ type instanceof \ReflectionIntersectionType:
368
+ $ isTypeUnion = false ;
369
+ case $ type instanceof \ReflectionUnionType;
370
+ $ types = $ type ->getTypes ();
371
+ break ;
372
+ default :
373
+ throw new \LogicException ('Unexpected return value of ReflectionParameter::getType ' );
374
+ }
358
375
359
- $ types = [$ type ];
376
+ // If there is no type restriction, it matches
377
+ if (empty ($ types )) {
378
+ return true ;
379
+ }
360
380
361
- if ($ type instanceof \ReflectionUnionType) {
362
- $ types = $ type ->getTypes ();
381
+ foreach ($ types as $ type ) {
382
+ if (!$ type instanceof \ReflectionNamedType) {
383
+ throw new \LogicException ('This implementation does not support groups of intersection or union types ' );
363
384
}
364
385
365
- $ mismatched = false ;
366
-
367
- foreach ($ types as $ type ) {
368
- if (!$ type || $ type ->isBuiltin ()) {
369
- continue ;
370
- }
386
+ // A named-type can be either a class-name or a built-in type like string, int, array, etc.
387
+ $ matches = ($ type ->isBuiltin () && \gettype ($ object ) === $ type ->getName ())
388
+ || (new \ReflectionClass ($ type ->getName ()))->isInstance ($ object );
371
389
372
- $ expectedClass = $ type ->getName ();
373
390
374
- if ($ object instanceof $ expectedClass ) {
391
+ // If we look for a single match (union), we can return early on match
392
+ // If we look for a full match (intersection), we can return early on mismatch
393
+ if ($ matches ) {
394
+ if ($ isTypeUnion ) {
375
395
return true ;
376
396
}
377
-
378
- $ mismatched = true ;
397
+ } else {
398
+ if (!$ isTypeUnion ) {
399
+ return false ;
400
+ }
379
401
}
380
-
381
- return !$ mismatched ;
382
402
}
403
+
404
+ // If we look for a single match (union) and did not return early, we matched no type and are false
405
+ // If we look for a full match (intersection) and did not return early, we matched all types and are true
406
+ return $ isTypeUnion ? false : true ;
383
407
}
0 commit comments