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