3030use Kint \Value \AbstractValue ;
3131use Kint \Value \Context \ClassConstContext ;
3232use Kint \Value \Context \ClassDeclaredContext ;
33- use Kint \Value \Context \ClassOwnedContext ;
3433use Kint \Value \Context \StaticPropertyContext ;
3534use Kint \Value \InstanceValue ;
3635use Kint \Value \Representation \ContainerRepresentation ;
4241
4342class ClassStaticsPlugin extends AbstractPlugin implements PluginCompleteInterface
4443{
45- /** @psalm-var array<class-string, array<1|0, list <AbstractValue>>> */
44+ /** @psalm-var array<class-string, array<1|0, array <AbstractValue>>> */
4645 private array $ cache = [];
4746
4847 public function getTypes (): array
@@ -69,162 +68,163 @@ public function parseComplete(&$var, AbstractValue $v, int $trigger): AbstractVa
6968 return $ v ;
7069 }
7170
72- $ class = $ v ->getClassName ();
73- $ parser = $ this ->getParser ();
74- $ r = new ReflectionClass ($ class );
71+ $ deep = 0 === $ this ->getParser ()->getDepthLimit ();
7572
76- $ statics_full_name = false ;
77- $ statics = [];
78- $ props = $ r ->getProperties (ReflectionProperty::IS_STATIC );
79- foreach ($ props as $ prop ) {
80- $ statics [$ prop ->name ] = $ prop ;
81- }
73+ $ r = new ReflectionClass ($ v ->getClassName ());
8274
83- $ parent = $ r ;
84- while ($ parent = $ parent ->getParentClass ()) {
85- foreach ($ parent ->getProperties (ReflectionProperty::IS_STATIC ) as $ static ) {
86- if (isset ($ statics [$ static ->name ]) && $ statics [$ static ->name ]->getDeclaringClass ()->name === $ static ->getDeclaringClass ()->name ) {
87- continue ;
88- }
89- $ statics [] = $ static ;
90- }
75+ if ($ statics = $ this ->getStatics ($ r , $ v ->getContext ()->getDepth () + 1 )) {
76+ $ v ->addRepresentation (new ContainerRepresentation ('Static properties ' , \array_values ($ statics ), 'statics ' ));
9177 }
9278
93- $ statics_parsed = [];
94- $ found_statics = [];
79+ if ($ consts = $ this ->getCachedConstants ($ r , $ deep )) {
80+ $ v ->addRepresentation (new ContainerRepresentation ('Class constants ' , \array_values ($ consts ), 'constants ' ));
81+ }
9582
96- $ cdepth = $ v ->getContext ()->getDepth ();
83+ return $ v ;
84+ }
9785
98- foreach ($ statics as $ static ) {
99- $ prop = new StaticPropertyContext (
100- '$ ' .$ static ->getName (),
101- $ static ->getDeclaringClass ()->name ,
102- ClassDeclaredContext::ACCESS_PUBLIC
103- );
104- $ prop ->depth = $ cdepth + 1 ;
105- $ prop ->final = KINT_PHP84 && $ static ->isFinal ();
86+ /** @psalm-return array<AbstractValue> */
87+ private function getStatics (ReflectionClass $ r , int $ depth ): array
88+ {
89+ $ cdepth = $ depth ?: 1 ;
90+ $ class = $ r ->getName ();
91+ $ parent = $ r ->getParentClass ();
10692
107- if ($ static ->isProtected ()) {
108- $ prop ->access = ClassDeclaredContext::ACCESS_PROTECTED ;
109- } elseif ($ static ->isPrivate ()) {
110- $ prop ->access = ClassDeclaredContext::ACCESS_PRIVATE ;
111- }
93+ $ parent_statics = $ parent ? $ this ->getStatics ($ parent , $ depth ) : [];
94+ $ statics = [];
11295
113- if ($ prop ->isAccessible ($ parser ->getCallerClass ())) {
114- $ prop ->access_path = '\\' .$ prop ->owner_class .':: ' .$ prop ->name ;
115- }
96+ foreach ($ r ->getProperties (ReflectionProperty::IS_STATIC ) as $ pr ) {
97+ $ canon_name = \strtolower ($ pr ->getDeclaringClass ()->name .':: ' .$ pr ->name );
11698
117- if (isset ($ found_statics [$ prop ->name ])) {
118- $ statics_full_name = true ;
99+ if ($ pr ->getDeclaringClass ()->name === $ class ) {
100+ $ statics [$ canon_name ] = $ this ->buildStaticValue ($ pr , $ cdepth );
101+ } elseif (isset ($ parent_statics [$ canon_name ])) {
102+ $ statics [$ canon_name ] = $ parent_statics [$ canon_name ];
103+ unset($ parent_statics [$ canon_name ]);
119104 } else {
120- $ found_statics [$ prop ->name ] = true ;
121-
122- if ($ prop ->owner_class !== $ class && ClassDeclaredContext::ACCESS_PRIVATE === $ prop ->access ) {
123- $ statics_full_name = true ;
124- }
105+ // This should never happen since abstract static properties can't exist
106+ $ statics [$ canon_name ] = $ this ->buildStaticValue ($ pr , $ cdepth ); // @codeCoverageIgnore
125107 }
108+ }
126109
127- if ( $ statics_full_name ) {
128- $ prop -> name = $ prop -> owner_class . ' :: ' . $ prop -> name ;
129- }
110+ foreach ( $ parent_statics as $ canon_name => $ value ) {
111+ $ statics [ $ canon_name ] = $ value ;
112+ }
130113
131- $ static ->setAccessible (true );
114+ return $ statics ;
115+ }
132116
133- /**
134- * @psalm-suppress TooFewArguments
135- * Appears to have been fixed in master
136- */
137- if (!$ static ->isInitialized ()) {
138- $ statics_parsed [] = new UninitializedValue ($ prop );
139- } else {
140- $ static = $ static ->getValue ();
141- $ statics_parsed [] = $ parser ->parse ($ static , $ prop );
142- }
117+ private function buildStaticValue (ReflectionProperty $ pr , int $ depth ): AbstractValue
118+ {
119+ $ context = new StaticPropertyContext (
120+ $ pr ->name ,
121+ $ pr ->getDeclaringClass ()->name ,
122+ ClassDeclaredContext::ACCESS_PUBLIC
123+ );
124+ $ context ->depth = $ depth ;
125+ $ context ->final = KINT_PHP84 && $ pr ->isFinal ();
126+
127+ if ($ pr ->isProtected ()) {
128+ $ context ->access = ClassDeclaredContext::ACCESS_PROTECTED ;
129+ } elseif ($ pr ->isPrivate ()) {
130+ $ context ->access = ClassDeclaredContext::ACCESS_PRIVATE ;
143131 }
144132
145- if ($ statics_parsed ) {
146- $ v ->addRepresentation (new ContainerRepresentation ('Static properties ' , $ statics_parsed , 'statics ' ));
133+ $ parser = $ this ->getParser ();
134+
135+ if ($ context ->isAccessible ($ parser ->getCallerClass ())) {
136+ $ context ->access_path = '\\' .$ context ->owner_class .'::$ ' .$ context ->name ;
147137 }
148138
149- if ($ consts = $ this ->getCachedConstants ($ r )) {
150- $ v ->addRepresentation (new ContainerRepresentation ('Class constants ' , $ consts , 'constants ' ));
139+ $ pr ->setAccessible (true );
140+
141+ /**
142+ * @psalm-suppress TooFewArguments
143+ * Appears to have been fixed in master.
144+ */
145+ if (!$ pr ->isInitialized ()) {
146+ $ context ->access_path = null ;
147+
148+ return new UninitializedValue ($ context );
151149 }
152150
153- return $ v ;
151+ $ val = $ pr ->getValue ();
152+
153+ $ out = $ this ->getParser ()->parse ($ val , $ context );
154+ $ context ->access_path = null ;
155+
156+ return $ out ;
154157 }
155158
156- /** @psalm-return list <AbstractValue> */
157- private function getCachedConstants (ReflectionClass $ r ): array
159+ /** @psalm-return array <AbstractValue> */
160+ private function getCachedConstants (ReflectionClass $ r, bool $ deep ): array
158161 {
159162 $ parser = $ this ->getParser ();
160- $ pdepth = $ parser ->getDepthLimit ();
161- $ pdepth_enabled = (int ) ( $ pdepth > 0 ) ;
163+ $ cdepth = $ parser ->getDepthLimit () ?: 1 ;
164+ $ deepkey = (int ) $ deep ;
162165 $ class = $ r ->getName ();
163166
164167 // Separate cache for dumping with/without depth limit
165168 // This means we can do immediate depth limit on normal dumps
166- if (!isset ($ this ->cache [$ class ][$ pdepth_enabled ])) {
169+ if (!isset ($ this ->cache [$ class ][$ deepkey ])) {
167170 $ consts = [];
168- $ reflectors = [];
169171
172+ $ parent_consts = [];
173+ if ($ parent = $ r ->getParentClass ()) {
174+ $ parent_consts = $ this ->getCachedConstants ($ parent , $ deep );
175+ }
170176 foreach ($ r ->getConstants () as $ name => $ val ) {
171177 $ cr = new ReflectionClassConstant ($ class , $ name );
172178
173179 // Skip enum constants
174- if (\is_a ( $ cr ->class , UnitEnum:: class, true ) && $ val instanceof UnitEnum && $ cr -> class === \get_class ( $ val )) {
180+ if ($ cr ->class === $ class && \is_a ( $ class , UnitEnum:: class, true )) {
175181 continue ;
176182 }
177183
178- $ reflectors [$ cr ->name ] = [$ cr , $ val ];
179- $ consts [$ cr ->name ] = null ;
180- }
184+ $ canon_name = \strtolower ($ cr ->getDeclaringClass ()->name .':: ' .$ name );
181185
182- if ($ r = $ r ->getParentClass ()) {
183- $ parents = $ this ->getCachedConstants ($ r );
184-
185- foreach ($ parents as $ value ) {
186- $ c = $ value ->getContext ();
187- $ cname = $ c ->getName ();
188-
189- if (isset ($ reflectors [$ cname ]) && $ c instanceof ClassOwnedContext && $ reflectors [$ cname ][0 ]->getDeclaringClass ()->name === $ c ->owner_class ) {
190- $ consts [$ cname ] = $ value ;
191- unset($ reflectors [$ cname ]);
192- } else {
193- $ value = clone $ value ;
194- $ c = $ value ->getContext ();
195- if ($ c instanceof ClassOwnedContext) {
196- $ c ->name = $ c ->owner_class .':: ' .$ cname ;
197- }
198- $ consts [] = $ value ;
199- }
200- }
201- }
186+ if ($ cr ->getDeclaringClass ()->name === $ class ) {
187+ $ context = $ this ->buildConstContext ($ cr );
188+ $ context ->depth = $ cdepth ;
202189
203- foreach ($ reflectors as [$ cr , $ val ]) {
204- $ context = new ClassConstContext (
205- $ cr ->name ,
206- $ cr ->getDeclaringClass ()->name ,
207- ClassDeclaredContext::ACCESS_PUBLIC
208- );
209- $ context ->depth = $ pdepth ?: 1 ;
210- $ context ->final = KINT_PHP81 && $ cr ->isFinal ();
211-
212- if ($ cr ->isProtected ()) {
213- $ context ->access = ClassDeclaredContext::ACCESS_PROTECTED ;
214- } elseif ($ cr ->isPrivate ()) {
215- $ context ->access = ClassDeclaredContext::ACCESS_PRIVATE ;
190+ $ consts [$ canon_name ] = $ parser ->parse ($ val , $ context );
191+ $ context ->access_path = null ;
192+ } elseif (isset ($ parent_consts [$ canon_name ])) {
193+ $ consts [$ canon_name ] = $ parent_consts [$ canon_name ];
216194 } else {
217- // No access path for protected/private. Tough shit the cache is worth it
218- $ context ->access_path = '\\' .$ context ->owner_class .':: ' .$ context ->name ;
195+ $ context = $ this ->buildConstContext ($ cr );
196+ $ context ->depth = $ cdepth ;
197+
198+ $ consts [$ canon_name ] = $ parser ->parse ($ val , $ context );
199+ $ context ->access_path = null ;
219200 }
220201
221- $ consts [ $ cr -> name ] = $ parser -> parse ( $ val , $ context );
202+ unset( $ parent_consts [ $ canon_name ] );
222203 }
223204
224- /** @psalm-var AbstractValue[] $consts */
225- $ this ->cache [$ class ][$ pdepth_enabled ] = \array_values ($ consts );
205+ $ this ->cache [$ class ][$ deepkey ] = $ consts + $ parent_consts ;
206+ }
207+
208+ return $ this ->cache [$ class ][$ deepkey ];
209+ }
210+
211+ private function buildConstContext (ReflectionClassConstant $ cr ): ClassConstContext
212+ {
213+ $ context = new ClassConstContext (
214+ $ cr ->name ,
215+ $ cr ->getDeclaringClass ()->name ,
216+ ClassDeclaredContext::ACCESS_PUBLIC
217+ );
218+ $ context ->final = KINT_PHP81 && $ cr ->isFinal ();
219+
220+ if ($ cr ->isProtected ()) {
221+ $ context ->access = ClassDeclaredContext::ACCESS_PROTECTED ;
222+ } elseif ($ cr ->isPrivate ()) {
223+ $ context ->access = ClassDeclaredContext::ACCESS_PRIVATE ;
224+ } else {
225+ $ context ->access_path = '\\' .$ context ->owner_class .':: ' .$ context ->name ;
226226 }
227227
228- return $ this -> cache [ $ class ][ $ pdepth_enabled ] ;
228+ return $ context ;
229229 }
230230}
0 commit comments