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:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -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;
}
}