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