1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace PHPStan\BetterReflection\Reflection;
6:
7: use Closure;
8: use Error;
9: use OutOfBoundsException;
10: use PhpParser\Node;
11: use PhpParser\Node\Stmt\Property as PropertyNode;
12: use PhpParser\NodeTraverser;
13: use PhpParser\NodeVisitor\FindingVisitor;
14: use ReflectionException;
15: use ReflectionProperty as CoreReflectionProperty;
16: use PHPStan\BetterReflection\NodeCompiler\CompiledValue;
17: use PHPStan\BetterReflection\NodeCompiler\CompileNodeToValue;
18: use PHPStan\BetterReflection\NodeCompiler\CompilerContext;
19: use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty as ReflectionPropertyAdapter;
20: use PHPStan\BetterReflection\Reflection\Attribute\ReflectionAttributeHelper;
21: use PHPStan\BetterReflection\Reflection\Deprecated\DeprecatedHelper;
22: use PHPStan\BetterReflection\Reflection\Exception\ClassDoesNotExist;
23: use PHPStan\BetterReflection\Reflection\Exception\CodeLocationMissing;
24: use PHPStan\BetterReflection\Reflection\Exception\NoObjectProvided;
25: use PHPStan\BetterReflection\Reflection\Exception\NotAnObject;
26: use PHPStan\BetterReflection\Reflection\Exception\ObjectNotInstanceOfClass;
27: use PHPStan\BetterReflection\Reflection\StringCast\ReflectionPropertyStringCast;
28: use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
29: use PHPStan\BetterReflection\Reflector\Reflector;
30: use PHPStan\BetterReflection\Util\CalculateReflectionColumn;
31: use PHPStan\BetterReflection\Util\ClassExistenceChecker;
32: use PHPStan\BetterReflection\Util\Exception\NoNodePosition;
33: use PHPStan\BetterReflection\Util\GetLastDocComment;
34:
35: use function array_map;
36: use function assert;
37: use function func_num_args;
38: use function is_object;
39: use function sprintf;
40: use function str_contains;
41:
42: /** @psalm-immutable */
43: class ReflectionProperty
44: {
45: private Reflector $reflector;
46: private ReflectionClass $declaringClass;
47: private ReflectionClass $implementingClass;
48: private bool $isPromoted;
49: private bool $declaredAtCompileTime;
50: /** @var non-empty-string */
51: private string $name;
52:
53: /** @var int-mask-of<ReflectionPropertyAdapter::IS_*> */
54: private int $modifiers;
55:
56: /**
57: * @var \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
58: */
59: private $type;
60:
61: /**
62: * @var \PhpParser\Node\Expr|null
63: */
64: private $default;
65:
66: /** @var non-empty-string|null */
67: private $docComment;
68:
69: /** @var list<ReflectionAttribute> */
70: private array $attributes;
71:
72: /** @var positive-int|null */
73: private $startLine;
74:
75: /** @var positive-int|null */
76: private $endLine;
77:
78: /** @var positive-int|null */
79: private $startColumn;
80:
81: /** @var positive-int|null */
82: private $endColumn;
83:
84: private bool $immediateVirtual;
85:
86: /** @var array{get?: ReflectionMethod, set?: ReflectionMethod} */
87: private array $immediateHooks;
88:
89: /**
90: * @var array{get?: ReflectionMethod, set?: ReflectionMethod}|null
91: * @psalm-allow-private-mutation
92: */
93: private $cachedHooks = null;
94:
95: /** @psalm-allow-private-mutation
96: * @var bool|null */
97: private $cachedVirtual = null;
98:
99: /** @psalm-allow-private-mutation
100: * @var \PHPStan\BetterReflection\NodeCompiler\CompiledValue|null */
101: private $compiledDefaultValue = null;
102:
103: private function __construct(Reflector $reflector, PropertyNode $node, Node\PropertyItem $propertyNode, ReflectionClass $declaringClass, ReflectionClass $implementingClass, bool $isPromoted, bool $declaredAtCompileTime)
104: {
105: $this->reflector = $reflector;
106: $this->declaringClass = $declaringClass;
107: $this->implementingClass = $implementingClass;
108: $this->isPromoted = $isPromoted;
109: $this->declaredAtCompileTime = $declaredAtCompileTime;
110: $this->name = $propertyNode->name->name;
111: $this->modifiers = $this->computeModifiers($node);
112: $this->type = $this->createType($node);
113: $this->default = $propertyNode->default;
114: $this->docComment = GetLastDocComment::forNode($node);
115: $this->attributes = ReflectionAttributeHelper::createAttributes($reflector, $this, $node->attrGroups);
116: $this->immediateVirtual = $this->computeImmediateVirtual($node);
117: $this->immediateHooks = $this->createImmediateHooks($node);
118: $startLine = $node->getStartLine();
119: if ($startLine === -1) {
120: $startLine = null;
121: }
122: $endLine = $node->getEndLine();
123: if ($endLine === -1) {
124: $endLine = null;
125: }
126: /** @psalm-suppress InvalidPropertyAssignmentValue */
127: $this->startLine = $startLine;
128: /** @psalm-suppress InvalidPropertyAssignmentValue */
129: $this->endLine = $endLine;
130: try {
131: $this->startColumn = CalculateReflectionColumn::getStartColumn($declaringClass->getLocatedSource()->getSource(), $node);
132: } catch (NoNodePosition $exception) {
133: $this->startColumn = null;
134: }
135: try {
136: $this->endColumn = CalculateReflectionColumn::getEndColumn($declaringClass->getLocatedSource()->getSource(), $node);
137: } catch (NoNodePosition $exception) {
138: $this->endColumn = null;
139: }
140: }
141:
142: /**
143: * Create a reflection of an instance's property by its name
144: *
145: * @param non-empty-string $propertyName
146: *
147: * @throws ReflectionException
148: * @throws IdentifierNotFound
149: * @throws OutOfBoundsException
150: */
151: public static function createFromInstance(object $instance, string $propertyName): self
152: {
153: $property = ReflectionClass::createFromInstance($instance)->getProperty($propertyName);
154:
155: if ($property === null) {
156: throw new OutOfBoundsException(sprintf('Could not find property: %s', $propertyName));
157: }
158:
159: return $property;
160: }
161:
162: /** @internal */
163: public function withImplementingClass(ReflectionClass $implementingClass): self
164: {
165: $clone = clone $this;
166: $clone->implementingClass = $implementingClass;
167:
168: if ($clone->type !== null) {
169: $clone->type = $clone->type->withOwner($clone);
170: }
171:
172: $clone->attributes = array_map(static fn (ReflectionAttribute $attribute): ReflectionAttribute => $attribute->withOwner($clone), $this->attributes);
173:
174: $this->compiledDefaultValue = null;
175:
176: return $clone;
177: }
178:
179: /** @return non-empty-string */
180: public function __toString(): string
181: {
182: return ReflectionPropertyStringCast::toString($this);
183: }
184:
185: /**
186: * @internal
187: *
188: * @param PropertyNode $node Node has to be processed by the PhpParser\NodeVisitor\NameResolver
189: */
190: public static function createFromNode(Reflector $reflector, PropertyNode $node, Node\PropertyItem $propertyProperty, ReflectionClass $declaringClass, ReflectionClass $implementingClass, bool $isPromoted = false, bool $declaredAtCompileTime = true): self
191: {
192: return new self(
193: $reflector,
194: $node,
195: $propertyProperty,
196: $declaringClass,
197: $implementingClass,
198: $isPromoted,
199: $declaredAtCompileTime,
200: );
201: }
202:
203: /**
204: * Has the property been declared at compile-time?
205: *
206: * Note that unless the property is static, this is hard coded to return
207: * true, because we are unable to reflect instances of classes, therefore
208: * we can be sure that all properties are always declared at compile-time.
209: */
210: public function isDefault(): bool
211: {
212: return $this->declaredAtCompileTime;
213: }
214:
215: public function isDynamic(): bool
216: {
217: return ! $this->isDefault();
218: }
219:
220: /**
221: * Get the core-reflection-compatible modifier values.
222: *
223: * @return int-mask-of<ReflectionPropertyAdapter::IS_*>
224: */
225: public function getModifiers(): int
226: {
227: /** @var int-mask-of<ReflectionPropertyAdapter::IS_*> $modifiers */
228: $modifiers = $this->modifiers
229: + ($this->isVirtual() ? ReflectionPropertyAdapter::IS_VIRTUAL_COMPATIBILITY : 0);
230:
231: return $modifiers;
232: }
233:
234: /**
235: * Get the name of the property.
236: *
237: * @return non-empty-string
238: */
239: public function getName(): string
240: {
241: return $this->name;
242: }
243:
244: /**
245: * Is the property private?
246: */
247: public function isPrivate(): bool
248: {
249: return (bool) ($this->modifiers & CoreReflectionProperty::IS_PRIVATE);
250: }
251:
252: public function isPrivateSet(): bool
253: {
254: return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY);
255: }
256:
257: /**
258: * Is the property protected?
259: */
260: public function isProtected(): bool
261: {
262: return (bool) ($this->modifiers & CoreReflectionProperty::IS_PROTECTED);
263: }
264:
265: public function isProtectedSet(): bool
266: {
267: return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY);
268: }
269:
270: /**
271: * Is the property public?
272: */
273: public function isPublic(): bool
274: {
275: return (bool) ($this->modifiers & CoreReflectionProperty::IS_PUBLIC);
276: }
277:
278: /**
279: * Is the property static?
280: */
281: public function isStatic(): bool
282: {
283: return (bool) ($this->modifiers & CoreReflectionProperty::IS_STATIC);
284: }
285:
286: public function isFinal(): bool
287: {
288: return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY);
289: }
290:
291: public function isAbstract(): bool
292: {
293: return (bool) ($this->modifiers & ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY)
294: || $this->declaringClass->isInterface();
295: }
296:
297: public function isPromoted(): bool
298: {
299: return $this->isPromoted;
300: }
301:
302: public function isInitialized(?object $object = null): bool
303: {
304: if ($object === null && $this->isStatic()) {
305: return ! $this->hasType() || $this->hasDefaultValue();
306: }
307:
308: try {
309: $this->getValue($object);
310:
311: return true;
312:
313: /** @phpstan-ignore catch.neverThrown */
314: } catch (Error $e) {
315: if (strpos($e->getMessage(), 'must not be accessed before initialization') !== false) {
316: return false;
317: }
318:
319: throw $e;
320: }
321: }
322:
323: public function isReadOnly(): bool
324: {
325: return ($this->modifiers & ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY)
326: || $this->getDeclaringClass()->isReadOnly();
327: }
328:
329: public function getDeclaringClass(): ReflectionClass
330: {
331: return $this->declaringClass;
332: }
333:
334: public function getImplementingClass(): ReflectionClass
335: {
336: return $this->implementingClass;
337: }
338:
339: /** @return non-empty-string|null */
340: public function getDocComment(): ?string
341: {
342: return $this->docComment;
343: }
344:
345: public function hasDefaultValue(): bool
346: {
347: return ! $this->hasType() || $this->default !== null;
348: }
349:
350: public function getDefaultValueExpression(): ?\PhpParser\Node\Expr
351: {
352: return $this->default;
353: }
354:
355: /**
356: * Get the default value of the property (as defined before constructor is
357: * called, when the property is defined)
358: * @return mixed
359: */
360: public function getDefaultValue()
361: {
362: if ($this->default === null) {
363: return null;
364: }
365:
366: if ($this->compiledDefaultValue === null) {
367: $this->compiledDefaultValue = (new CompileNodeToValue())->__invoke(
368: $this->default,
369: new CompilerContext(
370: $this->reflector,
371: $this,
372: ),
373: );
374: }
375:
376: /** @psalm-var scalar|array<scalar>|null $value */
377: $value = $this->compiledDefaultValue->value;
378:
379: return $value;
380: }
381:
382: public function isDeprecated(): bool
383: {
384: return DeprecatedHelper::isDeprecated($this);
385: }
386:
387: /**
388: * Get the line number that this property starts on.
389: *
390: * @return positive-int
391: *
392: * @throws CodeLocationMissing
393: */
394: public function getStartLine(): int
395: {
396: if ($this->startLine === null) {
397: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
398: }
399:
400: return $this->startLine;
401: }
402:
403: /**
404: * Get the line number that this property ends on.
405: *
406: * @return positive-int
407: *
408: * @throws CodeLocationMissing
409: */
410: public function getEndLine(): int
411: {
412: if ($this->endLine === null) {
413: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
414: }
415:
416: return $this->endLine;
417: }
418:
419: /**
420: * @return positive-int
421: *
422: * @throws CodeLocationMissing
423: */
424: public function getStartColumn(): int
425: {
426: if ($this->startColumn === null) {
427: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
428: }
429:
430: return $this->startColumn;
431: }
432:
433: /**
434: * @return positive-int
435: *
436: * @throws CodeLocationMissing
437: */
438: public function getEndColumn(): int
439: {
440: if ($this->endColumn === null) {
441: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
442: }
443:
444: return $this->endColumn;
445: }
446:
447: /** @return list<ReflectionAttribute> */
448: public function getAttributes(): array
449: {
450: return $this->attributes;
451: }
452:
453: /** @return list<ReflectionAttribute> */
454: public function getAttributesByName(string $name): array
455: {
456: return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name);
457: }
458:
459: /**
460: * @param class-string $className
461: *
462: * @return list<ReflectionAttribute>
463: */
464: public function getAttributesByInstance(string $className): array
465: {
466: return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className);
467: }
468:
469: /**
470: * @throws ClassDoesNotExist
471: * @throws NoObjectProvided
472: * @throws ObjectNotInstanceOfClass
473: * @return mixed
474: */
475: public function getValue(?object $object = null)
476: {
477: $implementingClassName = $this->getImplementingClass()->getName();
478:
479: if ($this->isStatic()) {
480: $this->assertClassExist($implementingClassName);
481:
482: $closure = Closure::bind(fn (string $implementingClassName, string $propertyName) => $implementingClassName::${$propertyName}, null, $implementingClassName);
483:
484: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
485: assert($closure instanceof Closure);
486:
487: return $closure->__invoke($implementingClassName, $this->getName());
488: }
489:
490: $instance = $this->assertObject($object);
491:
492: $closure = Closure::bind(fn (object $instance, string $propertyName) => $instance->{$propertyName}, $instance, $implementingClassName);
493:
494: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
495: assert($closure instanceof Closure);
496:
497: return $closure->__invoke($instance, $this->getName());
498: }
499:
500: /**
501: * @throws ClassDoesNotExist
502: * @throws NoObjectProvided
503: * @throws NotAnObject
504: * @throws ObjectNotInstanceOfClass
505: * @param mixed $object
506: * @param mixed $value
507: */
508: public function setValue($object, $value = null): void
509: {
510: $implementingClassName = $this->getImplementingClass()->getName();
511:
512: if ($this->isStatic()) {
513: $this->assertClassExist($implementingClassName);
514:
515: $closure = Closure::bind(function (string $_implementingClassName, string $_propertyName, $value): void {
516: /** @psalm-suppress MixedAssignment */
517: $_implementingClassName::${$_propertyName} = $value;
518: }, null, $implementingClassName);
519:
520: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
521: assert($closure instanceof Closure);
522:
523: $closure->__invoke($implementingClassName, $this->getName(), func_num_args() === 2 ? $value : $object);
524:
525: return;
526: }
527:
528: $instance = $this->assertObject($object);
529:
530: $closure = Closure::bind(function (object $instance, string $propertyName, $value): void {
531: $instance->{$propertyName} = $value;
532: }, $instance, $implementingClassName);
533:
534: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
535: assert($closure instanceof Closure);
536:
537: $closure->__invoke($instance, $this->getName(), $value);
538: }
539:
540: /**
541: * Does this property allow null?
542: */
543: public function allowsNull(): bool
544: {
545: return $this->type === null || $this->type->allowsNull();
546: }
547:
548: /**
549: * @return \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
550: */
551: private function createType(PropertyNode $node)
552: {
553: $type = $node->type;
554:
555: if ($type === null) {
556: return null;
557: }
558:
559: assert($type instanceof Node\Identifier || $type instanceof Node\Name || $type instanceof Node\NullableType || $type instanceof Node\UnionType || $type instanceof Node\IntersectionType);
560:
561: return ReflectionType::createFromNode($this->reflector, $this, $type);
562: }
563:
564: /**
565: * Get the ReflectionType instance representing the type declaration for
566: * this property
567: *
568: * (note: this has nothing to do with DocBlocks).
569: * @return \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
570: */
571: public function getType()
572: {
573: return $this->type;
574: }
575:
576: /**
577: * Does this property have a type declaration?
578: *
579: * (note: this has nothing to do with DocBlocks).
580: */
581: public function hasType(): bool
582: {
583: return $this->type !== null;
584: }
585:
586: public function isVirtual(): bool
587: {
588: $this->cachedVirtual ??= $this->createCachedVirtual();
589:
590: return $this->cachedVirtual;
591: }
592:
593: public function hasHooks(): bool
594: {
595: return $this->getHooks() !== [];
596: }
597:
598: /**
599: * @param ReflectionPropertyHookType::* $hookType
600: */
601: public function hasHook(string $hookType): bool
602: {
603: return isset($this->getHooks()[$hookType]);
604: }
605:
606: /**
607: * @param ReflectionPropertyHookType::* $hookType
608: */
609: public function getHook(string $hookType): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod
610: {
611: return $this->getHooks()[$hookType] ?? null;
612: }
613:
614: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
615: public function getHooks(): array
616: {
617: $this->cachedHooks ??= $this->createCachedHooks();
618:
619: return $this->cachedHooks;
620: }
621:
622: /**
623: * @param class-string $className
624: *
625: * @throws ClassDoesNotExist
626: */
627: private function assertClassExist(string $className): void
628: {
629: if (! ClassExistenceChecker::classExists($className, true) && ! ClassExistenceChecker::traitExists($className, true)) {
630: throw new ClassDoesNotExist('Property cannot be retrieved as the class does not exist');
631: }
632: }
633:
634: /**
635: * @throws NoObjectProvided
636: * @throws NotAnObject
637: * @throws ObjectNotInstanceOfClass
638: *
639: * @psalm-assert object $object
640: * @param mixed $object
641: */
642: private function assertObject($object): object
643: {
644: if ($object === null) {
645: throw NoObjectProvided::create();
646: }
647:
648: if (! is_object($object)) {
649: throw NotAnObject::fromNonObject($object);
650: }
651:
652: $implementingClassName = $this->getImplementingClass()->getName();
653:
654: if (get_class($object) !== $implementingClassName) {
655: throw ObjectNotInstanceOfClass::fromClassName($implementingClassName);
656: }
657:
658: return $object;
659: }
660:
661: /** @return int-mask-of<ReflectionPropertyAdapter::IS_*> */
662: private function computeModifiers(PropertyNode $node): int
663: {
664: $modifiers = $node->isReadonly() ? ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY : 0;
665: $modifiers += $node->isStatic() ? CoreReflectionProperty::IS_STATIC : 0;
666: $modifiers += $node->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0;
667: $modifiers += ! $node->isPrivate() && $node->isPrivateSet() ? ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY : 0;
668: $modifiers += $node->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0;
669: $modifiers += ! $node->isProtected() && $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0;
670: $modifiers += $node->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0;
671: $modifiers += $node->isFinal() ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0;
672: $modifiers += $node->isAbstract() ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0;
673:
674: if (
675: ! ($modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY)
676: && ($modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY)
677: ) {
678: $modifiers += ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY;
679: }
680:
681: if (
682: ! ($modifiers & (ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY))
683: && ! $node->isPublicSet()
684: && $node->isPublic()
685: && ($modifiers & ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY)
686: ) {
687: $modifiers += ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY;
688: }
689:
690: /** @phpstan-ignore return.type */
691: return $modifiers;
692: }
693:
694: private function computeImmediateVirtual(PropertyNode $node): bool
695: {
696: if ($node->hooks === []) {
697: return false;
698: }
699:
700: $setHook = null;
701: $getHook = null;
702:
703: foreach ($node->hooks as $hook) {
704: if ($hook->name->name === 'set') {
705: $setHook = $hook;
706: } elseif ($hook->name->name === 'get') {
707: $getHook = $hook;
708: }
709: }
710:
711: if ($setHook !== null && ! $this->computeImmediateVirtualBasedOnHook($setHook)) {
712: return false;
713: }
714:
715: if ($getHook === null) {
716: return true;
717: }
718:
719: return $this->computeImmediateVirtualBasedOnHook($getHook);
720: }
721:
722: private function computeImmediateVirtualBasedOnHook(Node\PropertyHook $hook): bool
723: {
724: $hookBody = $hook->getStmts();
725:
726: // Abstract property or property in interface
727: if ($hookBody === null) {
728: return true;
729: }
730:
731: $visitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof Node\Expr\PropertyFetch);
732: $traverser = new NodeTraverser($visitor);
733: $traverser->traverse($hookBody);
734:
735: foreach ($visitor->getFoundNodes() as $propertyFetchNode) {
736: assert($propertyFetchNode instanceof Node\Expr\PropertyFetch);
737:
738: if (
739: $propertyFetchNode->var instanceof Node\Expr\Variable
740: && $propertyFetchNode->var->name === 'this'
741: && $propertyFetchNode->name instanceof Node\Identifier
742: && $propertyFetchNode->name->name === $this->name
743: ) {
744: return false;
745: }
746: }
747:
748: return true;
749: }
750:
751: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
752: private function createImmediateHooks(PropertyNode $node): array
753: {
754: $hooks = [];
755:
756: foreach ($node->hooks as $hook) {
757: $hookName = $hook->name->name;
758: assert($hookName === 'get' || $hookName === 'set');
759:
760: $hookType = $node->type;
761: assert($hookType === null || $hookType instanceof Node\Identifier || $hookType instanceof Node\Name || $hookType instanceof Node\NullableType || $hookType instanceof Node\UnionType || $hookType instanceof Node\IntersectionType);
762:
763: $hooks[$hookName] = ReflectionMethod::createFromPropertyHook(
764: $this->reflector,
765: $hook,
766: $this->getDeclaringClass()->getLocatedSource(),
767: sprintf('$%s::%s', $this->name, $hookName),
768: $hookType,
769: $this->getDeclaringClass(),
770: $this->getImplementingClass(),
771: $this->getDeclaringClass(),
772: $this,
773: );
774: }
775:
776: return $hooks;
777: }
778:
779: private function createCachedVirtual(): bool
780: {
781: if (! $this->immediateVirtual) {
782: return false;
783: }
784:
785: return (($nullsafeVariable1 = $this->getParentProperty()) ? $nullsafeVariable1->isVirtual() : null) ?? true;
786: }
787:
788: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
789: private function createCachedHooks(): array
790: {
791: $hooks = $this->immediateHooks;
792:
793: // Just optimization - we don't need to check parent property when both hooks are defined in this class
794: if (isset($hooks['get'], $hooks['set'])) {
795: return $hooks;
796: }
797:
798: $parentHooks = (($nullsafeVariable2 = $this->getParentProperty()) ? $nullsafeVariable2->getHooks() : null) ?? [];
799:
800: foreach ($parentHooks as $hookName => $parentHook) {
801: if (isset($hooks[$hookName])) {
802: continue;
803: }
804:
805: $hooks[$hookName] = $parentHook;
806: }
807:
808: return $hooks;
809: }
810:
811: private function getParentProperty(): ?\PHPStan\BetterReflection\Reflection\ReflectionProperty
812: {
813: return ($nullsafeVariable3 = $this->getDeclaringClass()->getParentClass()) ? $nullsafeVariable3->getProperty($this->name) : null;
814: }
815: }
816: