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