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\Reflection\InitializerExprContext;
17: use PHPStan\Reflection\InitializerExprTypeResolver;
18: use PHPStan\Reflection\ParameterReflection;
19: use PHPStan\Reflection\PassedByReference;
20: use PHPStan\ShouldNotHappenException;
21: use ReflectionFunction;
22: use function array_map;
23: use function count;
24: use function str_replace;
25:
26: /** @api */
27: class ClosureTypeFactory
28: {
29:
30: public function __construct(
31: private InitializerExprTypeResolver $initializerExprTypeResolver,
32: private ReflectionSourceStubber $reflectionSourceStubber,
33: private Reflector $reflector,
34: private Parser $parser,
35: )
36: {
37: }
38:
39: /**
40: * @param Closure(): mixed $closure
41: */
42: public function fromClosureObject(Closure $closure): ClosureType
43: {
44: $stubData = $this->reflectionSourceStubber->generateFunctionStubFromReflection(new ReflectionFunction($closure));
45: if ($stubData === null) {
46: throw new ShouldNotHappenException('Closure reflection not found.');
47: }
48: $source = $stubData->getStub();
49: $source = str_replace('{closure}', 'foo', $source);
50: $locatedSource = new LocatedSource($source, '{closure}', $stubData->getFileName());
51: $find = new FindReflectionsInTree(new NodeToReflection());
52: $ast = $this->parser->parse($locatedSource->getSource());
53: if ($ast === null) {
54: throw new ShouldNotHappenException('Closure reflection not found.');
55: }
56:
57: /** @var list<\PHPStan\BetterReflection\Reflection\ReflectionFunction> $reflections */
58: $reflections = $find($this->reflector, $ast, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION), $locatedSource);
59: if (count($reflections) !== 1) {
60: throw new ShouldNotHappenException('Closure reflection not found.');
61: }
62:
63: $betterReflectionFunction = $reflections[0];
64:
65: $parameters = array_map(fn (BetterReflectionParameter $parameter) => new class($parameter, $this->initializerExprTypeResolver) implements ParameterReflection {
66:
67: public function __construct(private BetterReflectionParameter $reflection, private InitializerExprTypeResolver $initializerExprTypeResolver)
68: {
69: }
70:
71: public function getName(): string
72: {
73: return $this->reflection->getName();
74: }
75:
76: public function isOptional(): bool
77: {
78: return $this->reflection->isOptional();
79: }
80:
81: public function getType(): Type
82: {
83: return TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($this->reflection->getType()), null, null, $this->reflection->isVariadic());
84: }
85:
86: public function passedByReference(): PassedByReference
87: {
88: return $this->reflection->isPassedByReference()
89: ? PassedByReference::createCreatesNewVariable()
90: : PassedByReference::createNo();
91: }
92:
93: public function isVariadic(): bool
94: {
95: return $this->reflection->isVariadic();
96: }
97:
98: public function getDefaultValue(): ?Type
99: {
100: if (! $this->reflection->isDefaultValueAvailable()) {
101: return null;
102: }
103:
104: $defaultExpr = $this->reflection->getDefaultValueExpression();
105: if ($defaultExpr === null) {
106: return null;
107: }
108:
109: return $this->initializerExprTypeResolver->getType($defaultExpr, InitializerExprContext::fromReflectionParameter(new ReflectionParameter($this->reflection)));
110: }
111:
112: }, $betterReflectionFunction->getParameters());
113:
114: return new ClosureType($parameters, TypehintHelper::decideTypeFromReflection(ReflectionType::fromTypeOrNull($betterReflectionFunction->getReturnType())), $betterReflectionFunction->isVariadic());
115: }
116:
117: }
118: