Skip to content

Commit

Permalink
ClassStaticsPlugin: Rewrite without access paths on top level
Browse files Browse the repository at this point in the history
  • Loading branch information
jnvsor committed Dec 21, 2024
1 parent e2f95af commit 63ae183
Show file tree
Hide file tree
Showing 13 changed files with 339 additions and 156 deletions.
Binary file modified build/kint.phar
Binary file not shown.
2 changes: 1 addition & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
</file>
<file src="src/Parser/ClassStaticsPlugin.php">
<RedundantCondition>
<code><![CDATA[KINT_PHP84]]></code>
<code><![CDATA[KINT_PHP81]]></code>
<code><![CDATA[KINT_PHP84]]></code>
</RedundantCondition>
</file>
<file src="src/Parser/DomPlugin.php">
Expand Down
226 changes: 113 additions & 113 deletions src/Parser/ClassStaticsPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
use Kint\Value\AbstractValue;
use Kint\Value\Context\ClassConstContext;
use Kint\Value\Context\ClassDeclaredContext;
use Kint\Value\Context\ClassOwnedContext;
use Kint\Value\Context\StaticPropertyContext;
use Kint\Value\InstanceValue;
use Kint\Value\Representation\ContainerRepresentation;
Expand All @@ -42,7 +41,7 @@

class ClassStaticsPlugin extends AbstractPlugin implements PluginCompleteInterface
{
/** @psalm-var array<class-string, array<1|0, list<AbstractValue>>> */
/** @psalm-var array<class-string, array<1|0, array<AbstractValue>>> */
private array $cache = [];

public function getTypes(): array
Expand All @@ -69,162 +68,163 @@ public function parseComplete(&$var, AbstractValue $v, int $trigger): AbstractVa
return $v;
}

$class = $v->getClassName();
$parser = $this->getParser();
$r = new ReflectionClass($class);
$deep = 0 === $this->getParser()->getDepthLimit();

$statics_full_name = false;
$statics = [];
$props = $r->getProperties(ReflectionProperty::IS_STATIC);
foreach ($props as $prop) {
$statics[$prop->name] = $prop;
}
$r = new ReflectionClass($v->getClassName());

$parent = $r;
while ($parent = $parent->getParentClass()) {
foreach ($parent->getProperties(ReflectionProperty::IS_STATIC) as $static) {
if (isset($statics[$static->name]) && $statics[$static->name]->getDeclaringClass()->name === $static->getDeclaringClass()->name) {
continue;
}
$statics[] = $static;
}
if ($statics = $this->getStatics($r, $v->getContext()->getDepth() + 1)) {
$v->addRepresentation(new ContainerRepresentation('Static properties', \array_values($statics), 'statics'));
}

$statics_parsed = [];
$found_statics = [];
if ($consts = $this->getCachedConstants($r, $deep)) {
$v->addRepresentation(new ContainerRepresentation('Class constants', \array_values($consts), 'constants'));
}

$cdepth = $v->getContext()->getDepth();
return $v;
}

foreach ($statics as $static) {
$prop = new StaticPropertyContext(
'$'.$static->getName(),
$static->getDeclaringClass()->name,
ClassDeclaredContext::ACCESS_PUBLIC
);
$prop->depth = $cdepth + 1;
$prop->final = KINT_PHP84 && $static->isFinal();
/** @psalm-return array<AbstractValue> */
private function getStatics(ReflectionClass $r, int $depth): array
{
$cdepth = $depth ?: 1;
$class = $r->getName();
$parent = $r->getParentClass();

if ($static->isProtected()) {
$prop->access = ClassDeclaredContext::ACCESS_PROTECTED;
} elseif ($static->isPrivate()) {
$prop->access = ClassDeclaredContext::ACCESS_PRIVATE;
}
$parent_statics = $parent ? $this->getStatics($parent, $depth) : [];
$statics = [];

if ($prop->isAccessible($parser->getCallerClass())) {
$prop->access_path = '\\'.$prop->owner_class.'::'.$prop->name;
}
foreach ($r->getProperties(ReflectionProperty::IS_STATIC) as $pr) {
$canon_name = \strtolower($pr->getDeclaringClass()->name.'::'.$pr->name);

if (isset($found_statics[$prop->name])) {
$statics_full_name = true;
if ($pr->getDeclaringClass()->name === $class) {
$statics[$canon_name] = $this->buildStaticValue($pr, $cdepth);
} elseif (isset($parent_statics[$canon_name])) {
$statics[$canon_name] = $parent_statics[$canon_name];
unset($parent_statics[$canon_name]);
} else {
$found_statics[$prop->name] = true;

if ($prop->owner_class !== $class && ClassDeclaredContext::ACCESS_PRIVATE === $prop->access) {
$statics_full_name = true;
}
// This should never happen since abstract static properties can't exist
$statics[$canon_name] = $this->buildStaticValue($pr, $cdepth); // @codeCoverageIgnore
}
}

if ($statics_full_name) {
$prop->name = $prop->owner_class.'::'.$prop->name;
}
foreach ($parent_statics as $canon_name => $value) {
$statics[$canon_name] = $value;
}

$static->setAccessible(true);
return $statics;
}

/**
* @psalm-suppress TooFewArguments
* Appears to have been fixed in master
*/
if (!$static->isInitialized()) {
$statics_parsed[] = new UninitializedValue($prop);
} else {
$static = $static->getValue();
$statics_parsed[] = $parser->parse($static, $prop);
}
private function buildStaticValue(ReflectionProperty $pr, int $depth): AbstractValue
{
$context = new StaticPropertyContext(
$pr->name,
$pr->getDeclaringClass()->name,
ClassDeclaredContext::ACCESS_PUBLIC
);
$context->depth = $depth;
$context->final = KINT_PHP84 && $pr->isFinal();

if ($pr->isProtected()) {
$context->access = ClassDeclaredContext::ACCESS_PROTECTED;
} elseif ($pr->isPrivate()) {
$context->access = ClassDeclaredContext::ACCESS_PRIVATE;
}

if ($statics_parsed) {
$v->addRepresentation(new ContainerRepresentation('Static properties', $statics_parsed, 'statics'));
$parser = $this->getParser();

if ($context->isAccessible($parser->getCallerClass())) {
$context->access_path = '\\'.$context->owner_class.'::$'.$context->name;
}

if ($consts = $this->getCachedConstants($r)) {
$v->addRepresentation(new ContainerRepresentation('Class constants', $consts, 'constants'));
$pr->setAccessible(true);

/**
* @psalm-suppress TooFewArguments
* Appears to have been fixed in master.
*/
if (!$pr->isInitialized()) {
$context->access_path = null;

return new UninitializedValue($context);
}

return $v;
$val = $pr->getValue();

$out = $this->getParser()->parse($val, $context);
$context->access_path = null;

return $out;
}

/** @psalm-return list<AbstractValue> */
private function getCachedConstants(ReflectionClass $r): array
/** @psalm-return array<AbstractValue> */
private function getCachedConstants(ReflectionClass $r, bool $deep): array
{
$parser = $this->getParser();
$pdepth = $parser->getDepthLimit();
$pdepth_enabled = (int) ($pdepth > 0);
$cdepth = $parser->getDepthLimit() ?: 1;
$deepkey = (int) $deep;
$class = $r->getName();

// Separate cache for dumping with/without depth limit
// This means we can do immediate depth limit on normal dumps
if (!isset($this->cache[$class][$pdepth_enabled])) {
if (!isset($this->cache[$class][$deepkey])) {
$consts = [];
$reflectors = [];

$parent_consts = [];
if ($parent = $r->getParentClass()) {
$parent_consts = $this->getCachedConstants($parent, $deep);
}
foreach ($r->getConstants() as $name => $val) {
$cr = new ReflectionClassConstant($class, $name);

// Skip enum constants
if (\is_a($cr->class, UnitEnum::class, true) && $val instanceof UnitEnum && $cr->class === \get_class($val)) {
if ($cr->class === $class && \is_a($class, UnitEnum::class, true)) {
continue;
}

$reflectors[$cr->name] = [$cr, $val];
$consts[$cr->name] = null;
}
$canon_name = \strtolower($cr->getDeclaringClass()->name.'::'.$name);

if ($r = $r->getParentClass()) {
$parents = $this->getCachedConstants($r);

foreach ($parents as $value) {
$c = $value->getContext();
$cname = $c->getName();

if (isset($reflectors[$cname]) && $c instanceof ClassOwnedContext && $reflectors[$cname][0]->getDeclaringClass()->name === $c->owner_class) {
$consts[$cname] = $value;
unset($reflectors[$cname]);
} else {
$value = clone $value;
$c = $value->getContext();
if ($c instanceof ClassOwnedContext) {
$c->name = $c->owner_class.'::'.$cname;
}
$consts[] = $value;
}
}
}
if ($cr->getDeclaringClass()->name === $class) {
$context = $this->buildConstContext($cr);
$context->depth = $cdepth;

foreach ($reflectors as [$cr, $val]) {
$context = new ClassConstContext(
$cr->name,
$cr->getDeclaringClass()->name,
ClassDeclaredContext::ACCESS_PUBLIC
);
$context->depth = $pdepth ?: 1;
$context->final = KINT_PHP81 && $cr->isFinal();

if ($cr->isProtected()) {
$context->access = ClassDeclaredContext::ACCESS_PROTECTED;
} elseif ($cr->isPrivate()) {
$context->access = ClassDeclaredContext::ACCESS_PRIVATE;
$consts[$canon_name] = $parser->parse($val, $context);
$context->access_path = null;
} elseif (isset($parent_consts[$canon_name])) {
$consts[$canon_name] = $parent_consts[$canon_name];
} else {
// No access path for protected/private. Tough shit the cache is worth it
$context->access_path = '\\'.$context->owner_class.'::'.$context->name;
$context = $this->buildConstContext($cr);
$context->depth = $cdepth;

$consts[$canon_name] = $parser->parse($val, $context);
$context->access_path = null;
}

$consts[$cr->name] = $parser->parse($val, $context);
unset($parent_consts[$canon_name]);
}

/** @psalm-var AbstractValue[] $consts */
$this->cache[$class][$pdepth_enabled] = \array_values($consts);
$this->cache[$class][$deepkey] = $consts + $parent_consts;
}

return $this->cache[$class][$deepkey];
}

private function buildConstContext(ReflectionClassConstant $cr): ClassConstContext
{
$context = new ClassConstContext(
$cr->name,
$cr->getDeclaringClass()->name,
ClassDeclaredContext::ACCESS_PUBLIC
);
$context->final = KINT_PHP81 && $cr->isFinal();

if ($cr->isProtected()) {
$context->access = ClassDeclaredContext::ACCESS_PROTECTED;
} elseif ($cr->isPrivate()) {
$context->access = ClassDeclaredContext::ACCESS_PRIVATE;
} else {
$context->access_path = '\\'.$context->owner_class.'::'.$context->name;
}

return $this->cache[$class][$pdepth_enabled];
return $context;
}
}
5 changes: 5 additions & 0 deletions src/Value/Context/ClassConstContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class ClassConstContext extends ClassDeclaredContext
{
public bool $final = false;

public function getName(): string
{
return $this->owner_class.'::'.$this->name;
}

public function getOperator(): string
{
return '::';
Expand Down
5 changes: 5 additions & 0 deletions src/Value/Context/StaticPropertyContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class StaticPropertyContext extends DoubleAccessMemberContext
{
public bool $final = false;

public function getName(): string
{
return $this->owner_class.'::$'.$this->name;
}

public function getOperator(): string
{
return '::';
Expand Down
1 change: 1 addition & 0 deletions tests/Fixtures/Php74ChildTestClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Php74ChildTestClass extends Php74TestClass
public const VALUE_5 = 5;
private const VALUE_3 = 'replaced';
private const VALUE_6 = 6;
public const VALUE_ARRAY_2 = ['contents' => '{"test":"value"}'];

public static $value_1 = 'replaced';
public static $value_5 = 5;
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/Php74TestClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Php74TestClass
protected static int $value_uninit;
private static $value_3 = 3;
private static $value_4 = 4;
public static $value_a_pub = ['contents' => '{"test":"value"}'];
protected static $value_a_pro = ['contents' => '{"test":"value"}'];
private static $value_a_pri = ['contents' => '{"test":"value"}'];

public $a = 1;
public string $b = '2';
Expand Down
2 changes: 2 additions & 0 deletions tests/Fixtures/Php81TestClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

class Php81TestClass
{
final public const X = 'Y';

public readonly string $a;
protected readonly string $b;
private readonly string $c;
Expand Down
2 changes: 2 additions & 0 deletions tests/Fixtures/Php84TestClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ class Php84TestClass
private(set) int $i;
protected(set) int $j;
protected private(set) int $k;

final protected static int $l;
}
2 changes: 2 additions & 0 deletions tests/Fixtures/TestInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

interface TestInterface
{
const VALUE = 'abcd';

public function normalMethod();
public static function staticMethod();
}
Loading

0 comments on commit 63ae183

Please sign in to comment.