Skip to content

Commit 63ae183

Browse files
committed
ClassStaticsPlugin: Rewrite without access paths on top level
1 parent e2f95af commit 63ae183

13 files changed

+339
-156
lines changed

build/kint.phar

-110 Bytes
Binary file not shown.

psalm-baseline.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
</file>
2828
<file src="src/Parser/ClassStaticsPlugin.php">
2929
<RedundantCondition>
30-
<code><![CDATA[KINT_PHP84]]></code>
3130
<code><![CDATA[KINT_PHP81]]></code>
31+
<code><![CDATA[KINT_PHP84]]></code>
3232
</RedundantCondition>
3333
</file>
3434
<file src="src/Parser/DomPlugin.php">

src/Parser/ClassStaticsPlugin.php

Lines changed: 113 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
use Kint\Value\AbstractValue;
3131
use Kint\Value\Context\ClassConstContext;
3232
use Kint\Value\Context\ClassDeclaredContext;
33-
use Kint\Value\Context\ClassOwnedContext;
3433
use Kint\Value\Context\StaticPropertyContext;
3534
use Kint\Value\InstanceValue;
3635
use Kint\Value\Representation\ContainerRepresentation;
@@ -42,7 +41,7 @@
4241

4342
class 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
}

src/Value/Context/ClassConstContext.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ class ClassConstContext extends ClassDeclaredContext
3131
{
3232
public bool $final = false;
3333

34+
public function getName(): string
35+
{
36+
return $this->owner_class.'::'.$this->name;
37+
}
38+
3439
public function getOperator(): string
3540
{
3641
return '::';

src/Value/Context/StaticPropertyContext.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ class StaticPropertyContext extends DoubleAccessMemberContext
3131
{
3232
public bool $final = false;
3333

34+
public function getName(): string
35+
{
36+
return $this->owner_class.'::$'.$this->name;
37+
}
38+
3439
public function getOperator(): string
3540
{
3641
return '::';

tests/Fixtures/Php74ChildTestClass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Php74ChildTestClass extends Php74TestClass
88
public const VALUE_5 = 5;
99
private const VALUE_3 = 'replaced';
1010
private const VALUE_6 = 6;
11+
public const VALUE_ARRAY_2 = ['contents' => '{"test":"value"}'];
1112

1213
public static $value_1 = 'replaced';
1314
public static $value_5 = 5;

tests/Fixtures/Php74TestClass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ class Php74TestClass
1515
protected static int $value_uninit;
1616
private static $value_3 = 3;
1717
private static $value_4 = 4;
18+
public static $value_a_pub = ['contents' => '{"test":"value"}'];
19+
protected static $value_a_pro = ['contents' => '{"test":"value"}'];
20+
private static $value_a_pri = ['contents' => '{"test":"value"}'];
1821

1922
public $a = 1;
2023
public string $b = '2';

tests/Fixtures/Php81TestClass.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
class Php81TestClass
99
{
10+
final public const X = 'Y';
11+
1012
public readonly string $a;
1113
protected readonly string $b;
1214
private readonly string $c;

tests/Fixtures/Php84TestClass.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,6 @@ class Php84TestClass
4444
private(set) int $i;
4545
protected(set) int $j;
4646
protected private(set) int $k;
47+
48+
final protected static int $l;
4749
}

tests/Fixtures/TestInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
interface TestInterface
66
{
7+
const VALUE = 'abcd';
8+
79
public function normalMethod();
810
public static function staticMethod();
911
}

0 commit comments

Comments
 (0)