@@ -81,6 +81,12 @@ final class ClassReflection
8181 /** @var ExtendedPropertyReflection[] */
8282 private array $ properties = [];
8383
84+ /** @var ExtendedPropertyReflection[] */
85+ private array $ instanceProperties = [];
86+
87+ /** @var ExtendedPropertyReflection[] */
88+ private array $ staticProperties = [];
89+
8490 /** @var RealClassClassConstantReflection[] */
8591 private array $ constants = [];
8692
@@ -147,6 +153,12 @@ final class ClassReflection
147153 /** @var array<string, bool> */
148154 private array $ hasPropertyCache = [];
149155
156+ /** @var array<string, bool> */
157+ private array $ hasInstancePropertyCache = [];
158+
159+ /** @var array<string, bool> */
160+ private array $ hasStaticPropertyCache = [];
161+
150162 /**
151163 * @param PropertiesClassReflectionExtension[] $propertiesClassReflectionExtensions
152164 * @param MethodsClassReflectionExtension[] $methodsClassReflectionExtensions
@@ -460,6 +472,9 @@ private function allowsDynamicPropertiesExtensions(): bool
460472 return false ;
461473 }
462474
475+ /**
476+ * @deprecated Use hasInstanceProperty or hasStaticProperty instead
477+ */
463478 public function hasProperty (string $ propertyName ): bool
464479 {
465480 if (array_key_exists ($ propertyName , $ this ->hasPropertyCache )) {
@@ -479,13 +494,61 @@ public function hasProperty(string $propertyName): bool
479494 }
480495 }
481496
497+ // For BC purpose
498+ if ($ this ->getPhpExtension ()->hasStaticProperty ($ this , $ propertyName )) {
499+ return $ this ->hasPropertyCache [$ propertyName ] = true ;
500+ }
501+
482502 if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasProperty ($ this , $ propertyName )) {
483503 return $ this ->hasPropertyCache [$ propertyName ] = true ;
484504 }
485505
486506 return $ this ->hasPropertyCache [$ propertyName ] = false ;
487507 }
488508
509+ public function hasInstanceProperty (string $ propertyName ): bool
510+ {
511+ if (array_key_exists ($ propertyName , $ this ->hasInstancePropertyCache )) {
512+ return $ this ->hasInstancePropertyCache [$ propertyName ];
513+ }
514+
515+ if ($ this ->isEnum ()) {
516+ return $ this ->hasInstancePropertyCache [$ propertyName ] = $ this ->hasNativeProperty ($ propertyName );
517+ }
518+
519+ foreach ($ this ->propertiesClassReflectionExtensions as $ i => $ extension ) {
520+ if ($ i > 0 && !$ this ->allowsDynamicPropertiesExtensions ()) {
521+ break ;
522+ }
523+ if ($ extension ->hasProperty ($ this , $ propertyName )) {
524+ return $ this ->hasInstancePropertyCache [$ propertyName ] = true ;
525+ }
526+ }
527+
528+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasInstanceProperty ($ this , $ propertyName )) {
529+ return $ this ->hasPropertyCache [$ propertyName ] = true ;
530+ }
531+
532+ return $ this ->hasPropertyCache [$ propertyName ] = false ;
533+ }
534+
535+ public function hasStaticProperty (string $ propertyName ): bool
536+ {
537+ if (array_key_exists ($ propertyName , $ this ->hasStaticPropertyCache )) {
538+ return $ this ->hasStaticPropertyCache [$ propertyName ];
539+ }
540+
541+ if ($ this ->getPhpExtension ()->hasStaticProperty ($ this , $ propertyName )) {
542+ return $ this ->hasStaticPropertyCache [$ propertyName ] = true ;
543+ }
544+
545+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasStaticProperty ($ this , $ propertyName )) {
546+ return $ this ->hasStaticPropertyCache [$ propertyName ] = true ;
547+ }
548+
549+ return $ this ->hasStaticPropertyCache [$ propertyName ] = false ;
550+ }
551+
489552 public function hasMethod (string $ methodName ): bool
490553 {
491554 if (array_key_exists ($ methodName , $ this ->hasMethodCache )) {
@@ -630,6 +693,20 @@ public function evictPrivateSymbols(): void
630693
631694 unset($ this ->properties [$ name ]);
632695 }
696+ foreach ($ this ->instanceProperties as $ name => $ property ) {
697+ if (!$ property ->isPrivate ()) {
698+ continue ;
699+ }
700+
701+ unset($ this ->instanceProperties [$ name ]);
702+ }
703+ foreach ($ this ->staticProperties as $ name => $ property ) {
704+ if (!$ property ->isPrivate ()) {
705+ continue ;
706+ }
707+
708+ unset($ this ->staticProperties [$ name ]);
709+ }
633710 foreach ($ this ->methods as $ name => $ method ) {
634711 if (!$ method ->isPrivate ()) {
635712 continue ;
@@ -640,6 +717,7 @@ public function evictPrivateSymbols(): void
640717 $ this ->getPhpExtension ()->evictPrivateSymbols ($ this ->getCacheKey ());
641718 }
642719
720+ /** @deprecated Use getInstanceProperty or getStaticProperty */
643721 public function getProperty (string $ propertyName , ClassMemberAccessAnswerer $ scope ): ExtendedPropertyReflection
644722 {
645723 if ($ this ->isEnum ()) {
@@ -669,6 +747,15 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
669747 }
670748 }
671749
750+ // For BC purpose
751+ if ($ this ->getPhpExtension ()->hasStaticProperty ($ this , $ propertyName )) {
752+ $ property = $ this ->wrapExtendedProperty ($ this ->getPhpExtension ()->getStaticProperty ($ this , $ propertyName ));
753+ if ($ scope ->canReadProperty ($ property )) {
754+ return $ this ->properties [$ key ] = $ property ;
755+ }
756+ $ this ->properties [$ key ] = $ property ;
757+ }
758+
672759 if (!isset ($ this ->properties [$ key ])) {
673760 if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasProperty ($ this , $ propertyName )) {
674761 $ property = $ this ->requireExtendsPropertiesClassReflectionExtension ->getProperty ($ this , $ propertyName );
@@ -683,9 +770,83 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
683770 return $ this ->properties [$ key ];
684771 }
685772
773+ public function getInstanceProperty (string $ propertyName , ClassMemberAccessAnswerer $ scope ): ExtendedPropertyReflection
774+ {
775+ if ($ this ->isEnum ()) {
776+ return $ this ->getNativeProperty ($ propertyName );
777+ }
778+
779+ $ key = $ propertyName ;
780+ if ($ scope ->isInClass ()) {
781+ $ key = sprintf ('%s-%s ' , $ key , $ scope ->getClassReflection ()->getCacheKey ());
782+ }
783+
784+ if (!isset ($ this ->instanceProperties [$ key ])) {
785+ foreach ($ this ->propertiesClassReflectionExtensions as $ i => $ extension ) {
786+ if ($ i > 0 && !$ this ->allowsDynamicPropertiesExtensions ()) {
787+ break ;
788+ }
789+
790+ if (!$ extension ->hasProperty ($ this , $ propertyName )) {
791+ continue ;
792+ }
793+
794+ $ property = $ this ->wrapExtendedProperty ($ extension ->getProperty ($ this , $ propertyName ));
795+ if ($ scope ->canReadProperty ($ property )) {
796+ return $ this ->instanceProperties [$ key ] = $ property ;
797+ }
798+ $ this ->instanceProperties [$ key ] = $ property ;
799+ }
800+ }
801+
802+ if (!isset ($ this ->instanceProperties [$ key ])) {
803+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasInstanceProperty ($ this , $ propertyName )) {
804+ $ property = $ this ->requireExtendsPropertiesClassReflectionExtension ->getInstanceProperty ($ this , $ propertyName );
805+ $ this ->instanceProperties [$ key ] = $ property ;
806+ }
807+ }
808+
809+ if (!isset ($ this ->instanceProperties [$ key ])) {
810+ throw new MissingPropertyFromReflectionException ($ this ->getName (), $ propertyName );
811+ }
812+
813+ return $ this ->instanceProperties [$ key ];
814+ }
815+
816+ public function getStaticProperty (string $ propertyName , ClassMemberAccessAnswerer $ scope ): ExtendedPropertyReflection
817+ {
818+ $ key = $ propertyName ;
819+ if ($ scope ->isInClass ()) {
820+ $ key = sprintf ('%s-%s ' , $ key , $ scope ->getClassReflection ()->getCacheKey ());
821+ }
822+
823+ if (!isset ($ this ->staticProperties [$ key ])) {
824+ if ($ this ->getPhpExtension ()->hasStaticProperty ($ this , $ propertyName )) {
825+ $ property = $ this ->wrapExtendedProperty ($ this ->getPhpExtension ()->getStaticProperty ($ this , $ propertyName ));
826+ if ($ scope ->canReadProperty ($ property )) {
827+ return $ this ->staticProperties [$ key ] = $ property ;
828+ }
829+ $ this ->staticProperties [$ key ] = $ property ;
830+ }
831+ }
832+
833+ if (!isset ($ this ->staticProperties [$ key ])) {
834+ if ($ this ->requireExtendsPropertiesClassReflectionExtension ->hasStaticProperty ($ this , $ propertyName )) {
835+ $ property = $ this ->requireExtendsPropertiesClassReflectionExtension ->getStaticProperty ($ this , $ propertyName );
836+ $ this ->staticProperties [$ key ] = $ property ;
837+ }
838+ }
839+
840+ if (!isset ($ this ->staticProperties [$ key ])) {
841+ throw new MissingPropertyFromReflectionException ($ this ->getName (), $ propertyName );
842+ }
843+
844+ return $ this ->staticProperties [$ key ];
845+ }
846+
686847 public function hasNativeProperty (string $ propertyName ): bool
687848 {
688- return $ this ->getPhpExtension ()->hasProperty ($ this , $ propertyName );
849+ return $ this ->getPhpExtension ()->hasNativeProperty ($ this , $ propertyName );
689850 }
690851
691852 public function getNativeProperty (string $ propertyName ): PhpPropertyReflection
0 commit comments