- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
496 lines
16 KiB
PHP
496 lines
16 KiB
PHP
<?php
|
|
|
|
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 ReflectionType;
|
|
|
|
final readonly class CachedReflectionProvider implements ReflectionProvider
|
|
{
|
|
private ReflectionCache $cache;
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
}
|
|
|
|
// ClassReflector implementation
|
|
public function getClass(ClassName $className): WrappedReflectionClass
|
|
{
|
|
return $this->cache->classCache->getClass($className);
|
|
}
|
|
|
|
public function getNativeClass(ClassName $className): ReflectionClass
|
|
{
|
|
return $this->cache->classCache->getNativeClass($className);
|
|
}
|
|
|
|
public function getProperties(ClassName $className): PropertyCollection
|
|
{
|
|
return $this->cache->classCache->getProperties($className);
|
|
}
|
|
|
|
public function getMethods(ClassName $className, ?int $filter = null): MethodCollection
|
|
{
|
|
return $this->cache->classCache->getMethods($className, $filter);
|
|
}
|
|
|
|
public function getAttributes(ClassName $className, ?string $attributeClass = null): AttributeCollection
|
|
{
|
|
return $this->cache->classCache->getAttributes($className, $attributeClass);
|
|
}
|
|
|
|
public function hasAttribute(ClassName $className, string $attributeClass): bool
|
|
{
|
|
return $this->cache->classCache->hasAttribute($className, $attributeClass);
|
|
}
|
|
|
|
public function isInstantiable(ClassName $className): bool
|
|
{
|
|
return $this->cache->classCache->isInstantiable($className);
|
|
}
|
|
|
|
public function implementsInterface(ClassName $className, string $interfaceName): bool
|
|
{
|
|
return $this->cache->classCache->implementsInterface($className, $interfaceName);
|
|
}
|
|
|
|
// MethodReflector implementation
|
|
public function getMethod(ClassName $className, string $methodName): WrappedReflectionMethod
|
|
{
|
|
return $this->cache->methodCache->getMethod($className, $methodName);
|
|
}
|
|
|
|
public function getNativeMethod(ClassName $className, string $methodName): ReflectionMethod
|
|
{
|
|
return $this->cache->methodCache->getNativeMethod($className, $methodName);
|
|
}
|
|
|
|
public function getMethodParameters(ClassName $className, string $methodName): ParameterCollection
|
|
{
|
|
return $this->cache->parameterCache->getParameters($className, $methodName);
|
|
}
|
|
|
|
public function getMethodAttributes(ClassName $className, string $methodName, ?string $attributeClass = null): AttributeCollection
|
|
{
|
|
return $this->cache->methodCache->getMethodAttributes($className, $methodName, $attributeClass);
|
|
}
|
|
|
|
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 new PropertyCollection(...$result);
|
|
}
|
|
|
|
public function getPropertyAttributes(ClassName $className, string $propertyName, ?string $attributeClass = null): array
|
|
{
|
|
$property = $this->getProperty($className, $propertyName);
|
|
|
|
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->cache->flush();
|
|
}
|
|
|
|
public function getStats(): Statistics
|
|
{
|
|
return $this->cache->getStats();
|
|
}
|
|
|
|
/**
|
|
* Get the underlying cache instance
|
|
*/
|
|
public function getCache(): ReflectionCache
|
|
{
|
|
return $this->cache;
|
|
}
|
|
}
|