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