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($betterReflectionType->getTypes(), static function (BetterReflectionType $type) : bool {
44: return ! ($type instanceof BetterReflectionNamedType && $type->getName() === 'null');
45: }));
46:
47: if (
48: $betterReflectionType->allowsNull()
49: && count($nonNullTypes) === 1
50: && $nonNullTypes[0] instanceof BetterReflectionNamedType
51: ) {
52: return new ReflectionNamedType($nonNullTypes[0], true);
53: }
54:
55: return new ReflectionUnionType($betterReflectionType);
56: }
57:
58: if ($betterReflectionType instanceof BetterReflectionIntersectionType) {
59: return new ReflectionIntersectionType($betterReflectionType);
60: }
61:
62: return new ReflectionNamedType($betterReflectionType, $betterReflectionType->allowsNull());
63: }
64: }
65: