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