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