30
30
use Kint \Value \AbstractValue ;
31
31
use Kint \Value \Context \ClassConstContext ;
32
32
use Kint \Value \Context \ClassDeclaredContext ;
33
- use Kint \Value \Context \ClassOwnedContext ;
34
33
use Kint \Value \Context \StaticPropertyContext ;
35
34
use Kint \Value \InstanceValue ;
36
35
use Kint \Value \Representation \ContainerRepresentation ;
42
41
43
42
class ClassStaticsPlugin extends AbstractPlugin implements PluginCompleteInterface
44
43
{
45
- /** @psalm-var array<class-string, array<1|0, list <AbstractValue>>> */
44
+ /** @psalm-var array<class-string, array<1|0, array <AbstractValue>>> */
46
45
private array $ cache = [];
47
46
48
47
public function getTypes (): array
@@ -69,162 +68,163 @@ public function parseComplete(&$var, AbstractValue $v, int $trigger): AbstractVa
69
68
return $ v ;
70
69
}
71
70
72
- $ class = $ v ->getClassName ();
73
- $ parser = $ this ->getParser ();
74
- $ r = new ReflectionClass ($ class );
71
+ $ deep = 0 === $ this ->getParser ()->getDepthLimit ();
75
72
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 ());
82
74
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 ' ));
91
77
}
92
78
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
+ }
95
82
96
- $ cdepth = $ v ->getContext ()->getDepth ();
83
+ return $ v ;
84
+ }
97
85
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 ();
106
92
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 = [];
112
95
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 );
116
98
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 ]);
119
104
} 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
125
107
}
108
+ }
126
109
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
+ }
130
113
131
- $ static ->setAccessible (true );
114
+ return $ statics ;
115
+ }
132
116
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 ;
143
131
}
144
132
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 ;
147
137
}
148
138
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 );
151
149
}
152
150
153
- return $ v ;
151
+ $ val = $ pr ->getValue ();
152
+
153
+ $ out = $ this ->getParser ()->parse ($ val , $ context );
154
+ $ context ->access_path = null ;
155
+
156
+ return $ out ;
154
157
}
155
158
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
158
161
{
159
162
$ parser = $ this ->getParser ();
160
- $ pdepth = $ parser ->getDepthLimit ();
161
- $ pdepth_enabled = (int ) ( $ pdepth > 0 ) ;
163
+ $ cdepth = $ parser ->getDepthLimit () ?: 1 ;
164
+ $ deepkey = (int ) $ deep ;
162
165
$ class = $ r ->getName ();
163
166
164
167
// Separate cache for dumping with/without depth limit
165
168
// 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 ])) {
167
170
$ consts = [];
168
- $ reflectors = [];
169
171
172
+ $ parent_consts = [];
173
+ if ($ parent = $ r ->getParentClass ()) {
174
+ $ parent_consts = $ this ->getCachedConstants ($ parent , $ deep );
175
+ }
170
176
foreach ($ r ->getConstants () as $ name => $ val ) {
171
177
$ cr = new ReflectionClassConstant ($ class , $ name );
172
178
173
179
// 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 )) {
175
181
continue ;
176
182
}
177
183
178
- $ reflectors [$ cr ->name ] = [$ cr , $ val ];
179
- $ consts [$ cr ->name ] = null ;
180
- }
184
+ $ canon_name = \strtolower ($ cr ->getDeclaringClass ()->name .':: ' .$ name );
181
185
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 ;
202
189
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 ];
216
194
} 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 ;
219
200
}
220
201
221
- $ consts [ $ cr -> name ] = $ parser -> parse ( $ val , $ context );
202
+ unset( $ parent_consts [ $ canon_name ] );
222
203
}
223
204
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 ;
226
226
}
227
227
228
- return $ this -> cache [ $ class ][ $ pdepth_enabled ] ;
228
+ return $ context ;
229
229
}
230
230
}
0 commit comments