1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection\Adapter;
6:
7: use ReflectionType as CoreReflectionType;
8: use PHPStan\BetterReflection\Reflection\ReflectionIntersectionType as BetterReflectionIntersectionType;
9: use PHPStan\BetterReflection\Reflection\ReflectionNamedType as BetterReflectionNamedType;
10: use PHPStan\BetterReflection\Reflection\ReflectionType as BetterReflectionType;
11: use PHPStan\BetterReflection\Reflection\ReflectionUnionType as BetterReflectionUnionType;
12:
13: use function array_filter;
14: use function array_values;
15: use function count;
16:
17: /** @psalm-immutable */
18: abstract class ReflectionType extends CoreReflectionType
19: {
20: /** @psalm-pure
21: * @param BetterReflectionUnionType|BetterReflectionNamedType|BetterReflectionIntersectionType|null $betterReflectionType
22: * @return \PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType|null */
23: public static function fromTypeOrNull($betterReflectionType)
24: {
25: return $betterReflectionType !== null ? self::fromType($betterReflectionType) : null;
26: }
27:
28: /**
29: * @internal
30: *
31: * @psalm-pure
32: * @param BetterReflectionNamedType|BetterReflectionUnionType|BetterReflectionIntersectionType $betterReflectionType
33: * @return \PHPStan\BetterReflection\Reflection\Adapter\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\Adapter\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\Adapter\ReflectionIntersectionType
34: */
35: public static function fromType($betterReflectionType)
36: {
37: if ($betterReflectionType instanceof BetterReflectionUnionType) {
38: // php-src has this weird behavior where a union type composed of a single type `T`
39: // together with `null` means that a `ReflectionNamedType` for `?T` is produced,
40: // rather than `T|null`. This is done to keep BC compatibility with PHP 7.1 (which
41: // introduced nullable types), but at reflection level, this is mostly a nuisance.
42: // In order to keep parity with core, we stashed this weird behavior in here.
43: $nonNullTypes = array_values(array_filter(
44: $betterReflectionType->getTypes(),
45: static fn (BetterReflectionType $type): bool => ! ($type instanceof BetterReflectionNamedType && $type->getName() === 'null'),
46: ));
47:
48: if (
49: $betterReflectionType->allowsNull()
50: && count($nonNullTypes) === 1
51: && $nonNullTypes[0] instanceof BetterReflectionNamedType
52: ) {
53: return new ReflectionNamedType($nonNullTypes[0], true);
54: }
55:
56: return new ReflectionUnionType($betterReflectionType);
57: }
58:
59: if ($betterReflectionType instanceof BetterReflectionIntersectionType) {
60: return new ReflectionIntersectionType($betterReflectionType);
61: }
62:
63: return new ReflectionNamedType($betterReflectionType, $betterReflectionType->allowsNull());
64: }
65: }
66: