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: * @api
30: */
31: final class PhpMethodFromParserNodeReflection extends PhpFunctionFromParserNodeReflection implements ExtendedMethodReflection
32: {
33:
34: /**
35: * @param Type[] $realParameterTypes
36: * @param Type[] $phpDocParameterTypes
37: * @param Type[] $realParameterDefaultValues
38: * @param array<string, bool> $immediatelyInvokedCallableParameters
39: * @param array<string, Type> $phpDocClosureThisTypeParameters
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: /** @var Node\Stmt\ClassMethod|Node\PropertyHook $functionLike */
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: * @phpstan-assert-if-true !null $this->getHookedPropertyName()
172: * @phpstan-assert-if-true !null $this->getPropertyHookName()
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: * @return 'get'|'set'|null
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: