@@ -83,6 +83,12 @@ final class ClassReflection
8383 /** @var ExtendedPropertyReflection[] */
8484 private array $ properties = [];
8585
86+ /** @var ExtendedPropertyReflection[] */
87+ private array $ instanceProperties = [];
88+
89+ /** @var ExtendedPropertyReflection[] */
90+ private array $ staticProperties = [];
91+
8692 /** @var RealClassClassConstantReflection[] */
8793 private array $ constants = [];
8894
@@ -149,6 +155,12 @@ final class ClassReflection
149155 /** @var array<string, bool> */
150156 private array $ hasPropertyCache = [];
151157
158+ /** @var array<string, bool> */
159+ private array $ hasInstancePropertyCache = [];
160+
161+ /** @var array<string, bool> */
162+ private array $ hasStaticPropertyCache = [];
163+
152164 /**
153165 * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions
154166 * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions
@@ -449,6 +461,9 @@ public function allowsDynamicProperties(): bool
449461 return $ attributes !== [];
450462 }
451463
464+ /**
465+ * @deprecated Use hasInstanceProperty or hasStaticProperty instead
466+ */
452467 public function hasProperty (string $ propertyName ): bool
453468 {
454469 if (array_key_exists ($ propertyName , $ this ->hasPropertyCache )) {
@@ -468,13 +483,68 @@ public function hasProperty(string $propertyName): bool
468483 }
469484 }
470485
486+ // For BC purpose
487+ if ($ this ->getPhpExtension ()->hasProperty ($ this , $ propertyName )) {
488+ return $ this ->hasPropertyCache [$ propertyName ] = true ;
489+ }
490+
471491 if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasProperty ($ this , $ propertyName )) {
472492 return $ this ->hasPropertyCache [$ propertyName ] = true ;
473493 }
474494
475495 return $ this ->hasPropertyCache [$ propertyName ] = false ;
476496 }
477497
498+ public function hasInstanceProperty (string $ propertyName ): bool
499+ {
500+ if (array_key_exists ($ propertyName , $ this ->hasInstancePropertyCache )) {
501+ return $ this ->hasInstancePropertyCache [$ propertyName ];
502+ }
503+
504+ if ($ this ->isEnum ()) {
505+ return $ this ->hasInstancePropertyCache [$ propertyName ] = $ this ->hasNativeProperty ($ propertyName );
506+ }
507+
508+ foreach ($ this ->propertiesClassReflectionExtensions as $ i => $ extension ) {
509+ if ($ i > 0 && !$ this ->allowsDynamicProperties ()) {
510+ break ;
511+ }
512+ if ($ extension ->hasProperty ($ this , $ propertyName )) {
513+ $ property = $ extension ->getProperty ($ this , $ propertyName );
514+ if ($ property ->isStatic ()) {
515+ continue ;
516+ }
517+ return $ this ->hasInstancePropertyCache [$ propertyName ] = true ;
518+ }
519+ }
520+
521+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasInstanceProperty ($ this , $ propertyName )) {
522+ return $ this ->hasPropertyCache [$ propertyName ] = true ;
523+ }
524+
525+ return $ this ->hasPropertyCache [$ propertyName ] = false ;
526+ }
527+
528+ public function hasStaticProperty (string $ propertyName ): bool
529+ {
530+ if (array_key_exists ($ propertyName , $ this ->hasStaticPropertyCache )) {
531+ return $ this ->hasStaticPropertyCache [$ propertyName ];
532+ }
533+
534+ if ($ this ->getPhpExtension ()->hasProperty ($ this , $ propertyName )) {
535+ $ property = $ this ->getPhpExtension ()->getProperty ($ this , $ propertyName );
536+ if ($ property ->isStatic ()) {
537+ return $ this ->hasStaticPropertyCache [$ propertyName ] = true ;
538+ }
539+ }
540+
541+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasStaticProperty ($ this , $ propertyName )) {
542+ return $ this ->hasStaticPropertyCache [$ propertyName ] = true ;
543+ }
544+
545+ return $ this ->hasStaticPropertyCache [$ propertyName ] = false ;
546+ }
547+
478548 public function hasMethod (string $ methodName ): bool
479549 {
480550 if (array_key_exists ($ methodName , $ this ->hasMethodCache )) {
@@ -619,6 +689,20 @@ public function evictPrivateSymbols(): void
619689
620690 unset($ this ->properties [$ name ]);
621691 }
692+ foreach ($ this ->instanceProperties as $ name => $ property ) {
693+ if (!$ property ->isPrivate ()) {
694+ continue ;
695+ }
696+
697+ unset($ this ->instanceProperties [$ name ]);
698+ }
699+ foreach ($ this ->staticProperties as $ name => $ property ) {
700+ if (!$ property ->isPrivate ()) {
701+ continue ;
702+ }
703+
704+ unset($ this ->staticProperties [$ name ]);
705+ }
622706 foreach ($ this ->methods as $ name => $ method ) {
623707 if (!$ method ->isPrivate ()) {
624708 continue ;
@@ -629,6 +713,7 @@ public function evictPrivateSymbols(): void
629713 $ this ->getPhpExtension ()->evictPrivateSymbols ($ this ->getCacheKey ());
630714 }
631715
716+ /** @deprecated Use getInstanceProperty or getStaticProperty */
632717 public function getProperty (string $ propertyName , ClassMemberAccessAnswerer $ scope ): ExtendedPropertyReflection
633718 {
634719 if ($ this ->isEnum ()) {
@@ -658,6 +743,13 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
658743 }
659744 }
660745
746+ // For BC purpose
747+ if ($ this ->getPhpExtension ()->hasProperty ($ this , $ propertyName )) {
748+ $ property = $ this ->getPhpExtension ()->getProperty ($ this , $ propertyName );
749+
750+ return $ this ->properties [$ key ] = $ property ;
751+ }
752+
661753 if (!isset ($ this ->properties [$ key ])) {
662754 if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasProperty ($ this , $ propertyName )) {
663755 $ property = $ this ->requireExtendsPropertiesClassReflectionExtension ->getProperty ($ this , $ propertyName );
@@ -672,6 +764,79 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
672764 return $ this ->properties [$ key ];
673765 }
674766
767+ public function getInstanceProperty (string $ propertyName , ClassMemberAccessAnswerer $ scope ): ExtendedPropertyReflection
768+ {
769+ if ($ this ->isEnum ()) {
770+ return $ this ->getNativeProperty ($ propertyName );
771+ }
772+
773+ $ key = $ propertyName ;
774+ if ($ scope ->isInClass ()) {
775+ $ key = sprintf ('%s-%s ' , $ key , $ scope ->getClassReflection ()->getCacheKey ());
776+ }
777+
778+ if (!isset ($ this ->instanceProperties [$ key ])) {
779+ foreach ($ this ->propertiesClassReflectionExtensions as $ i => $ extension ) {
780+ if ($ i > 0 && !$ this ->allowsDynamicProperties ()) {
781+ break ;
782+ }
783+
784+ if (!$ extension ->hasProperty ($ this , $ propertyName )) {
785+ continue ;
786+ }
787+
788+ $ nakedProperty = $ extension ->getProperty ($ this , $ propertyName );
789+ if ($ nakedProperty ->isStatic ()) {
790+ continue ;
791+ }
792+
793+ $ property = $ this ->wrapExtendedProperty ($ propertyName , $ nakedProperty );
794+ if ($ scope ->canReadProperty ($ property )) {
795+ return $ this ->instanceProperties [$ key ] = $ property ;
796+ }
797+ $ this ->instanceProperties [$ key ] = $ property ;
798+ }
799+ }
800+
801+ if (!isset ($ this ->instanceProperties [$ key ])) {
802+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasInstanceProperty ($ this , $ propertyName )) {
803+ $ property = $ this ->requireExtendsPropertiesClassReflectionExtension ->getInstanceProperty ($ this , $ propertyName );
804+ $ this ->instanceProperties [$ key ] = $ property ;
805+ }
806+ }
807+
808+ if (!isset ($ this ->instanceProperties [$ key ])) {
809+ throw new MissingPropertyFromReflectionException ($ this ->getName (), $ propertyName );
810+ }
811+
812+ return $ this ->instanceProperties [$ key ];
813+ }
814+
815+ public function getStaticProperty (string $ propertyName ): ExtendedPropertyReflection
816+ {
817+ $ key = $ propertyName ;
818+ if (isset ($ this ->staticProperties [$ key ])) {
819+ return $ this ->staticProperties [$ key ];
820+ }
821+
822+ if ($ this ->getPhpExtension ()->hasProperty ($ this , $ propertyName )) {
823+ $ nakedProperty = $ this ->getPhpExtension ()->getProperty ($ this , $ propertyName );
824+ if ($ nakedProperty ->isStatic ()) {
825+ $ property = $ this ->wrapExtendedProperty ($ propertyName , $ nakedProperty );
826+ if ($ property ->isStatic ()) {
827+ return $ this ->staticProperties [$ key ] = $ property ;
828+ }
829+ }
830+ }
831+
832+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasStaticProperty ($ this , $ propertyName )) {
833+ $ property = $ this ->requireExtendsPropertiesClassReflectionExtension ->getStaticProperty ($ this , $ propertyName );
834+ return $ this ->staticProperties [$ key ] = $ property ;
835+ }
836+
837+ throw new MissingPropertyFromReflectionException ($ this ->getName (), $ propertyName );
838+ }
839+
675840 public function hasNativeProperty (string $ propertyName ): bool
676841 {
677842 return $ this ->getPhpExtension ()->hasProperty ($ this , $ propertyName );
0 commit comments