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