1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Reflection;
4:
5: use PhpParser\Node\PropertyHook;
6: use PhpParser\Node\Stmt\ClassMethod;
7: use PhpParser\Node\Stmt\Function_;
8: use PHPStan\Analyser\Scope;
9: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction;
10: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
11: use PHPStan\BetterReflection\Reflection\ReflectionConstant;
12: use PHPStan\Reflection\Php\PhpMethodFromParserNodeReflection;
13: use PHPStan\ShouldNotHappenException;
14: use PHPStan\Turbo\ShadowedByTurboExtension;
15: use function array_slice;
16: use function count;
17: use function explode;
18: use function implode;
19: use function sprintf;
20:
21: /**
22: * @api
23: */
24: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/InitializerExprContext.cpp')]
25: final class InitializerExprContext implements NamespaceAnswerer
26: {
27:
28: /**
29: * @param non-empty-string|null $namespace
30: */
31: private function __construct(
32: private ?string $file,
33: private ?string $namespace,
34: private ?string $className,
35: private ?string $traitName,
36: private ?string $function,
37: private ?string $method,
38: private ?string $property,
39: )
40: {
41: }
42:
43: public static function fromScope(Scope $scope): self
44: {
45: $function = $scope->getFunction();
46:
47: // __FILE__ and __DIR__ are resolved at compile time, so in a trait context they
48: // point at the file the trait is declared in, while Scope::getFile() returns the
49: // file of the class that uses the trait.
50: $file = $scope->getFile();
51: if ($scope->isInTrait()) {
52: $traitFileName = $scope->getTraitReflection()->getFileName();
53: if ($traitFileName !== null) {
54: $file = $traitFileName;
55: }
56: }
57:
58: return new self(
59: $file,
60: $scope->getNamespace(),
61: $scope->isInClass() ? $scope->getClassReflection()->getName() : null,
62: $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null,
63: $scope->isInAnonymousFunction() ? '{closure}' : ($function !== null ? $function->getName() : null),
64: $scope->isInAnonymousFunction() ? '{closure}' : ($function instanceof MethodReflection
65: ? sprintf('%s::%s', $function->getDeclaringClass()->getName(), $function->getName())
66: : ($function instanceof FunctionReflection ? $function->getName() : null)),
67: $function instanceof PhpMethodFromParserNodeReflection && $function->isPropertyHook() ? $function->getHookedPropertyName() : null,
68: );
69: }
70:
71: /**
72: * @return non-empty-string|null
73: */
74: private static function parseNamespace(string $name): ?string
75: {
76: $parts = explode('\\', $name);
77: if (count($parts) > 1) {
78: $ns = implode('\\', array_slice($parts, 0, -1));
79: if ($ns === '') {
80: throw new ShouldNotHappenException('Namespace cannot be empty.');
81: }
82: return $ns;
83: }
84:
85: return null;
86: }
87:
88: public static function fromClassReflection(ClassReflection $classReflection): self
89: {
90: return self::fromClass($classReflection->getName(), $classReflection->getFileName());
91: }
92:
93: public static function fromClass(string $className, ?string $fileName): self
94: {
95: return new self(
96: $fileName,
97: self::parseNamespace($className),
98: $className,
99: null,
100: null,
101: null,
102: null,
103: );
104: }
105:
106: public static function fromFunction(string $functionName, ?string $fileName): self
107: {
108: return new self(
109: $fileName,
110: self::parseNamespace($functionName),
111: null,
112: null,
113: $functionName,
114: $functionName,
115: null,
116: );
117: }
118:
119: public static function fromClassMethod(string $className, ?string $traitName, string $methodName, ?string $fileName): self
120: {
121: return new self(
122: $fileName,
123: self::parseNamespace($className),
124: $className,
125: $traitName,
126: $methodName,
127: sprintf('%s::%s', $className, $methodName),
128: null,
129: );
130: }
131:
132: public static function fromReflectionParameter(ReflectionParameter $parameter): self
133: {
134: $declaringFunction = $parameter->getDeclaringFunction();
135: if ($declaringFunction instanceof ReflectionFunction) {
136: $file = $declaringFunction->getFileName();
137: return new self(
138: $file === false ? null : $file,
139: self::parseNamespace($declaringFunction->getName()),
140: null,
141: null,
142: $declaringFunction->getName(),
143: $declaringFunction->getName(),
144: null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that
145: );
146: }
147:
148: $file = $declaringFunction->getFileName();
149:
150: $betterReflection = $declaringFunction->getBetterReflection();
151:
152: return new self(
153: $file === false ? null : $file,
154: self::parseNamespace($betterReflection->getDeclaringClass()->getName()),
155: $declaringFunction->getDeclaringClass()->getName(),
156: $betterReflection->getDeclaringClass()->isTrait() ? $betterReflection->getDeclaringClass()->getName() : null,
157: $declaringFunction->getName(),
158: sprintf('%s::%s', $declaringFunction->getDeclaringClass()->getName(), $declaringFunction->getName()),
159: null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that
160: );
161: }
162:
163: public static function fromStubParameter(
164: ?string $className,
165: string $stubFile,
166: ClassMethod|Function_|PropertyHook $function,
167: ): self
168: {
169: $namespace = null;
170: if ($className !== null) {
171: $namespace = self::parseNamespace($className);
172: } else {
173: if ($function instanceof Function_ && $function->namespacedName !== null) {
174: $namespace = self::parseNamespace($function->namespacedName->toString());
175: }
176: }
177:
178: $functionName = null;
179: $propertyName = null;
180: if ($function instanceof Function_ && $function->namespacedName !== null) {
181: $functionName = $function->namespacedName->toString();
182: } elseif ($function instanceof ClassMethod) {
183: $functionName = $function->name->toString();
184: } elseif ($function instanceof PropertyHook) {
185: $propertyName = $function->getAttribute('propertyName');
186: $functionName = sprintf('$%s::%s', $propertyName, $function->name->toString());
187: }
188:
189: $methodName = null;
190: if ($function instanceof ClassMethod && $className !== null) {
191: $methodName = sprintf('%s::%s', $className, $function->name->toString());
192: } elseif ($function instanceof PropertyHook) {
193: $propertyName = $function->getAttribute('propertyName');
194: $methodName = sprintf('%s::$%s::%s', $className, $propertyName, $function->name->toString());
195: } elseif ($function instanceof Function_ && $function->namespacedName !== null) {
196: $methodName = $function->namespacedName->toString();
197: }
198:
199: return new self(
200: $stubFile,
201: $namespace,
202: $className,
203: null,
204: $functionName,
205: $methodName,
206: $propertyName,
207: );
208: }
209:
210: public static function fromGlobalConstant(ReflectionConstant $constant): self
211: {
212: return new self(
213: $constant->getFileName(),
214: $constant->getNamespaceName(),
215: null,
216: null,
217: null,
218: null,
219: null,
220: );
221: }
222:
223: public static function createEmpty(): self
224: {
225: return new self(
226: file: null,
227: namespace: null,
228: className: null,
229: traitName: null,
230: function: null,
231: method: null,
232: property: null,
233: );
234: }
235:
236: public function getFile(): ?string
237: {
238: return $this->file;
239: }
240:
241: public function getClassName(): ?string
242: {
243: return $this->className;
244: }
245:
246: public function getNamespace(): ?string
247: {
248: return $this->namespace;
249: }
250:
251: public function getTraitName(): ?string
252: {
253: return $this->traitName;
254: }
255:
256: public function getFunction(): ?string
257: {
258: return $this->function;
259: }
260:
261: public function getMethod(): ?string
262: {
263: return $this->method;
264: }
265:
266: public function getProperty(): ?string
267: {
268: return $this->property;
269: }
270:
271: }
272: