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