1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace PHPStan\Reflection\Php; |
4: | |
5: | use PhpParser\Node; |
6: | use PhpParser\Node\Stmt\ClassMethod; |
7: | use PHPStan\Reflection\Assertions; |
8: | use PHPStan\Reflection\AttributeReflection; |
9: | use PHPStan\Reflection\ClassMemberReflection; |
10: | use PHPStan\Reflection\ClassReflection; |
11: | use PHPStan\Reflection\ExtendedMethodReflection; |
12: | use PHPStan\Reflection\MissingMethodFromReflectionException; |
13: | use PHPStan\ShouldNotHappenException; |
14: | use PHPStan\TrinaryLogic; |
15: | use PHPStan\Type\ArrayType; |
16: | use PHPStan\Type\BooleanType; |
17: | use PHPStan\Type\Generic\TemplateTypeMap; |
18: | use PHPStan\Type\IntegerType; |
19: | use PHPStan\Type\MixedType; |
20: | use PHPStan\Type\ObjectWithoutClassType; |
21: | use PHPStan\Type\StringType; |
22: | use PHPStan\Type\Type; |
23: | use PHPStan\Type\TypeCombinator; |
24: | use PHPStan\Type\VoidType; |
25: | use function in_array; |
26: | use function sprintf; |
27: | use function strtolower; |
28: | |
29: | |
30: | |
31: | |
32: | final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflection implements ExtendedMethodReflection |
33: | { |
34: | |
35: | |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | |
43: | |
44: | public function __construct( |
45: | private ClassReflection $declaringClass, |
46: | private ClassMethod|Node\PropertyHook $classMethod, |
47: | private ?string $hookForProperty, |
48: | string $fileName, |
49: | TemplateTypeMap $templateTypeMap, |
50: | array $realParameterTypes, |
51: | array $phpDocParameterTypes, |
52: | array $realParameterDefaultValues, |
53: | array $parameterAttributes, |
54: | Type $realReturnType, |
55: | ?Type $phpDocReturnType, |
56: | ?Type $throwType, |
57: | ?string $deprecatedDescription, |
58: | bool $isDeprecated, |
59: | bool $isInternal, |
60: | private bool $isFinal, |
61: | ?bool $isPure, |
62: | bool $acceptsNamedArguments, |
63: | Assertions $assertions, |
64: | private ?Type $selfOutType, |
65: | ?string $phpDocComment, |
66: | array $parameterOutTypes, |
67: | array $immediatelyInvokedCallableParameters, |
68: | array $phpDocClosureThisTypeParameters, |
69: | private bool $isConstructor, |
70: | array $attributes, |
71: | ) |
72: | { |
73: | if ($this->classMethod instanceof Node\PropertyHook) { |
74: | if ($this->hookForProperty === null) { |
75: | throw new ShouldNotHappenException('Hook was provided but property was not'); |
76: | } |
77: | } elseif ($this->hookForProperty !== null) { |
78: | throw new ShouldNotHappenException('Hooked property was provided but hook was not'); |
79: | } |
80: | |
81: | $name = strtolower($classMethod->name->name); |
82: | if ($this->isConstructor) { |
83: | $realReturnType = new VoidType(); |
84: | } |
85: | if (in_array($name, ['__destruct', '__unset', '__wakeup', '__clone'], true)) { |
86: | $realReturnType = new VoidType(); |
87: | } |
88: | if ($name === '__tostring') { |
89: | $realReturnType = new StringType(); |
90: | } |
91: | if ($name === '__isset') { |
92: | $realReturnType = new BooleanType(); |
93: | } |
94: | if ($name === '__sleep') { |
95: | $realReturnType = new ArrayType(new IntegerType(), new StringType()); |
96: | } |
97: | if ($name === '__set_state') { |
98: | $realReturnType = TypeCombinator::intersect(new ObjectWithoutClassType(), $realReturnType); |
99: | } |
100: | if ($name === '__set') { |
101: | $realReturnType = new VoidType(); |
102: | } |
103: | |
104: | if ($name === '__debuginfo') { |
105: | $realReturnType = TypeCombinator::intersect(TypeCombinator::addNull( |
106: | new ArrayType(new MixedType(true), new MixedType(true)), |
107: | ), $realReturnType); |
108: | } |
109: | |
110: | if ($name === '__unserialize') { |
111: | $realReturnType = new VoidType(); |
112: | } |
113: | if ($name === '__serialize') { |
114: | $realReturnType = new ArrayType(new MixedType(true), new MixedType(true)); |
115: | } |
116: | |
117: | parent::__construct( |
118: | $classMethod, |
119: | $fileName, |
120: | $templateTypeMap, |
121: | $realParameterTypes, |
122: | $phpDocParameterTypes, |
123: | $realParameterDefaultValues, |
124: | $parameterAttributes, |
125: | $realReturnType, |
126: | $phpDocReturnType, |
127: | $throwType, |
128: | $deprecatedDescription, |
129: | $isDeprecated, |
130: | $isInternal, |
131: | $isPure, |
132: | $acceptsNamedArguments, |
133: | $assertions, |
134: | $phpDocComment, |
135: | $parameterOutTypes, |
136: | $immediatelyInvokedCallableParameters, |
137: | $phpDocClosureThisTypeParameters, |
138: | $attributes, |
139: | ); |
140: | } |
141: | |
142: | public function getDeclaringClass(): ClassReflection |
143: | { |
144: | return $this->declaringClass; |
145: | } |
146: | |
147: | public function getPrototype(): ClassMemberReflection |
148: | { |
149: | try { |
150: | return $this->declaringClass->getNativeMethod($this->getClassMethod()->name->name)->getPrototype(); |
151: | } catch (MissingMethodFromReflectionException) { |
152: | return $this; |
153: | } |
154: | } |
155: | |
156: | private function getClassMethod(): ClassMethod|Node\PropertyHook |
157: | { |
158: | |
159: | $functionLike = $this->getFunctionLike(); |
160: | return $functionLike; |
161: | } |
162: | |
163: | public function getName(): string |
164: | { |
165: | $function = $this->getFunctionLike(); |
166: | if (!$function instanceof Node\PropertyHook) { |
167: | return parent::getName(); |
168: | } |
169: | |
170: | if ($this->hookForProperty === null) { |
171: | throw new ShouldNotHappenException('Hook was provided but property was not'); |
172: | } |
173: | |
174: | return sprintf('$%s::%s', $this->hookForProperty, $function->name->toString()); |
175: | } |
176: | |
177: | |
178: | |
179: | |
180: | |
181: | public function isPropertyHook(): bool |
182: | { |
183: | return $this->hookForProperty !== null; |
184: | } |
185: | |
186: | public function getHookedPropertyName(): ?string |
187: | { |
188: | return $this->hookForProperty; |
189: | } |
190: | |
191: | |
192: | |
193: | |
194: | public function getPropertyHookName(): ?string |
195: | { |
196: | $function = $this->getFunctionLike(); |
197: | if (!$function instanceof Node\PropertyHook) { |
198: | return null; |
199: | } |
200: | |
201: | $name = $function->name->toLowerString(); |
202: | if (!in_array($name, ['get', 'set'], true)) { |
203: | throw new ShouldNotHappenException(sprintf('Unknown property hook: %s', $name)); |
204: | } |
205: | |
206: | return $name; |
207: | } |
208: | |
209: | public function isStatic(): bool |
210: | { |
211: | $method = $this->getClassMethod(); |
212: | if ($method instanceof Node\PropertyHook) { |
213: | return false; |
214: | } |
215: | |
216: | return $method->isStatic(); |
217: | } |
218: | |
219: | public function isPrivate(): bool |
220: | { |
221: | $method = $this->getClassMethod(); |
222: | if ($method instanceof Node\PropertyHook) { |
223: | return false; |
224: | } |
225: | |
226: | return $method->isPrivate(); |
227: | } |
228: | |
229: | public function isPublic(): bool |
230: | { |
231: | $method = $this->getClassMethod(); |
232: | if ($method instanceof Node\PropertyHook) { |
233: | return true; |
234: | } |
235: | |
236: | return $method->isPublic(); |
237: | } |
238: | |
239: | public function isFinal(): TrinaryLogic |
240: | { |
241: | $method = $this->getClassMethod(); |
242: | if ($method instanceof Node\PropertyHook) { |
243: | return TrinaryLogic::createFromBoolean($method->isFinal()); |
244: | } |
245: | |
246: | return TrinaryLogic::createFromBoolean($method->isFinal() || $this->isFinal); |
247: | } |
248: | |
249: | public function isFinalByKeyword(): TrinaryLogic |
250: | { |
251: | return TrinaryLogic::createFromBoolean($this->getClassMethod()->isFinal()); |
252: | } |
253: | |
254: | public function isBuiltin(): bool |
255: | { |
256: | return false; |
257: | } |
258: | |
259: | public function getSelfOutType(): ?Type |
260: | { |
261: | return $this->selfOutType; |
262: | } |
263: | |
264: | public function returnsByReference(): TrinaryLogic |
265: | { |
266: | return TrinaryLogic::createFromBoolean($this->getClassMethod()->returnsByRef()); |
267: | } |
268: | |
269: | public function isAbstract(): TrinaryLogic |
270: | { |
271: | $method = $this->getClassMethod(); |
272: | if ($method instanceof Node\PropertyHook) { |
273: | return TrinaryLogic::createFromBoolean($method->body === null); |
274: | } |
275: | |
276: | return TrinaryLogic::createFromBoolean($method->isAbstract()); |
277: | } |
278: | |
279: | public function isConstructor(): bool |
280: | { |
281: | return $this->isConstructor; |
282: | } |
283: | |
284: | public function hasSideEffects(): TrinaryLogic |
285: | { |
286: | if ( |
287: | strtolower($this->getName()) !== '__construct' |
288: | && $this->getReturnType()->isVoid()->yes() |
289: | ) { |
290: | return TrinaryLogic::createYes(); |
291: | } |
292: | if ($this->isPure !== null) { |
293: | return TrinaryLogic::createFromBoolean(!$this->isPure); |
294: | } |
295: | |
296: | return TrinaryLogic::createMaybe(); |
297: | } |
298: | |
299: | } |
300: | |