Project code

master 1.0.0
Julien Rosset 1 year ago
parent 161f1e5064
commit f90469ba63

@ -15,7 +15,8 @@
"minimum-stability": "stable",
"require": {
"php": "^8.0"
"php": "^8.1",
"ext-reflection": "*"
},
"autoload": {
"psr-4": {

@ -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…
Cancel
Save