1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use Closure;
8: use PhpParser\Node;
9: use PHPStan\BetterReflection\BetterReflection;
10: use PHPStan\BetterReflection\Reflection\Adapter\Exception\NotImplemented;
11: use PHPStan\BetterReflection\Reflection\Exception\FunctionDoesNotExist;
12: use PHPStan\BetterReflection\Reflection\StringCast\ReflectionFunctionStringCast;
13: use PHPStan\BetterReflection\Reflector\DefaultReflector;
14: use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
15: use PHPStan\BetterReflection\Reflector\Reflector;
16: use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
17: use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
18: use PHPStan\BetterReflection\SourceLocator\Type\ClosureSourceLocator;
19:
20: use function assert;
21: use function function_exists;
22:
23: /** @psalm-immutable */
24: class ReflectionFunction implements Reflection
25: {
26: private Reflector $reflector;
27: private LocatedSource $locatedSource;
28: /**
29: * @var non-empty-string|null
30: */
31: private $namespace = null;
32: use ReflectionFunctionAbstract;
33:
34: public const CLOSURE_NAME = '{closure}';
35:
36: private bool $isStatic;
37:
38: /** @param non-empty-string|null $namespace
39: * @param \PhpParser\Node\Stmt\ClassMethod|\PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure|\PhpParser\Node\Expr\ArrowFunction $node */
40: private function __construct(Reflector $reflector, $node, LocatedSource $locatedSource, ?string $namespace = null)
41: {
42: $this->reflector = $reflector;
43: $this->locatedSource = $locatedSource;
44: $this->namespace = $namespace;
45: assert($node instanceof Node\Stmt\Function_ || $node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction);
46: $this->name = $node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction
47: ? self::CLOSURE_NAME
48: : $node->name->name;
49: $this->fillFromNode($node);
50: $isClosure = $node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction;
51: $this->isStatic = $isClosure && $node->static;
52: $this->isClosure = $isClosure;
53: $this->isGenerator = $this->nodeIsOrContainsYield($node);
54: }
55:
56: /** @throws IdentifierNotFound */
57: public static function createFromClosure(Closure $closure): self
58: {
59: $configuration = new BetterReflection();
60:
61: return (new DefaultReflector(new AggregateSourceLocator([
62: $configuration->sourceLocator(),
63: new ClosureSourceLocator($closure, $configuration->phpParser()),
64: ])))->reflectFunction(self::CLOSURE_NAME);
65: }
66:
67: /** @return non-empty-string */
68: public function __toString(): string
69: {
70: return ReflectionFunctionStringCast::toString($this);
71: }
72:
73: /**
74: * @internal
75: *
76: * @param non-empty-string|null $namespace
77: * @param \PhpParser\Node\Stmt\Function_|\PhpParser\Node\Expr\Closure|\PhpParser\Node\Expr\ArrowFunction $node
78: */
79: public static function createFromNode(Reflector $reflector, $node, LocatedSource $locatedSource, ?string $namespace = null): self
80: {
81: return new self($reflector, $node, $locatedSource, $namespace);
82: }
83:
84: /**
85: * Get the "short" name of the function (e.g. for A\B\foo, this will return
86: * "foo").
87: *
88: * @return non-empty-string
89: */
90: public function getShortName(): string
91: {
92: return $this->name;
93: }
94:
95: /**
96: * Check to see if this function has been disabled (by the PHP INI file
97: * directive `disable_functions`).
98: *
99: * Note - we cannot reflect on internal functions (as there is no PHP source
100: * code we can access. This means, at present, we can only EVER return false
101: * from this function, because you cannot disable user-defined functions.
102: *
103: * @see https://php.net/manual/en/ini.core.php#ini.disable-functions
104: *
105: * @todo https://github.com/Roave/BetterReflection/issues/14
106: */
107: public function isDisabled(): bool
108: {
109: return false;
110: }
111:
112: public function isStatic(): bool
113: {
114: return $this->isStatic;
115: }
116:
117: /**
118: * @throws NotImplemented
119: * @throws FunctionDoesNotExist
120: */
121: public function getClosure(): Closure
122: {
123: $this->assertIsNoClosure();
124:
125: $functionName = $this->getName();
126:
127: $this->assertFunctionExist($functionName);
128:
129: return static fn (...$args) => $functionName(...$args);
130: }
131:
132: /**
133: * @throws NotImplemented
134: * @throws FunctionDoesNotExist
135: * @param mixed ...$args
136: * @return mixed
137: */
138: public function invoke(...$args)
139: {
140: return $this->invokeArgs($args);
141: }
142:
143: /**
144: * @param array<mixed> $args
145: *
146: * @throws NotImplemented
147: * @throws FunctionDoesNotExist
148: * @return mixed
149: */
150: public function invokeArgs(array $args = [])
151: {
152: $this->assertIsNoClosure();
153:
154: $functionName = $this->getName();
155:
156: $this->assertFunctionExist($functionName);
157:
158: return $functionName(...$args);
159: }
160:
161: /** @throws NotImplemented */
162: private function assertIsNoClosure(): void
163: {
164: if ($this->isClosure()) {
165: throw new NotImplemented('Not implemented for closures');
166: }
167: }
168:
169: /** @throws FunctionDoesNotExist */
170: private function assertFunctionExist(string $functionName): void
171: {
172: if (! function_exists($functionName)) {
173: throw FunctionDoesNotExist::fromName($functionName);
174: }
175: }
176: }
177: