1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Type;
4:
5: use Closure;
6: use PhpParser\Parser;
7: use PHPStan\BetterReflection\Identifier\IdentifierType;
8: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
9: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionType;
10: use PHPStan\BetterReflection\Reflection\ReflectionParameter as BetterReflectionParameter;
11: use PHPStan\BetterReflection\Reflector\Reflector;
12: use PHPStan\BetterReflection\SourceLocator\Ast\FindReflectionsInTree;
13: use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
14: use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
15: use PHPStan\BetterReflection\SourceLocator\SourceStubber\ReflectionSourceStubber;
16: use PHPStan\DependencyInjection\AutowiredParameter;
17: use PHPStan\DependencyInjection\AutowiredService;
18: use PHPStan\Reflection\InitializerExprContext;
19: use PHPStan\Reflection\InitializerExprTypeResolver;
20: use PHPStan\Reflection\ParameterReflection;
21: use PHPStan\Reflection\PassedByReference;
22: use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
23: use PHPStan\ShouldNotHappenException;
24: use ReflectionFunction;
25: use function array_map;
26: use function count;
27: use function method_exists;
28: use function str_replace;
29:
30: /**
31: * @api
32: */
33: #[AutowiredService]
34: final class ClosureTypeFactory
35: {
36:
37: public function __construct(
38: private InitializerExprTypeResolver $initializerExprTypeResolver,
39: private ReflectionSourceStubber $reflectionSourceStubber,
40: private Reflector $reflector,
41: private ReflectionProviderProvider $reflectionProviderProvider,
42: #[AutowiredParameter(ref: '@currentPhpVersionPhpParser')]
43: private Parser $parser,
44: )
45: {
46: }
47:
48: /**
49: * @param Closure(): mixed $closure
50: */
51: public function fromClosureObject(Closure $closure): ClosureType
52: {
53: $closureReflectionFunction = new ReflectionFunction($closure);
54: $stubData = $this->reflectionSourceStubber->generateFunctionStubFromReflection($closureReflectionFunction);
55: if ($stubData === null) {
56: throw new ShouldNotHappenException('Closure reflection not found.');
57: }
58: $source = $stubData->getStub();
59: $source = str_replace('{closure}', 'foo', $source);
60: $locatedSource = new LocatedSource($source, '{closure}', $stubData->getFileName());
61: $find = new FindReflectionsInTree(new NodeToReflection());
62: $ast = $this->parser->parse($locatedSource->getSource());
63: if ($ast === null) {
64: throw new ShouldNotHappenException('Closure reflection not found.');
65: }
66:
67: /** @var list<\PHPStan\BetterReflection\Reflection\ReflectionFunction> $reflections */
68: $reflections = $find($this->reflector, $ast, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION), $locatedSource);
69: if (count($reflections) !== 1) {
70: throw new ShouldNotHappenException('Closure reflection not found.');
71: }
72:
73: $betterReflectionFunction = $reflections[0];
74:
75: $parameters = array_map(fn (BetterReflectionParameter $parameter) => new class($parameter, $this->initializerExprTypeResolver) implements ParameterReflection {
76:
77: public function __construct(private BetterReflectionParameter $reflection, private InitializerExprTypeResolver $initializerExprTypeResolver)
78: {
79: }
80:
81: public function getName(): string
82: {
83: return $this->reflection->getName();
84: }
85:
86: public function isOptional(): bool
87: {
88: return $this->reflection->isOptional();
89: }
90:
91: public function getType(): Type
92: {
93: return TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($this->reflection->getType()), isVariadic: $this->reflection->isVariadic());
94: }
95:
96: public function passedByReference(): PassedByReference
97: {
98: return $this->reflection->isPassedByReference()
99: ? PassedByReference::createCreatesNewVariable()
100: : PassedByReference::createNo();
101: }
102:
103: public function isVariadic(): bool
104: {
105: return $this->reflection->isVariadic();
106: }
107:
108: public function getDefaultValue(): ?Type
109: {
110: if (! $this->reflection->isDefaultValueAvailable()) {
111: return null;
112: }
113:
114: $defaultExpr = $this->reflection->getDefaultValueExpression();
115: if ($defaultExpr === null) {
116: return null;
117: }
118:
119: return $this->initializerExprTypeResolver->getType($defaultExpr, InitializerExprContext::fromReflectionParameter(new ReflectionParameter($this->reflection)));
120: }
121:
122: }, $betterReflectionFunction->getParameters());
123:
124: $selfClass = null;
125: if (method_exists($closureReflectionFunction, 'getClosureCalledClass') && $closureReflectionFunction->getClosureCalledClass() !== null) {
126: $potentialSelfClassName = $closureReflectionFunction->getClosureCalledClass()->getName();
127: $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
128: if ($reflectionProvider->hasClass($potentialSelfClassName)) {
129: $selfClass = $reflectionProvider->getClass($potentialSelfClassName);
130: }
131: } elseif ($closureReflectionFunction->getClosureScopeClass() !== null) {
132: $potentialSelfClassName = $closureReflectionFunction->getClosureScopeClass()->getName();
133: $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
134: if ($reflectionProvider->hasClass($potentialSelfClassName)) {
135: $selfClass = $reflectionProvider->getClass($potentialSelfClassName);
136: }
137: }
138:
139: return new ClosureType($parameters, TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($betterReflectionFunction->getReturnType()), selfClass: $selfClass), $betterReflectionFunction->isVariadic());
140: }
141:
142: }
143: