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: }
295:
296: public function isPromoted(): bool
297: {
298: return $this->isPromoted;
299: }
300:
301: public function isInitialized(?object $object = null): bool
302: {
303: if ($object === null && $this->isStatic()) {
304: return ! $this->hasType() || $this->hasDefaultValue();
305: }
306:
307: try {
308: $this->getValue($object);
309:
310: return true;
311:
312: /** @phpstan-ignore catch.neverThrown */
313: } catch (Error $e) {
314: if (strpos($e->getMessage(), 'must not be accessed before initialization') !== false) {
315: return false;
316: }
317:
318: throw $e;
319: }
320: }
321:
322: public function isReadOnly(): bool
323: {
324: return ($this->modifiers & ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY)
325: || $this->getDeclaringClass()->isReadOnly();
326: }
327:
328: public function getDeclaringClass(): ReflectionClass
329: {
330: return $this->declaringClass;
331: }
332:
333: public function getImplementingClass(): ReflectionClass
334: {
335: return $this->implementingClass;
336: }
337:
338: /** @return non-empty-string|null */
339: public function getDocComment(): ?string
340: {
341: return $this->docComment;
342: }
343:
344: public function hasDefaultValue(): bool
345: {
346: return ! $this->hasType() || $this->default !== null;
347: }
348:
349: public function getDefaultValueExpression(): ?\PhpParser\Node\Expr
350: {
351: return $this->default;
352: }
353:
354: /**
355: * Get the default value of the property (as defined before constructor is
356: * called, when the property is defined)
357: * @return mixed
358: */
359: public function getDefaultValue()
360: {
361: if ($this->default === null) {
362: return null;
363: }
364:
365: if ($this->compiledDefaultValue === null) {
366: $this->compiledDefaultValue = (new CompileNodeToValue())->__invoke(
367: $this->default,
368: new CompilerContext(
369: $this->reflector,
370: $this,
371: ),
372: );
373: }
374:
375: /** @psalm-var scalar|array<scalar>|null $value */
376: $value = $this->compiledDefaultValue->value;
377:
378: return $value;
379: }
380:
381: public function isDeprecated(): bool
382: {
383: return DeprecatedHelper::isDeprecated($this);
384: }
385:
386: /**
387: * Get the line number that this property starts on.
388: *
389: * @return positive-int
390: *
391: * @throws CodeLocationMissing
392: */
393: public function getStartLine(): int
394: {
395: if ($this->startLine === null) {
396: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
397: }
398:
399: return $this->startLine;
400: }
401:
402: /**
403: * Get the line number that this property ends on.
404: *
405: * @return positive-int
406: *
407: * @throws CodeLocationMissing
408: */
409: public function getEndLine(): int
410: {
411: if ($this->endLine === null) {
412: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
413: }
414:
415: return $this->endLine;
416: }
417:
418: /**
419: * @return positive-int
420: *
421: * @throws CodeLocationMissing
422: */
423: public function getStartColumn(): int
424: {
425: if ($this->startColumn === null) {
426: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
427: }
428:
429: return $this->startColumn;
430: }
431:
432: /**
433: * @return positive-int
434: *
435: * @throws CodeLocationMissing
436: */
437: public function getEndColumn(): int
438: {
439: if ($this->endColumn === null) {
440: throw CodeLocationMissing::create(sprintf('Was looking for property "$%s" in "%s".', $this->name, $this->implementingClass->getName()));
441: }
442:
443: return $this->endColumn;
444: }
445:
446: /** @return list<ReflectionAttribute> */
447: public function getAttributes(): array
448: {
449: return $this->attributes;
450: }
451:
452: /** @return list<ReflectionAttribute> */
453: public function getAttributesByName(string $name): array
454: {
455: return ReflectionAttributeHelper::filterAttributesByName($this->getAttributes(), $name);
456: }
457:
458: /**
459: * @param class-string $className
460: *
461: * @return list<ReflectionAttribute>
462: */
463: public function getAttributesByInstance(string $className): array
464: {
465: return ReflectionAttributeHelper::filterAttributesByInstance($this->getAttributes(), $className);
466: }
467:
468: /**
469: * @throws ClassDoesNotExist
470: * @throws NoObjectProvided
471: * @throws ObjectNotInstanceOfClass
472: * @return mixed
473: */
474: public function getValue(?object $object = null)
475: {
476: $implementingClassName = $this->getImplementingClass()->getName();
477:
478: if ($this->isStatic()) {
479: $this->assertClassExist($implementingClassName);
480:
481: $closure = Closure::bind(fn (string $implementingClassName, string $propertyName) => $implementingClassName::${$propertyName}, null, $implementingClassName);
482:
483: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
484: assert($closure instanceof Closure);
485:
486: return $closure->__invoke($implementingClassName, $this->getName());
487: }
488:
489: $instance = $this->assertObject($object);
490:
491: $closure = Closure::bind(fn (object $instance, string $propertyName) => $instance->{$propertyName}, $instance, $implementingClassName);
492:
493: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
494: assert($closure instanceof Closure);
495:
496: return $closure->__invoke($instance, $this->getName());
497: }
498:
499: /**
500: * @throws ClassDoesNotExist
501: * @throws NoObjectProvided
502: * @throws NotAnObject
503: * @throws ObjectNotInstanceOfClass
504: * @param mixed $object
505: * @param mixed $value
506: */
507: public function setValue($object, $value = null): void
508: {
509: $implementingClassName = $this->getImplementingClass()->getName();
510:
511: if ($this->isStatic()) {
512: $this->assertClassExist($implementingClassName);
513:
514: $closure = Closure::bind(function (string $_implementingClassName, string $_propertyName, $value): void {
515: /** @psalm-suppress MixedAssignment */
516: $_implementingClassName::${$_propertyName} = $value;
517: }, null, $implementingClassName);
518:
519: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
520: assert($closure instanceof Closure);
521:
522: $closure->__invoke($implementingClassName, $this->getName(), func_num_args() === 2 ? $value : $object);
523:
524: return;
525: }
526:
527: $instance = $this->assertObject($object);
528:
529: $closure = Closure::bind(function (object $instance, string $propertyName, $value): void {
530: $instance->{$propertyName} = $value;
531: }, $instance, $implementingClassName);
532:
533: /** @phpstan-ignore function.alreadyNarrowedType, instanceof.alwaysTrue */
534: assert($closure instanceof Closure);
535:
536: $closure->__invoke($instance, $this->getName(), $value);
537: }
538:
539: /**
540: * Does this property allow null?
541: */
542: public function allowsNull(): bool
543: {
544: return $this->type === null || $this->type->allowsNull();
545: }
546:
547: /**
548: * @return \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
549: */
550: private function createType(PropertyNode $node)
551: {
552: $type = $node->type;
553:
554: if ($type === null) {
555: return null;
556: }
557:
558: assert($type instanceof Node\Identifier || $type instanceof Node\Name || $type instanceof Node\NullableType || $type instanceof Node\UnionType || $type instanceof Node\IntersectionType);
559:
560: return ReflectionType::createFromNode($this->reflector, $this, $type);
561: }
562:
563: /**
564: * Get the ReflectionType instance representing the type declaration for
565: * this property
566: *
567: * (note: this has nothing to do with DocBlocks).
568: * @return \PHPStan\BetterReflection\Reflection\ReflectionNamedType|\PHPStan\BetterReflection\Reflection\ReflectionUnionType|\PHPStan\BetterReflection\Reflection\ReflectionIntersectionType|null
569: */
570: public function getType()
571: {
572: return $this->type;
573: }
574:
575: /**
576: * Does this property have a type declaration?
577: *
578: * (note: this has nothing to do with DocBlocks).
579: */
580: public function hasType(): bool
581: {
582: return $this->type !== null;
583: }
584:
585: public function isVirtual(): bool
586: {
587: $this->cachedVirtual ??= $this->createCachedVirtual();
588:
589: return $this->cachedVirtual;
590: }
591:
592: public function hasHooks(): bool
593: {
594: return $this->getHooks() !== [];
595: }
596:
597: /**
598: * @param ReflectionPropertyHookType::* $hookType
599: */
600: public function hasHook(string $hookType): bool
601: {
602: return isset($this->getHooks()[$hookType]);
603: }
604:
605: /**
606: * @param @param ReflectionPropertyHookType::* $hookType
607: */
608: public function getHook(string $hookType): ?\PHPStan\BetterReflection\Reflection\ReflectionMethod
609: {
610: return $this->getHooks()[$hookType] ?? null;
611: }
612:
613: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
614: public function getHooks(): array
615: {
616: $this->cachedHooks ??= $this->createCachedHooks();
617:
618: return $this->cachedHooks;
619: }
620:
621: /**
622: * @param class-string $className
623: *
624: * @throws ClassDoesNotExist
625: */
626: private function assertClassExist(string $className): void
627: {
628: if (! ClassExistenceChecker::classExists($className, true) && ! ClassExistenceChecker::traitExists($className, true)) {
629: throw new ClassDoesNotExist('Property cannot be retrieved as the class does not exist');
630: }
631: }
632:
633: /**
634: * @throws NoObjectProvided
635: * @throws NotAnObject
636: * @throws ObjectNotInstanceOfClass
637: *
638: * @psalm-assert object $object
639: * @param mixed $object
640: */
641: private function assertObject($object): object
642: {
643: if ($object === null) {
644: throw NoObjectProvided::create();
645: }
646:
647: if (! is_object($object)) {
648: throw NotAnObject::fromNonObject($object);
649: }
650:
651: $implementingClassName = $this->getImplementingClass()->getName();
652:
653: if (get_class($object) !== $implementingClassName) {
654: throw ObjectNotInstanceOfClass::fromClassName($implementingClassName);
655: }
656:
657: return $object;
658: }
659:
660: /** @return int-mask-of<ReflectionPropertyAdapter::IS_*> */
661: private function computeModifiers(PropertyNode $node): int
662: {
663: $modifiers = $node->isReadonly() ? ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY : 0;
664: $modifiers += $node->isStatic() ? CoreReflectionProperty::IS_STATIC : 0;
665: $modifiers += $node->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0;
666: $modifiers += ! $node->isPrivate() && $node->isPrivateSet() ? ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY : 0;
667: $modifiers += $node->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0;
668: $modifiers += ! $node->isProtected() && $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0;
669: $modifiers += $node->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0;
670: $modifiers += $node->isFinal() ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0;
671: $modifiers += $node->isAbstract() ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0;
672:
673: if (
674: ! ($modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY)
675: && ($modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY)
676: ) {
677: $modifiers += ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY;
678: }
679:
680: if (
681: ! ($modifiers & (ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY))
682: && ! $node->isPublicSet()
683: && $node->isPublic()
684: && ($modifiers & ReflectionPropertyAdapter::IS_READONLY_COMPATIBILITY)
685: ) {
686: $modifiers += ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY;
687: }
688:
689: /** @phpstan-ignore return.type */
690: return $modifiers;
691: }
692:
693: private function computeImmediateVirtual(PropertyNode $node): bool
694: {
695: if ($node->hooks === []) {
696: return false;
697: }
698:
699: $setHook = null;
700: $getHook = null;
701:
702: foreach ($node->hooks as $hook) {
703: if ($hook->name->name === 'set') {
704: $setHook = $hook;
705: } elseif ($hook->name->name === 'get') {
706: $getHook = $hook;
707: }
708: }
709:
710: if ($setHook !== null && ! $this->computeImmediateVirtualBasedOnHook($setHook)) {
711: return false;
712: }
713:
714: if ($getHook === null) {
715: return true;
716: }
717:
718: return $this->computeImmediateVirtualBasedOnHook($getHook);
719: }
720:
721: private function computeImmediateVirtualBasedOnHook(Node\PropertyHook $hook): bool
722: {
723: $hookBody = $hook->getStmts();
724:
725: // Abstract property or property in interface
726: if ($hookBody === null) {
727: return true;
728: }
729:
730: $visitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof Node\Expr\PropertyFetch);
731: $traverser = new NodeTraverser($visitor);
732: $traverser->traverse($hookBody);
733:
734: foreach ($visitor->getFoundNodes() as $propertyFetchNode) {
735: assert($propertyFetchNode instanceof Node\Expr\PropertyFetch);
736:
737: if (
738: $propertyFetchNode->var instanceof Node\Expr\Variable
739: && $propertyFetchNode->var->name === 'this'
740: && $propertyFetchNode->name instanceof Node\Identifier
741: && $propertyFetchNode->name->name === $this->name
742: ) {
743: return false;
744: }
745: }
746:
747: return true;
748: }
749:
750: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
751: private function createImmediateHooks(PropertyNode $node): array
752: {
753: $hooks = [];
754:
755: foreach ($node->hooks as $hook) {
756: $hookName = $hook->name->name;
757: assert($hookName === 'get' || $hookName === 'set');
758:
759: $hookType = $node->type;
760: assert($hookType === null || $hookType instanceof Node\Identifier || $hookType instanceof Node\Name || $hookType instanceof Node\NullableType || $hookType instanceof Node\UnionType || $hookType instanceof Node\IntersectionType);
761:
762: $hooks[$hookName] = ReflectionMethod::createFromPropertyHook(
763: $this->reflector,
764: $hook,
765: $this->getDeclaringClass()->getLocatedSource(),
766: sprintf('$%s::%s', $this->name, $hookName),
767: $hookType,
768: $this->getDeclaringClass(),
769: $this->getImplementingClass(),
770: $this->getDeclaringClass(),
771: $this,
772: );
773: }
774:
775: return $hooks;
776: }
777:
778: private function createCachedVirtual(): bool
779: {
780: if (! $this->immediateVirtual) {
781: return false;
782: }
783:
784: return (($nullsafeVariable1 = $this->getParentProperty()) ? $nullsafeVariable1->isVirtual() : null) ?? true;
785: }
786:
787: /** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */
788: private function createCachedHooks(): array
789: {
790: $hooks = $this->immediateHooks;
791:
792: // Just optimization - we don't need to check parent property when both hooks are defined in this class
793: if (isset($hooks['get'], $hooks['set'])) {
794: return $hooks;
795: }
796:
797: $parentHooks = (($nullsafeVariable2 = $this->getParentProperty()) ? $nullsafeVariable2->getHooks() : null) ?? [];
798:
799: foreach ($parentHooks as $hookName => $parentHook) {
800: if (isset($hooks[$hookName])) {
801: continue;
802: }
803:
804: $hooks[$hookName] = $parentHook;
805: }
806:
807: return $hooks;
808: }
809:
810: private function getParentProperty(): ?\PHPStan\BetterReflection\Reflection\ReflectionProperty
811: {
812: return ($nullsafeVariable3 = $this->getDeclaringClass()->getParentClass()) ? $nullsafeVariable3->getProperty($this->name) : null;
813: }
814: }
815: