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