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:
332
src/Framework/Discovery/Factory/DiscoveryServiceFactory.php
Normal file
332
src/Framework/Discovery/Factory/DiscoveryServiceFactory.php
Normal file
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Discovery\Factory;
|
||||
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\CommandBus\CommandHandlerMapper;
|
||||
use App\Framework\Console\ConsoleCommandMapper;
|
||||
use App\Framework\Core\AttributeMapper;
|
||||
use App\Framework\Core\Events\EventDispatcher;
|
||||
use App\Framework\Core\Events\EventHandlerMapper;
|
||||
use App\Framework\Core\PathProvider;
|
||||
use App\Framework\Core\RouteMapper;
|
||||
use App\Framework\Database\Migration\Migration;
|
||||
use App\Framework\DateTime\Clock;
|
||||
use App\Framework\DI\Container;
|
||||
use App\Framework\DI\DefaultContainer;
|
||||
use App\Framework\DI\InitializerMapper;
|
||||
use App\Framework\Discovery\UnifiedDiscoveryService;
|
||||
use App\Framework\Discovery\ValueObjects\DiscoveryConfiguration;
|
||||
use App\Framework\Filesystem\FileSystemService;
|
||||
use App\Framework\Http\HttpMiddleware;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Mcp\McpResourceMapper;
|
||||
use App\Framework\Mcp\McpToolMapper;
|
||||
use App\Framework\Performance\MemoryMonitor;
|
||||
use App\Framework\QueryBus\QueryHandlerMapper;
|
||||
use App\Framework\Reflection\CachedReflectionProvider;
|
||||
use App\Framework\Reflection\ReflectionProvider;
|
||||
use App\Framework\Template\Processing\DomProcessor;
|
||||
use App\Framework\Template\Processing\StringProcessor;
|
||||
|
||||
/**
|
||||
* Factory for creating properly configured DiscoveryService instances
|
||||
*
|
||||
* Centralizes the complex dependency creation and configuration logic
|
||||
* that was previously scattered in constructors and bootstrappers.
|
||||
*/
|
||||
final readonly class DiscoveryServiceFactory
|
||||
{
|
||||
public function __construct(
|
||||
private Container $container,
|
||||
private PathProvider $pathProvider,
|
||||
private Cache $cache,
|
||||
private Clock $clock
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fully configured DiscoveryService
|
||||
*/
|
||||
public function create(DiscoveryConfiguration $config): UnifiedDiscoveryService
|
||||
{
|
||||
// Validate configuration
|
||||
$config->validate();
|
||||
|
||||
// Resolve or create required dependencies
|
||||
$reflectionProvider = $this->resolveReflectionProvider();
|
||||
$fileSystemService = $this->resolveFileSystemService();
|
||||
|
||||
// Resolve optional dependencies based on configuration
|
||||
$logger = $config->enablePerformanceTracking
|
||||
? $this->resolveLogger()
|
||||
: null;
|
||||
|
||||
$eventDispatcher = $config->enableEventDispatcher
|
||||
? $this->resolveEventDispatcher()
|
||||
: null;
|
||||
|
||||
$memoryMonitor = $config->enableMemoryMonitoring
|
||||
? $this->resolveMemoryMonitor()
|
||||
: null;
|
||||
|
||||
// Merge configured mappers with defaults
|
||||
$attributeMappers = $this->buildAttributeMappers($config->attributeMappers);
|
||||
$targetInterfaces = $this->buildTargetInterfaces($config->targetInterfaces);
|
||||
|
||||
return new UnifiedDiscoveryService(
|
||||
pathProvider: $this->pathProvider,
|
||||
cache: $this->cache,
|
||||
clock: $this->clock,
|
||||
reflectionProvider: $reflectionProvider,
|
||||
configuration: $config,
|
||||
attributeMappers: $attributeMappers,
|
||||
targetInterfaces: $targetInterfaces,
|
||||
logger: $logger,
|
||||
eventDispatcher: $eventDispatcher,
|
||||
memoryMonitor: $memoryMonitor,
|
||||
fileSystemService: $fileSystemService
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DiscoveryService for development environment
|
||||
*/
|
||||
public function createForDevelopment(array $paths = []): UnifiedDiscoveryService
|
||||
{
|
||||
$config = DiscoveryConfiguration::development();
|
||||
$scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths();
|
||||
$config = $config->withPaths($scanPaths);
|
||||
|
||||
return $this->create($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DiscoveryService for production environment
|
||||
*/
|
||||
public function createForProduction(array $paths = []): UnifiedDiscoveryService
|
||||
{
|
||||
$config = DiscoveryConfiguration::production();
|
||||
$scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths();
|
||||
$config = $config->withPaths($scanPaths);
|
||||
|
||||
return $this->create($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DiscoveryService for testing environment
|
||||
*/
|
||||
public function createForTesting(array $paths = []): UnifiedDiscoveryService
|
||||
{
|
||||
$config = DiscoveryConfiguration::testing();
|
||||
$scanPaths = ! empty($paths) ? $paths : $this->getDefaultScanPaths();
|
||||
$config = $config->withPaths($scanPaths);
|
||||
|
||||
return $this->create($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DiscoveryService with custom configuration
|
||||
*/
|
||||
public function createWithCustomConfig(
|
||||
array $paths = [],
|
||||
bool $useCache = true,
|
||||
array $attributeMappers = [],
|
||||
array $targetInterfaces = []
|
||||
): UnifiedDiscoveryService {
|
||||
$config = new DiscoveryConfiguration(
|
||||
paths: $paths,
|
||||
attributeMappers: $attributeMappers,
|
||||
targetInterfaces: $targetInterfaces,
|
||||
useCache: $useCache
|
||||
);
|
||||
|
||||
return $this->create($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve ReflectionProvider from container or create default
|
||||
*/
|
||||
private function resolveReflectionProvider(): ReflectionProvider
|
||||
{
|
||||
if ($this->container->has(ReflectionProvider::class)) {
|
||||
return $this->container->get(ReflectionProvider::class);
|
||||
}
|
||||
|
||||
return new CachedReflectionProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve FileSystemService from container or create default
|
||||
*/
|
||||
private function resolveFileSystemService(): FileSystemService
|
||||
{
|
||||
if ($this->container->has(FileSystemService::class)) {
|
||||
return $this->container->get(FileSystemService::class);
|
||||
}
|
||||
|
||||
return new FileSystemService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve Logger from container if available
|
||||
*/
|
||||
private function resolveLogger(): ?Logger
|
||||
{
|
||||
return $this->container->has(Logger::class)
|
||||
? $this->container->get(Logger::class)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve EventDispatcher from container if available
|
||||
*/
|
||||
private function resolveEventDispatcher(): ?EventDispatcher
|
||||
{
|
||||
return $this->container->has(EventDispatcher::class)
|
||||
? $this->container->get(EventDispatcher::class)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve MemoryMonitor from container if available
|
||||
*/
|
||||
private function resolveMemoryMonitor(): ?MemoryMonitor
|
||||
{
|
||||
return $this->container->has(MemoryMonitor::class)
|
||||
? $this->container->get(MemoryMonitor::class)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build attribute mappers array with defaults and user-provided mappers
|
||||
*/
|
||||
private function buildAttributeMappers(array $customMappers = []): array
|
||||
{
|
||||
$defaultMappers = [
|
||||
new RouteMapper(),
|
||||
new EventHandlerMapper(),
|
||||
new \App\Framework\EventBus\EventHandlerMapper(),
|
||||
new QueryHandlerMapper(),
|
||||
new CommandHandlerMapper(),
|
||||
new InitializerMapper(),
|
||||
new McpToolMapper(),
|
||||
new McpResourceMapper(),
|
||||
new ConsoleCommandMapper(),
|
||||
];
|
||||
|
||||
// Merge custom mappers with defaults, allowing override by class name
|
||||
$mappers = [];
|
||||
$mapperClasses = [];
|
||||
|
||||
// Add default mappers first
|
||||
foreach ($defaultMappers as $mapper) {
|
||||
$className = get_class($mapper);
|
||||
$mappers[] = $mapper;
|
||||
$mapperClasses[] = $className;
|
||||
}
|
||||
|
||||
// Add custom mappers, replacing defaults if same class
|
||||
foreach ($customMappers as $mapper) {
|
||||
$className = get_class($mapper);
|
||||
$existingIndex = array_search($className, $mapperClasses, true);
|
||||
|
||||
if ($existingIndex !== false) {
|
||||
// Replace existing mapper
|
||||
$mappers[$existingIndex] = $mapper;
|
||||
} else {
|
||||
// Add new mapper
|
||||
$mappers[] = $mapper;
|
||||
$mapperClasses[] = $className;
|
||||
}
|
||||
}
|
||||
|
||||
return $mappers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build target interfaces array with defaults and user-provided interfaces
|
||||
*/
|
||||
private function buildTargetInterfaces(array $customInterfaces = []): array
|
||||
{
|
||||
$defaultInterfaces = [
|
||||
AttributeMapper::class,
|
||||
HttpMiddleware::class,
|
||||
DomProcessor::class,
|
||||
StringProcessor::class,
|
||||
Migration::class,
|
||||
];
|
||||
|
||||
// Merge and deduplicate
|
||||
return array_values(array_unique(array_merge($defaultInterfaces, $customInterfaces)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a lightweight factory for specific use cases
|
||||
*/
|
||||
public static function lightweight(
|
||||
PathProvider $pathProvider,
|
||||
Cache $cache,
|
||||
Clock $clock
|
||||
): self {
|
||||
// Create a minimal container for basic dependencies
|
||||
$container = new DefaultContainer();
|
||||
|
||||
return new self($container, $pathProvider, $cache, $clock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that all required services are available
|
||||
* @return string[]
|
||||
*/
|
||||
public function validateDependencies(DiscoveryConfiguration $config): array
|
||||
{
|
||||
$issues = [];
|
||||
|
||||
// Check required dependencies
|
||||
if (! $this->container->has(PathProvider::class) && ! isset($this->pathProvider)) {
|
||||
$issues[] = 'PathProvider is required but not available';
|
||||
}
|
||||
|
||||
if (! $this->container->has(Cache::class) && ! isset($this->cache)) {
|
||||
$issues[] = 'Cache is required but not available';
|
||||
}
|
||||
|
||||
if (! $this->container->has(Clock::class) && ! isset($this->clock)) {
|
||||
$issues[] = 'Clock is required but not available';
|
||||
}
|
||||
|
||||
// Check optional dependencies if enabled
|
||||
if ($config->enableEventDispatcher && ! $this->container->has(EventDispatcher::class)) {
|
||||
$issues[] = 'EventDispatcher is enabled but not available in container';
|
||||
}
|
||||
|
||||
if ($config->enableMemoryMonitoring && ! $this->container->has(MemoryMonitor::class)) {
|
||||
$issues[] = 'MemoryMonitor is enabled but not available in container';
|
||||
}
|
||||
|
||||
if ($config->enablePerformanceTracking && ! $this->container->has(Logger::class)) {
|
||||
$issues[] = 'Logger is needed for performance tracking but not available in container';
|
||||
}
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default scan paths for discovery
|
||||
* @return string[]
|
||||
*/
|
||||
private function getDefaultScanPaths(): array
|
||||
{
|
||||
$basePath = $this->pathProvider->getBasePath();
|
||||
|
||||
|
||||
return [
|
||||
$this->pathProvider->getSourcePath(),
|
||||
#$basePath . '/src',
|
||||
#$basePath . '/app', // Support for app/ directory structure
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user