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