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 fromReflectionParameter(ReflectionParameter $parameter): self
94: {
95: $declaringFunction = $parameter->getDeclaringFunction();
96: if ($declaringFunction instanceof ReflectionFunction) {
97: $file = $declaringFunction->getFileName();
98: return new self(
99: $file === false ? null : $file,
100: self::parseNamespace($declaringFunction->getName()),
101: null,
102: null,
103: $declaringFunction->getName(),
104: $declaringFunction->getName(),
105: null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that
106: );
107: }
108:
109: $file = $declaringFunction->getFileName();
110:
111: $betterReflection = $declaringFunction->getBetterReflection();
112:
113: return new self(
114: $file === false ? null : $file,
115: self::parseNamespace($betterReflection->getDeclaringClass()->getName()),
116: $declaringFunction->getDeclaringClass()->getName(),
117: $betterReflection->getDeclaringClass()->isTrait() ? $betterReflection->getDeclaringClass()->getName() : null,
118: $declaringFunction->getName(),
119: sprintf('%s::%s', $declaringFunction->getDeclaringClass()->getName(), $declaringFunction->getName()),
120: null, // Property hook parameter cannot have a default value. fromReflectionParameter is only used for that
121: );
122: }
123:
124: public static function fromStubParameter(
125: ?string $className,
126: string $stubFile,
127: ClassMethod|Function_|PropertyHook $function,
128: ): self
129: {
130: $namespace = null;
131: if ($className !== null) {
132: $namespace = self::parseNamespace($className);
133: } else {
134: if ($function instanceof Function_ && $function->namespacedName !== null) {
135: $namespace = self::parseNamespace($function->namespacedName->toString());
136: }
137: }
138:
139: $functionName = null;
140: $propertyName = null;
141: if ($function instanceof Function_ && $function->namespacedName !== null) {
142: $functionName = $function->namespacedName->toString();
143: } elseif ($function instanceof ClassMethod) {
144: $functionName = $function->name->toString();
145: } elseif ($function instanceof PropertyHook) {
146: $propertyName = $function->getAttribute('propertyName');
147: $functionName = sprintf('$%s::%s', $propertyName, $function->name->toString());
148: }
149:
150: $methodName = null;
151: if ($function instanceof ClassMethod && $className !== null) {
152: $methodName = sprintf('%s::%s', $className, $function->name->toString());
153: } elseif ($function instanceof PropertyHook) {
154: $propertyName = $function->getAttribute('propertyName');
155: $methodName = sprintf('%s::$%s::%s', $className, $propertyName, $function->name->toString());
156: } elseif ($function instanceof Function_ && $function->namespacedName !== null) {
157: $methodName = $function->namespacedName->toString();
158: }
159:
160: return new self(
161: $stubFile,
162: $namespace,
163: $className,
164: null,
165: $functionName,
166: $methodName,
167: $propertyName,
168: );
169: }
170:
171: public static function fromGlobalConstant(ReflectionConstant $constant): self
172: {
173: return new self(
174: $constant->getFileName(),
175: $constant->getNamespaceName(),
176: null,
177: null,
178: null,
179: null,
180: null,
181: );
182: }
183:
184: public static function createEmpty(): self
185: {
186: return new self(null, null, null, null, null, null, null);
187: }
188:
189: public function getFile(): ?string
190: {
191: return $this->file;
192: }
193:
194: public function getClassName(): ?string
195: {
196: return $this->className;
197: }
198:
199: public function getNamespace(): ?string
200: {
201: return $this->namespace;
202: }
203:
204: public function getTraitName(): ?string
205: {
206: return $this->traitName;
207: }
208:
209: public function getFunction(): ?string
210: {
211: return $this->function;
212: }
213:
214: public function getMethod(): ?string
215: {
216: return $this->method;
217: }
218:
219: public function getProperty(): ?string
220: {
221: return $this->property;
222: }
223:
224: }
225: