parent
161f1e5064
commit
f90469ba63
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace jrosset\DebugInfo;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* Mark a class with debug metadata
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
class DebugClass {
|
||||
/**
|
||||
* @var DebugClassMinimalScope The minimal scope level
|
||||
*/
|
||||
public readonly DebugClassMinimalScope $minimalScope;
|
||||
/**
|
||||
* @var bool Also include inherited properties and methods ?
|
||||
*/
|
||||
public readonly bool $inherited;
|
||||
|
||||
/**
|
||||
* @var string|null The prefix to properties/methods name ; Null if default one
|
||||
*/
|
||||
public readonly ?string $namePrefix;
|
||||
|
||||
/**
|
||||
* Initialization
|
||||
*
|
||||
* @param DebugClassMinimalScope $minimalScope The minimal scope level
|
||||
* @param bool $inherited True if inherited (parent class) properties and methods are also included, else False
|
||||
*/
|
||||
public function __construct (DebugClassMinimalScope $minimalScope = DebugClassMinimalScope::Private, bool $inherited = true, ?string $namePrefix = null) {
|
||||
$this->minimalScope = $minimalScope;
|
||||
$this->inherited = $inherited;
|
||||
$this->namePrefix = $namePrefix;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace jrosset\DebugInfo;
|
||||
|
||||
use ReflectionAttribute;
|
||||
use ReflectionProperty;
|
||||
|
||||
/**
|
||||
* A minimal scope level for class debug
|
||||
*/
|
||||
enum DebugClassMinimalScope {
|
||||
/**
|
||||
* Properties and methods with {@see DebugProperty} attribute
|
||||
*/
|
||||
case Attribute;
|
||||
/**
|
||||
* Public properties and methods
|
||||
*/
|
||||
case Public;
|
||||
/**
|
||||
* Protected properties and methods
|
||||
*/
|
||||
case Protected;
|
||||
/**
|
||||
* Private properties and methods
|
||||
*/
|
||||
case Private;
|
||||
|
||||
/**
|
||||
* Is a property valid for debug ?
|
||||
*
|
||||
* @param ReflectionProperty $reflectionProperty The property's reflection
|
||||
*
|
||||
* @return bool True if the property is valid for debug, else False
|
||||
*/
|
||||
public function isValidReflectionProperty (ReflectionProperty $reflectionProperty): bool {
|
||||
if (count($reflectionProperty->getAttributes(DebugIgnoredProperty::class, ReflectionAttribute::IS_INSTANCEOF)) > 0) {
|
||||
return false;
|
||||
}
|
||||
if (count($reflectionProperty->getAttributes(DebugProperty::class, ReflectionAttribute::IS_INSTANCEOF)) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return match ($this) {
|
||||
self::Private => $reflectionProperty->isPrivate() || $reflectionProperty->isProtected() || $reflectionProperty->isPublic(),
|
||||
self::Protected => $reflectionProperty->isProtected() || $reflectionProperty->isPublic(),
|
||||
self::Public => $reflectionProperty->isPublic(),
|
||||
default => false
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace jrosset\DebugInfo;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* Mark a class property to NOT debug
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
class DebugIgnoredProperty {
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace jrosset\DebugInfo;
|
||||
|
||||
use ReflectionAttribute;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* A class for working around debug information
|
||||
*/
|
||||
abstract class DebugInfo {
|
||||
/**
|
||||
* Collect debug information about an object
|
||||
*
|
||||
* @param object $object The object
|
||||
*
|
||||
* @return array The debug information of the object
|
||||
*
|
||||
* @see https://www.php.net/manual/language.oop5.magic.php#object.debuginfo __debugInfo
|
||||
*/
|
||||
public static function collect (object $object): array {
|
||||
return static::collectThroughReflection($object, new ReflectionClass($object));
|
||||
}
|
||||
/**
|
||||
* Collect debug information about an object through a {@see ReflectionClass}
|
||||
*
|
||||
* @param object $object The object
|
||||
* @param ReflectionClass $objectReflection The object reflection class
|
||||
*
|
||||
* @return array The debug information of the object
|
||||
*
|
||||
* @see https://www.php.net/manual/language.oop5.magic.php#object.debuginfo __debugInfo
|
||||
*/
|
||||
private static function collectThroughReflection (object $object, ReflectionClass $objectReflection): array {
|
||||
$debugInformation = [];
|
||||
|
||||
$objectDebugClassAttributeReflection = $objectReflection->getAttributes(DebugClass::class, ReflectionAttribute::IS_INSTANCEOF)[0] ?? null;
|
||||
$objectDebugClassAttribute = $objectDebugClassAttributeReflection === null ? new DebugClass() : $objectDebugClassAttributeReflection->newInstance();
|
||||
$namePrefix = $objectDebugClassAttribute->namePrefix ?? $objectReflection->getName();
|
||||
|
||||
foreach ($objectReflection->getProperties() as $propertyReflection) {
|
||||
if ($propertyReflection->getDeclaringClass()->getName() !== $objectReflection->getName()) {
|
||||
continue;
|
||||
}
|
||||
if (!$objectDebugClassAttribute->minimalScope->isValidReflectionProperty($propertyReflection)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$propertyDebugAttributeReflection = $propertyReflection->getAttributes(DebugProperty::class, ReflectionAttribute::IS_INSTANCEOF)[0] ?? null;
|
||||
$propertyDebugAttribute = $propertyDebugAttributeReflection === null ? new DebugProperty() : $propertyDebugAttributeReflection->newInstance();
|
||||
$propertyName = $propertyDebugAttribute->name ?? $propertyReflection->getName();
|
||||
|
||||
$debugInformation[$namePrefix . ':'
|
||||
. $propertyName . ':'
|
||||
. match (true) {
|
||||
$propertyReflection->isPrivate() => 'private',
|
||||
$propertyReflection->isProtected() => 'protected',
|
||||
$propertyReflection->isPublic() => 'public',
|
||||
}
|
||||
. ($propertyReflection->isStatic() ? ',static' : '')] = $propertyReflection->isInitialized($object)
|
||||
? $propertyReflection->getValue($object)
|
||||
: new UninitializedValue((string)$propertyReflection->getType());
|
||||
}
|
||||
|
||||
if ($objectDebugClassAttribute->inherited && ($parentReflection = $objectReflection->getParentClass()) !== false) {
|
||||
try {
|
||||
$parentDebugInformation = $parentReflection->hasMethod('__debugInfo')
|
||||
? $parentReflection->getMethod('__debugInfo')->invoke($object)
|
||||
: static::collectThroughReflection($object, $parentReflection);
|
||||
}
|
||||
catch (ReflectionException) {
|
||||
$parentDebugInformation = static::collectThroughReflection($object, $parentReflection);
|
||||
}
|
||||
|
||||
$debugInformation += $parentDebugInformation;
|
||||
}
|
||||
|
||||
return $debugInformation;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace jrosset\DebugInfo;
|
||||
|
||||
use Attribute;
|
||||
|
||||
/**
|
||||
* Mark a class property or method to debug
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD)]
|
||||
class DebugProperty {
|
||||
/**
|
||||
* @var string|null The property/method name ; Null if default one
|
||||
*/
|
||||
public readonly ?string $name;
|
||||
|
||||
/**
|
||||
* Initialization
|
||||
*
|
||||
* @param string|null $name The property/method name ; Null if default one
|
||||
*/
|
||||
public function __construct (?string $name = null) {
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace jrosset\DebugInfo;
|
||||
|
||||
/**
|
||||
* Implements {@see https://www.php.net/manual/language.oop5.magic.php#object.debuginfo __debugInfo} using {@see DebugInfo::collect()}
|
||||
*/
|
||||
trait TDebugInfoCollector {
|
||||
/**
|
||||
* Get debug information, using {@see DebugInfo::collect()}
|
||||
*
|
||||
* @return array The debug information
|
||||
*/
|
||||
public function __debugInfo (): array {
|
||||
return DebugInfo::collect($this);
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace jrosset\DebugInfo;
|
||||
|
||||
/**
|
||||
* Indicate an uninitialized value
|
||||
*/
|
||||
final class UninitializedValue {
|
||||
/**
|
||||
* @var string The value's type
|
||||
*/
|
||||
public readonly string $type;
|
||||
|
||||
/**
|
||||
* @param string $type The value's type
|
||||
*/
|
||||
public function __construct (string $type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/** @noinspection PhpIllegalPsrClassPathInspection */
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use jrosset\DebugInfo\DebugClass;
|
||||
use jrosset\DebugInfo\DebugClassMinimalScope;
|
||||
use jrosset\DebugInfo\DebugIgnoredProperty;
|
||||
use jrosset\DebugInfo\DebugInfo;
|
||||
use jrosset\DebugInfo\DebugProperty;
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
#[DebugClass(minimalScope: DebugClassMinimalScope::Protected, inherited: false)]
|
||||
class ClassNoParentDebug extends DateTimeImmutable {
|
||||
private bool $debug = true;
|
||||
|
||||
#[DebugProperty]
|
||||
private float $price = 0.17;
|
||||
|
||||
protected string $foo = 'bar';
|
||||
|
||||
#[DebugIgnoredProperty]
|
||||
protected string $foo2 = 'bar2';
|
||||
}
|
||||
|
||||
#[DebugClass(namePrefix: 'CustomPrefix')]
|
||||
class ClassWithCustomPrefix extends ClassNoParentDebug {
|
||||
private static int $quantity = 42;
|
||||
public readonly string|int $compoundVar;
|
||||
}
|
||||
|
||||
var_dump(DebugInfo::collect(new ClassWithCustomPrefix()));
|
Loading…
Reference in New Issue