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

@@ -0,0 +1,281 @@
<?php
declare(strict_types=1);
namespace App\Framework\Discovery\Processing;
use App\Framework\Core\AttributeMapper;
use App\Framework\Core\ValueObjects\ClassName;
use App\Framework\Core\ValueObjects\MethodName;
use App\Framework\Discovery\DiscoveryDataCollector;
use App\Framework\Discovery\ValueObjects\AttributeTarget;
use App\Framework\Discovery\ValueObjects\DiscoveredAttribute;
use App\Framework\Discovery\ValueObjects\FileContext;
use App\Framework\Discovery\ValueObjects\TemplateMapping;
use App\Framework\Filesystem\File;
use App\Framework\Http\Method;
use App\Framework\Logging\Logger;
/**
* Coordinates visitor execution with shared reflection context
*
* This replaces the duplicate visitor logic in UnifiedDiscoveryService
*/
final class VisitorCoordinator
{
/** @var array<string, AttributeMapper> */
private array $mapperMap;
/** @var array<string> */
private array $ignoredAttributes = [
'Attribute', 'Override', 'AllowDynamicProperties',
'ReturnTypeWillChange', 'SensitiveParameter',
];
public function __construct(
private readonly ProcessingContext $processingContext,
private readonly array $attributeMappers = [],
private readonly array $targetInterfaces = [],
private ?Logger $logger = null
) {
// Build mapper map for quick lookup
$mapperMap = [];
foreach ($this->attributeMappers as $mapper) {
$mapperMap[$mapper->getAttributeClass()] = $mapper;
}
$this->mapperMap = $mapperMap;
}
/**
* Process a file with all discovery logic
*/
public function processFile(
File $file,
FileContext $fileContext,
DiscoveryDataCollector $collector
): void {
// Process each class in the file
foreach ($fileContext->getClassNames() as $className) {
$this->processClass($className, $fileContext, $collector);
}
// Process templates (file-based)
$this->processTemplates($file, $collector);
}
/**
* Process a single class
*/
private function processClass(
ClassName $className,
FileContext $fileContext,
DiscoveryDataCollector $collector
): void {
// Get shared reflection instance
$reflection = $this->processingContext->getReflection($className);
if ($reflection === null) {
return;
}
// Process attributes
$this->processClassAttributes($className, $fileContext, $reflection, $collector);
$this->processMethodAttributes($className, $fileContext, $reflection, $collector);
// Process interface implementations
if (! empty($this->targetInterfaces)) {
$this->processInterfaces($className, $reflection, $collector);
}
}
/**
* Process class-level attributes
*/
private function processClassAttributes(
ClassName $className,
FileContext $fileContext,
$reflection,
DiscoveryDataCollector $collector
): void {
foreach ($reflection->getAttributes() as $attribute) {
$attributeClass = $attribute->getName();
if ($this->shouldIgnoreAttribute($attributeClass)) {
continue;
}
$mappedData = $this->applyMapper($attributeClass, $reflection, $attribute);
$discovered = new DiscoveredAttribute(
className: $className,
attributeClass: $attributeClass,
target: AttributeTarget::TARGET_CLASS,
methodName: null,
propertyName: null,
arguments: $this->extractAttributeArguments($attribute),
filePath: $fileContext->path,
additionalData: $mappedData ?? []
);
$collector->getAttributeRegistry()->add($attributeClass, $discovered);
}
}
/**
* Process method-level attributes
*/
private function processMethodAttributes(
ClassName $className,
FileContext $fileContext,
$reflection,
DiscoveryDataCollector $collector
): void {
foreach ($reflection->getMethods() as $method) {
foreach ($method->getAttributes() as $attribute) {
$attributeClass = $attribute->getName();
if ($this->shouldIgnoreAttribute($attributeClass)) {
continue;
}
$mappedData = $this->applyMapper($attributeClass, $method, $attribute);
$discovered = new DiscoveredAttribute(
className: $className,
attributeClass: $attributeClass,
target: AttributeTarget::METHOD,
methodName: MethodName::create($method->getName()),
propertyName: null,
arguments: $this->extractAttributeArguments($attribute),
filePath: $fileContext->path,
additionalData: $mappedData ?? []
);
$collector->getAttributeRegistry()->add($attributeClass, $discovered);
}
}
}
/**
* Process interface implementations
*/
private function processInterfaces(
ClassName $className,
$reflection,
DiscoveryDataCollector $collector
): void {
foreach ($this->targetInterfaces as $targetInterface) {
if ($reflection->implementsInterface($targetInterface)) {
$collector->getInterfaceRegistry()->add(
$targetInterface,
$className->getFullyQualified()
);
}
}
}
/**
* Process templates
*/
private function processTemplates(File $file, DiscoveryDataCollector $collector): void
{
$filePath = $file->getPath()->toString();
if (str_ends_with($filePath, '.view.php') || str_contains($filePath, '/views/')) {
$templateName = basename($filePath, '.php');
$mapping = TemplateMapping::create($templateName, $filePath);
$collector->getTemplateRegistry()->add($mapping);
}
}
/**
* Apply attribute mapper if available
*/
private function applyMapper(string $attributeClass, $reflectionElement, $attribute): ?array
{
if (! isset($this->mapperMap[$attributeClass])) {
return $this->tryDefaultMapper($attributeClass, $reflectionElement, $attribute);
}
try {
$mapper = $this->mapperMap[$attributeClass];
$attributeInstance = $attribute->newInstance();
return $mapper->map($reflectionElement, $attributeInstance);
} catch (\Throwable) {
return null;
}
}
/**
* Try to use a default mapper for common attributes
*/
private function tryDefaultMapper(string $attributeClass, $reflectionElement, $attribute): ?array
{
$defaultMapper = match ($attributeClass) {
'App\Framework\Attributes\Route' => new \App\Framework\Core\RouteMapper(),
'App\Framework\Core\Events\OnEvent' => new \App\Framework\Core\Events\EventHandlerMapper(),
'App\Framework\CommandBus\CommandHandler' => new \App\Framework\CommandBus\CommandHandlerMapper(),
'App\Framework\QueryBus\QueryHandler' => new \App\Framework\QueryBus\QueryHandlerMapper(),
'App\Framework\DI\Initializer' => new \App\Framework\DI\InitializerMapper(),
default => null,
};
if ($defaultMapper === null) {
return null;
}
try {
$attributeInstance = $attribute->newInstance();
return $defaultMapper->map($reflectionElement, $attributeInstance);
} catch (\Throwable) {
return null;
}
}
/**
* Extract and normalize attribute arguments
*/
private function extractAttributeArguments($attribute): array
{
try {
$arguments = $attribute->getArguments();
$normalizedArgs = [];
$reflection = new \ReflectionClass($attribute->getName());
$constructor = $reflection->getConstructor();
if ($constructor) {
$parameters = $constructor->getParameters();
foreach ($arguments as $key => $value) {
if (is_int($key) && isset($parameters[$key])) {
$normalizedArgs[$parameters[$key]->getName()] = $value;
} else {
$normalizedArgs[$key] = $value;
}
}
} else {
$normalizedArgs = $arguments;
}
return $normalizedArgs;
} catch (\Throwable) {
return [];
}
}
/**
* Check if an attribute should be ignored
*/
private function shouldIgnoreAttribute(string $attributeName): bool
{
if (in_array($attributeName, $this->ignoredAttributes, true)) {
return true;
}
$shortName = substr($attributeName, strrpos($attributeName, '\\') + 1);
return in_array($shortName, $this->ignoredAttributes, true);
}
}