Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
@@ -4,171 +4,492 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Reflection;
|
||||
|
||||
use App\Framework\Core\ValueObjects\ClassName;
|
||||
use App\Framework\Core\ValueObjects\Statistics;
|
||||
use App\Framework\Reflection\Collections\AttributeCollection;
|
||||
use App\Framework\Reflection\Collections\MethodCollection;
|
||||
use App\Framework\Reflection\Collections\ParameterCollection;
|
||||
use App\Framework\Reflection\Collections\PropertyCollection;
|
||||
use App\Framework\Reflection\Factory\ReflectionCacheFactory;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
use ReflectionException;
|
||||
use ReflectionType;
|
||||
|
||||
final class CachedReflectionProvider implements ReflectionProvider
|
||||
final readonly class CachedReflectionProvider implements ReflectionProvider
|
||||
{
|
||||
private array $classCache = [];
|
||||
private array $methodCache = [];
|
||||
private array $parameterCache = [];
|
||||
private array $propertyCache = [];
|
||||
private array $attributeCache = [];
|
||||
private array $parameterInfoCache = [];
|
||||
private array $instantiableCache = [];
|
||||
private array $interfaceCache = [];
|
||||
private array $wrappedClassCache = [];
|
||||
private array $wrappedMethodCache = [];
|
||||
private ReflectionCache $cache;
|
||||
|
||||
public function getWrappedClass(string $className): WrappedReflectionClass
|
||||
{
|
||||
return $this->wrappedClassCache[$className] ??= new WrappedReflectionClass(
|
||||
$this->getClass($className)
|
||||
);
|
||||
/**
|
||||
* @param ReflectionCache|null $cache The reflection cache to use, or null to create one
|
||||
* @param ReflectionCacheFactory|null $factory The factory to use for creating a cache if none is provided
|
||||
* @param string|null $environment The environment to use for cache configuration ('production' or 'development')
|
||||
*/
|
||||
public function __construct(
|
||||
?ReflectionCache $cache = null,
|
||||
?ReflectionCacheFactory $factory = null,
|
||||
?string $environment = null
|
||||
) {
|
||||
if ($cache === null) {
|
||||
$factory ??= new ReflectionCacheFactory();
|
||||
$env = $environment ?? 'production';
|
||||
|
||||
// Use optimized cache in production, regular cache in development
|
||||
$this->cache = $env === 'development'
|
||||
? $factory->createForDevelopment()
|
||||
: $factory->createOptimized();
|
||||
} else {
|
||||
$this->cache = $cache;
|
||||
}
|
||||
}
|
||||
|
||||
public function getWrappedMethod(string $className, string $methodName): WrappedReflectionMethod
|
||||
// ClassReflector implementation
|
||||
public function getClass(ClassName $className): WrappedReflectionClass
|
||||
{
|
||||
$key = "{$className}::{$methodName}";
|
||||
return $this->wrappedMethodCache[$key] ??= new WrappedReflectionMethod(
|
||||
$this->getMethod($className, $methodName)
|
||||
);
|
||||
return $this->cache->classCache->getClass($className);
|
||||
}
|
||||
|
||||
public function getClass(string $className): ReflectionClass
|
||||
public function getNativeClass(ClassName $className): ReflectionClass
|
||||
{
|
||||
return $this->classCache[$className] ??= new ReflectionClass($className);
|
||||
return $this->cache->classCache->getNativeClass($className);
|
||||
}
|
||||
|
||||
public function getMethod(string $className, string $methodName): ReflectionMethod
|
||||
public function getProperties(ClassName $className): PropertyCollection
|
||||
{
|
||||
$key = "{$className}::{$methodName}";
|
||||
return $this->methodCache[$key] ??= $this->getClass($className)->getMethod($methodName);
|
||||
return $this->cache->classCache->getProperties($className);
|
||||
}
|
||||
|
||||
public function getMethodParameters(string $className, string $methodName): array
|
||||
public function getMethods(ClassName $className, ?int $filter = null): MethodCollection
|
||||
{
|
||||
$key = "{$className}::{$methodName}::params";
|
||||
return $this->parameterCache[$key] ??= $this->getMethod($className, $methodName)->getParameters();
|
||||
return $this->cache->classCache->getMethods($className, $filter);
|
||||
}
|
||||
|
||||
public function getProperties(string $className): array
|
||||
public function getAttributes(ClassName $className, ?string $attributeClass = null): AttributeCollection
|
||||
{
|
||||
$key = "{$className}::properties";
|
||||
return $this->propertyCache[$key] ??= $this->getClass($className)->getProperties();
|
||||
return $this->cache->classCache->getAttributes($className, $attributeClass);
|
||||
}
|
||||
|
||||
public function getAttributes(string $className, ?string $attributeClass = null): array
|
||||
public function hasAttribute(ClassName $className, string $attributeClass): bool
|
||||
{
|
||||
$key = "{$className}::attributes::" . ($attributeClass ?? 'all');
|
||||
|
||||
return $this->attributeCache[$key] ??= $attributeClass
|
||||
? $this->getClass($className)->getAttributes($attributeClass)
|
||||
: $this->getClass($className)->getAttributes();
|
||||
return $this->cache->classCache->hasAttribute($className, $attributeClass);
|
||||
}
|
||||
|
||||
public function getMethodAttributes(string $className, string $methodName, ?string $attributeClass = null): array
|
||||
public function isInstantiable(ClassName $className): bool
|
||||
{
|
||||
$key = "{$className}::{$methodName}::attributes::" . ($attributeClass ?? 'all');
|
||||
|
||||
return $this->attributeCache[$key] ??= $attributeClass
|
||||
? $this->getMethod($className, $methodName)->getAttributes($attributeClass)
|
||||
: $this->getMethod($className, $methodName)->getAttributes();
|
||||
return $this->cache->classCache->isInstantiable($className);
|
||||
}
|
||||
|
||||
public function hasAttribute(string $className, string $attributeClass): bool
|
||||
public function implementsInterface(ClassName $className, string $interfaceName): bool
|
||||
{
|
||||
return !empty($this->getAttributes($className, $attributeClass));
|
||||
return $this->cache->classCache->implementsInterface($className, $interfaceName);
|
||||
}
|
||||
|
||||
public function getParameterInfo(string $className, string $methodName): array
|
||||
// MethodReflector implementation
|
||||
public function getMethod(ClassName $className, string $methodName): WrappedReflectionMethod
|
||||
{
|
||||
$key = "{$className}::{$methodName}::paramInfo";
|
||||
|
||||
return $this->parameterInfoCache[$key] ??= $this->buildParameterInfo($className, $methodName);
|
||||
return $this->cache->methodCache->getMethod($className, $methodName);
|
||||
}
|
||||
|
||||
public function isInstantiable(string $className): bool
|
||||
public function getNativeMethod(ClassName $className, string $methodName): ReflectionMethod
|
||||
{
|
||||
return $this->instantiableCache[$className] ??= $this->getClass($className)->isInstantiable();
|
||||
return $this->cache->methodCache->getNativeMethod($className, $methodName);
|
||||
}
|
||||
|
||||
public function implementsInterface(string $className, string $interfaceName): bool
|
||||
public function getMethodParameters(ClassName $className, string $methodName): ParameterCollection
|
||||
{
|
||||
$key = "{$className}::implements::{$interfaceName}";
|
||||
|
||||
return $this->interfaceCache[$key] ??= $this->getClass($className)->implementsInterface($interfaceName);
|
||||
return $this->cache->parameterCache->getParameters($className, $methodName);
|
||||
}
|
||||
|
||||
private function buildParameterInfo(string $className, string $methodName): array
|
||||
public function getMethodAttributes(ClassName $className, string $methodName, ?string $attributeClass = null): AttributeCollection
|
||||
{
|
||||
$parameters = $this->getMethodParameters($className, $methodName);
|
||||
$paramInfo = [];
|
||||
return $this->cache->methodCache->getMethodAttributes($className, $methodName, $attributeClass);
|
||||
}
|
||||
|
||||
foreach ($parameters as $param) {
|
||||
$type = $param->getType();
|
||||
$paramInfo[] = [
|
||||
'name' => $param->getName(),
|
||||
'type' => $type ? $type->getName() : null,
|
||||
'isBuiltin' => $type ? $type->isBuiltin() : true,
|
||||
'allowsNull' => $param->allowsNull(),
|
||||
'isOptional' => $param->isOptional(),
|
||||
'hasDefaultValue' => $param->isDefaultValueAvailable(),
|
||||
'default' => $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null,
|
||||
'position' => $param->getPosition(),
|
||||
];
|
||||
public function getParameterInfo(ClassName $className, string $methodName): array
|
||||
{
|
||||
return $this->cache->parameterCache->getParameterInfo($className, $methodName);
|
||||
}
|
||||
|
||||
// PropertyReflector implementation
|
||||
public function getProperty(ClassName $className, string $propertyName): ReflectionProperty
|
||||
{
|
||||
return $this->getNativeClass($className)->getProperty($propertyName);
|
||||
}
|
||||
|
||||
public function hasProperty(ClassName $className, string $propertyName): bool
|
||||
{
|
||||
return $this->getNativeClass($className)->hasProperty($propertyName);
|
||||
}
|
||||
|
||||
public function getPropertiesWithAttribute(ClassName $className, string $attributeClass): PropertyCollection
|
||||
{
|
||||
$properties = $this->getProperties($className);
|
||||
$result = [];
|
||||
|
||||
foreach ($properties as $property) {
|
||||
if (count($property->getAttributes($attributeClass)) > 0) {
|
||||
$result[] = $property;
|
||||
}
|
||||
}
|
||||
|
||||
return $paramInfo;
|
||||
return new PropertyCollection(...$result);
|
||||
}
|
||||
|
||||
public function forget(string $className): void
|
||||
public function getPropertyAttributes(ClassName $className, string $propertyName, ?string $attributeClass = null): array
|
||||
{
|
||||
unset($this->classCache[$className]);
|
||||
unset($this->instantiableCache[$className]);
|
||||
unset($this->wrappedClassCache[$className]);
|
||||
$property = $this->getProperty($className, $propertyName);
|
||||
|
||||
// Alle zugehörigen Caches leeren
|
||||
$this->clearRelatedCaches($className);
|
||||
return $property->getAttributes($attributeClass);
|
||||
}
|
||||
|
||||
public function getPropertyType(ClassName $className, string $propertyName): ?ReflectionType
|
||||
{
|
||||
$property = $this->getProperty($className, $propertyName);
|
||||
|
||||
return $property->getType();
|
||||
}
|
||||
|
||||
public function getPropertyDefaultValue(ClassName $className, string $propertyName): mixed
|
||||
{
|
||||
$property = $this->getProperty($className, $propertyName);
|
||||
|
||||
if (! $property->hasDefaultValue()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $property->getDefaultValue();
|
||||
}
|
||||
|
||||
// ParameterReflector implementation
|
||||
public function getMethodParameter(ClassName $className, string $methodName, string $parameterName): ReflectionParameter
|
||||
{
|
||||
$method = $this->getNativeMethod($className, $methodName);
|
||||
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
if ($parameter->getName() === $parameterName) {
|
||||
return $parameter;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \ReflectionException("Parameter {$parameterName} does not exist in method {$methodName}");
|
||||
}
|
||||
|
||||
public function hasMethodParameter(ClassName $className, string $methodName, string $parameterName): bool
|
||||
{
|
||||
$method = $this->getNativeMethod($className, $methodName);
|
||||
|
||||
foreach ($method->getParameters() as $parameter) {
|
||||
if ($parameter->getName() === $parameterName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getParameterType(ClassName $className, string $methodName, string $parameterName): ?ReflectionType
|
||||
{
|
||||
$parameter = $this->getMethodParameter($className, $methodName, $parameterName);
|
||||
|
||||
return $parameter->getType();
|
||||
}
|
||||
|
||||
public function getParameterDefaultValue(ClassName $className, string $methodName, string $parameterName): mixed
|
||||
{
|
||||
$parameter = $this->getMethodParameter($className, $methodName, $parameterName);
|
||||
|
||||
if (! $parameter->isDefaultValueAvailable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $parameter->getDefaultValue();
|
||||
}
|
||||
|
||||
public function isParameterOptional(ClassName $className, string $methodName, string $parameterName): bool
|
||||
{
|
||||
$parameter = $this->getMethodParameter($className, $methodName, $parameterName);
|
||||
|
||||
return $parameter->isOptional();
|
||||
}
|
||||
|
||||
public function isParameterVariadic(ClassName $className, string $methodName, string $parameterName): bool
|
||||
{
|
||||
$parameter = $this->getMethodParameter($className, $methodName, $parameterName);
|
||||
|
||||
return $parameter->isVariadic();
|
||||
}
|
||||
|
||||
public function getParameterAttributes(
|
||||
ClassName $className,
|
||||
string $methodName,
|
||||
string $parameterName,
|
||||
?string $attributeClass = null
|
||||
): array {
|
||||
$parameter = $this->getMethodParameter($className, $methodName, $parameterName);
|
||||
|
||||
return $parameter->getAttributes($attributeClass);
|
||||
}
|
||||
|
||||
// AttributeReflector implementation
|
||||
public function hasMethodAttribute(ClassName $className, string $methodName, string $attributeClass): bool
|
||||
{
|
||||
$attributes = $this->getMethodAttributes($className, $methodName, $attributeClass);
|
||||
|
||||
return ! $attributes->isEmpty();
|
||||
}
|
||||
|
||||
public function getAttributeInstances(ClassName $className, ?string $attributeClass = null): array
|
||||
{
|
||||
$attributes = $this->getAttributes($className, $attributeClass);
|
||||
$instances = [];
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
try {
|
||||
$instances[] = $attribute->newInstance();
|
||||
} catch (\Throwable) {
|
||||
// Skip attributes that can't be instantiated
|
||||
}
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
public function getMethodAttributeInstances(ClassName $className, string $methodName, ?string $attributeClass = null): array
|
||||
{
|
||||
$attributes = $this->getMethodAttributes($className, $methodName, $attributeClass);
|
||||
$instances = [];
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
try {
|
||||
$instances[] = $attribute->newInstance();
|
||||
} catch (\Throwable) {
|
||||
// Skip attributes that can't be instantiated
|
||||
}
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
// EnumReflector implementation
|
||||
public function isEnum(ClassName $className): bool
|
||||
{
|
||||
return $this->getNativeClass($className)->isEnum();
|
||||
}
|
||||
|
||||
public function getEnumCases(ClassName $className): array
|
||||
{
|
||||
if (! $this->isEnum($className)) {
|
||||
throw new \ReflectionException("Class {$className->getFullyQualified()} is not an enum");
|
||||
}
|
||||
|
||||
// Use ReflectionEnum instead of ReflectionClass for enum-specific methods
|
||||
/** @var class-string $classNameString */
|
||||
$classNameString = $className->getFullyQualified();
|
||||
$reflectionEnum = new \ReflectionEnum($classNameString);
|
||||
$reflectionCases = $reflectionEnum->getCases();
|
||||
|
||||
$cases = [];
|
||||
foreach ($reflectionCases as $case) {
|
||||
$caseName = $case->getName();
|
||||
// Use constant() function to dynamically access enum case
|
||||
$cases[$caseName] = constant("$classNameString::$caseName");
|
||||
}
|
||||
|
||||
return $cases;
|
||||
}
|
||||
|
||||
public function getEnumCase(ClassName $className, string $caseName): object
|
||||
{
|
||||
if (! $this->isEnum($className)) {
|
||||
throw new \ReflectionException("Class {$className->getFullyQualified()} is not an enum");
|
||||
}
|
||||
|
||||
if (! $this->hasEnumCase($className, $caseName)) {
|
||||
throw new \ReflectionException("Enum case {$caseName} does not exist in enum {$className->getFullyQualified()}");
|
||||
}
|
||||
|
||||
/** @var class-string $classNameString */
|
||||
$classNameString = $className->getFullyQualified();
|
||||
|
||||
// Use constant() function to dynamically access enum case
|
||||
return constant("$classNameString::$caseName");
|
||||
}
|
||||
|
||||
public function hasEnumCase(ClassName $className, string $caseName): bool
|
||||
{
|
||||
if (! $this->isEnum($className)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use ReflectionEnum instead of ReflectionClass for enum-specific methods
|
||||
/** @var class-string $classNameString */
|
||||
$classNameString = $className->getFullyQualified();
|
||||
$reflectionEnum = new \ReflectionEnum($classNameString);
|
||||
|
||||
// ReflectionEnum has a hasCase method we can use directly
|
||||
return $reflectionEnum->hasCase($caseName);
|
||||
}
|
||||
|
||||
public function getEnumCaseAttributes(
|
||||
ClassName $className,
|
||||
string $caseName,
|
||||
?string $attributeClass = null
|
||||
): array {
|
||||
if (! $this->isEnum($className)) {
|
||||
throw new \ReflectionException("Class {$className->getFullyQualified()} is not an enum");
|
||||
}
|
||||
|
||||
// Use ReflectionEnum instead of ReflectionClass for enum-specific methods
|
||||
/** @var class-string $classNameString */
|
||||
$classNameString = $className->getFullyQualified();
|
||||
$reflectionEnum = new \ReflectionEnum($classNameString);
|
||||
|
||||
// Check if the case exists
|
||||
if (! $reflectionEnum->hasCase($caseName)) {
|
||||
throw new \ReflectionException("Enum case {$caseName} does not exist in enum {$className->getFullyQualified()}");
|
||||
}
|
||||
|
||||
// Get the case directly using getCase method
|
||||
$case = $reflectionEnum->getCase($caseName);
|
||||
|
||||
return $case->getAttributes($attributeClass);
|
||||
}
|
||||
|
||||
public function isBackedEnum(ClassName $className): bool
|
||||
{
|
||||
if (! $this->isEnum($className)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use ReflectionEnum instead of ReflectionClass for enum-specific methods
|
||||
/** @var class-string $classNameString */
|
||||
$classNameString = $className->getFullyQualified();
|
||||
$reflectionEnum = new \ReflectionEnum($classNameString);
|
||||
|
||||
return $reflectionEnum->isBacked();
|
||||
}
|
||||
|
||||
public function getEnumBackingType(ClassName $className): ?string
|
||||
{
|
||||
if (! $this->isBackedEnum($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use ReflectionEnum instead of ReflectionClass for enum-specific methods
|
||||
/** @var class-string $classNameString */
|
||||
$classNameString = $className->getFullyQualified();
|
||||
$reflectionEnum = new \ReflectionEnum($classNameString);
|
||||
$backingType = $reflectionEnum->getBackingType();
|
||||
|
||||
return $backingType ? $backingType->getName() : null;
|
||||
}
|
||||
|
||||
public function getEnumCaseBackingValue(ClassName $className, string $caseName): int|string|null
|
||||
{
|
||||
if (! $this->isBackedEnum($className)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$case = $this->getEnumCase($className, $caseName);
|
||||
|
||||
// For backed enums, the value property contains the backing value
|
||||
return $case->value;
|
||||
}
|
||||
|
||||
// InstantiationReflector implementation
|
||||
public function createInstance(ClassName $className, array $args = []): object
|
||||
{
|
||||
if (! $this->isInstantiable($className)) {
|
||||
throw new \ReflectionException("Class {$className->getFullyQualified()} is not instantiable");
|
||||
}
|
||||
|
||||
$class = $this->getNativeClass($className);
|
||||
|
||||
return $class->newInstance(...$args);
|
||||
}
|
||||
|
||||
public function createInstanceWithoutConstructor(ClassName $className): object
|
||||
{
|
||||
$class = $this->getNativeClass($className);
|
||||
|
||||
return $class->newInstanceWithoutConstructor();
|
||||
}
|
||||
|
||||
public function createLazyGhost(ClassName $className, callable $initializer, int $options = 0): object
|
||||
{
|
||||
$class = $this->getNativeClass($className);
|
||||
|
||||
return $class->newLazyGhost($initializer, $options);
|
||||
}
|
||||
|
||||
public function createLazyProxy(ClassName $className, callable $factory, int $options = 0): object
|
||||
{
|
||||
$class = $this->getNativeClass($className);
|
||||
|
||||
return $class->newLazyProxy($factory, $options);
|
||||
}
|
||||
|
||||
public function hasConstructor(ClassName $className): bool
|
||||
{
|
||||
$class = $this->getNativeClass($className);
|
||||
|
||||
return $class->getConstructor() !== null;
|
||||
}
|
||||
|
||||
public function getConstructorParameters(ClassName $className): array
|
||||
{
|
||||
$class = $this->getNativeClass($className);
|
||||
$constructor = $class->getConstructor();
|
||||
|
||||
if ($constructor === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $constructor->getParameters();
|
||||
}
|
||||
|
||||
public function isAbstract(ClassName $className): bool
|
||||
{
|
||||
return $this->getNativeClass($className)->isAbstract();
|
||||
}
|
||||
|
||||
public function isFinal(ClassName $className): bool
|
||||
{
|
||||
return $this->getNativeClass($className)->isFinal();
|
||||
}
|
||||
|
||||
public function isInternal(ClassName $className): bool
|
||||
{
|
||||
return $this->getNativeClass($className)->isInternal();
|
||||
}
|
||||
|
||||
public function isUserDefined(ClassName $className): bool
|
||||
{
|
||||
return $this->getNativeClass($className)->isUserDefined();
|
||||
}
|
||||
|
||||
// CacheManager implementation
|
||||
public function forget(ClassName $className): void
|
||||
{
|
||||
$this->cache->forget($className);
|
||||
}
|
||||
|
||||
public function flush(): void
|
||||
{
|
||||
$this->classCache = [];
|
||||
$this->methodCache = [];
|
||||
$this->parameterCache = [];
|
||||
$this->propertyCache = [];
|
||||
$this->attributeCache = [];
|
||||
$this->parameterInfoCache = [];
|
||||
$this->instantiableCache = [];
|
||||
$this->interfaceCache = [];
|
||||
$this->wrappedClassCache = [];
|
||||
$this->wrappedMethodCache = [];
|
||||
$this->cache->flush();
|
||||
}
|
||||
|
||||
private function clearRelatedCaches(string $className): void
|
||||
public function getStats(): Statistics
|
||||
{
|
||||
$prefix = $className . '::';
|
||||
return $this->cache->getStats();
|
||||
}
|
||||
|
||||
$caches = [
|
||||
&$this->methodCache,
|
||||
&$this->parameterCache,
|
||||
&$this->propertyCache,
|
||||
&$this->attributeCache,
|
||||
&$this->parameterInfoCache,
|
||||
&$this->interfaceCache,
|
||||
&$this->wrappedMethodCache
|
||||
];
|
||||
|
||||
foreach ($caches as &$cache) {
|
||||
$cache = array_filter(
|
||||
$cache,
|
||||
fn($key) => !str_starts_with($key, $prefix),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Get the underlying cache instance
|
||||
*/
|
||||
public function getCache(): ReflectionCache
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user