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