264 lines
9.9 KiB
PHP
264 lines
9.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Core;
|
|
|
|
use App\Framework\Cache\Cache;
|
|
use App\Framework\Cache\CacheInitializer;
|
|
use App\Framework\DateTime\Clock;
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\DI\Container;
|
|
use App\Framework\DI\ContainerCompiler;
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\DI\DependencyResolver;
|
|
use App\Framework\Discovery\DiscoveryServiceBootstrapper;
|
|
use App\Framework\Http\ResponseEmitter;
|
|
use App\Framework\Logging\DefaultLogger;
|
|
use App\Framework\Logging\Logger;
|
|
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
|
|
use App\Framework\Reflection\CachedReflectionProvider;
|
|
|
|
final readonly class ContainerBootstrapper
|
|
{
|
|
public function __construct(
|
|
private Container $container,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Bootstrap container with intelligent compilation strategy
|
|
*/
|
|
public function bootstrap(
|
|
string $basePath,
|
|
PerformanceCollectorInterface $collector,
|
|
): Container {
|
|
// Temporarily disable compiled container to fix bootstrap issues
|
|
// TODO: Re-enable once container interface compatibility is fixed
|
|
// $optimizedContainer = $this->tryLoadCompiledContainer($basePath, $collector);
|
|
// if ($optimizedContainer !== null) {
|
|
// return $optimizedContainer;
|
|
// }
|
|
|
|
// Fallback to fresh container bootstrap
|
|
return $this->bootstrapFreshContainer($basePath, $collector);
|
|
}
|
|
|
|
/**
|
|
* Try to load compiled container if valid
|
|
*/
|
|
private function tryLoadCompiledContainer(
|
|
string $basePath,
|
|
PerformanceCollectorInterface $collector
|
|
): ?Container {
|
|
try {
|
|
$cacheDir = sys_get_temp_dir() . '/framework-cache';
|
|
$compiledPath = ContainerCompiler::getCompiledContainerPath($cacheDir);
|
|
|
|
// Quick existence check first
|
|
if (! file_exists($compiledPath)) {
|
|
return null;
|
|
}
|
|
|
|
// Create temporary fresh container to validate against
|
|
$tempContainer = $this->createFreshContainer($basePath, $collector);
|
|
|
|
// Create compiler for validation
|
|
$reflectionProvider = new CachedReflectionProvider();
|
|
$dependencyResolver = new DependencyResolver($reflectionProvider, $tempContainer);
|
|
$compiler = new ContainerCompiler($reflectionProvider, $dependencyResolver);
|
|
|
|
// Validate compiled container
|
|
if (! $compiler->isCompiledContainerValid($tempContainer, $compiledPath)) {
|
|
return null; // Invalid, will trigger fresh bootstrap
|
|
}
|
|
|
|
// Load and return compiled container
|
|
$compiledContainer = ContainerCompiler::load($compiledPath);
|
|
|
|
// Add runtime instances that can't be compiled
|
|
$this->addRuntimeInstances($compiledContainer, $basePath, $collector);
|
|
|
|
return $compiledContainer;
|
|
|
|
} catch (\ParseError $e) {
|
|
// Parse error in compiled container - delete cache and fallback
|
|
$this->clearCompiledCache($compiledPath);
|
|
error_log("Compiled container has syntax error, deleted cache: " . $e->getMessage());
|
|
|
|
return null;
|
|
} catch (\Exception $e) {
|
|
// Any other error means we fallback to fresh container
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bootstrap fresh container and compile for next request
|
|
*/
|
|
private function bootstrapFreshContainer(
|
|
string $basePath,
|
|
PerformanceCollectorInterface $collector
|
|
): Container {
|
|
$container = $this->createFreshContainer($basePath, $collector);
|
|
|
|
// Compile for next request (async in production)
|
|
$this->compileContainerAsync($container);
|
|
|
|
return $container;
|
|
}
|
|
|
|
/**
|
|
* Create fresh container with all bindings
|
|
*/
|
|
private function createFreshContainer(
|
|
string $basePath,
|
|
PerformanceCollectorInterface $collector
|
|
): DefaultContainer {
|
|
// Use the existing container or create new DefaultContainer
|
|
$container = $this->container instanceof DefaultContainer
|
|
? $this->container
|
|
: new DefaultContainer();
|
|
|
|
$this->addRuntimeInstances($container, $basePath, $collector);
|
|
$this->autowire($container);
|
|
|
|
return $container;
|
|
}
|
|
|
|
/**
|
|
* Add instances that must be created at runtime
|
|
*/
|
|
private function addRuntimeInstances(
|
|
Container $container,
|
|
string $basePath,
|
|
PerformanceCollectorInterface $collector
|
|
): void {
|
|
// Core services that need runtime data
|
|
$container->instance(Logger::class, new DefaultLogger());
|
|
$container->instance(PerformanceCollectorInterface::class, $collector);
|
|
$container->instance(Cache::class, new CacheInitializer($collector, $container)());
|
|
$container->instance(PathProvider::class, new PathProvider($basePath));
|
|
$container->instance(ResponseEmitter::class, new ResponseEmitter());
|
|
$container->instance(Clock::class, new SystemClock());
|
|
|
|
// TEMPORARY FIX: Manual RequestFactory binding until Discovery issue is resolved
|
|
$container->singleton(\App\Framework\Http\Request::class, function ($container) {
|
|
error_log("ContainerBootstrapper: Creating Request singleton");
|
|
|
|
// Get Cache from container (it was just registered above)
|
|
$frameworkCache = $container->get(\App\Framework\Cache\Cache::class);
|
|
$parserCache = new \App\Framework\Http\Parser\ParserCache($frameworkCache);
|
|
$parser = new \App\Framework\Http\Parser\HttpRequestParser($parserCache);
|
|
$factory = new \App\Framework\Http\RequestFactory($parser);
|
|
|
|
error_log("ContainerBootstrapper: About to call factory->createFromGlobals()");
|
|
$request = $factory->createFromGlobals();
|
|
error_log("ContainerBootstrapper: Request created successfully");
|
|
|
|
return $request;
|
|
});
|
|
|
|
// TEMPORARY FIX: Manual DatabasePlatform binding until Initializer Discovery issue is resolved
|
|
$container->singleton(\App\Framework\Database\Platform\DatabasePlatform::class, function ($container) {
|
|
error_log("ContainerBootstrapper: Creating DatabasePlatform singleton");
|
|
|
|
$env = $container->get(\App\Framework\Config\Environment::class);
|
|
$driver = $env->get('DB_DRIVER', 'pgsql');
|
|
|
|
error_log("ContainerBootstrapper: DB_DRIVER = {$driver}");
|
|
|
|
$platform = match($driver) {
|
|
'mysql', 'mysqli' => new \App\Framework\Database\Platform\MySQLPlatform(),
|
|
'pgsql', 'postgres', 'postgresql' => new \App\Framework\Database\Platform\PostgreSQLPlatform(),
|
|
'sqlite' => throw new \RuntimeException('SQLite platform not yet implemented'),
|
|
default => throw new \RuntimeException("Unsupported database driver: {$driver}")
|
|
};
|
|
|
|
error_log("ContainerBootstrapper: DatabasePlatform created: " . get_class($platform));
|
|
|
|
return $platform;
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Compile container asynchronously for next request
|
|
*/
|
|
private function compileContainerAsync(DefaultContainer $container): void
|
|
{
|
|
try {
|
|
$cacheDir = sys_get_temp_dir() . '/framework-cache';
|
|
$compiledPath = ContainerCompiler::getCompiledContainerPath($cacheDir);
|
|
|
|
$reflectionProvider = new CachedReflectionProvider();
|
|
$dependencyResolver = new DependencyResolver($reflectionProvider, $container);
|
|
$compiler = new ContainerCompiler($reflectionProvider, $dependencyResolver);
|
|
|
|
// Compile (async in production, sync in development)
|
|
#$isProduction = $this->config->get('app.environment') === 'production';
|
|
$isProduction = false;
|
|
if ($isProduction) {
|
|
$compiler->compileAsync($container, $compiledPath);
|
|
} else {
|
|
$compiler->compile($container, $compiledPath);
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
// Compilation errors should not break the application
|
|
// In production, log this error
|
|
}
|
|
}
|
|
|
|
private function autowire(Container $container): void
|
|
{
|
|
// Discovery service bootstrapping
|
|
$clock = $container->get(Clock::class);
|
|
|
|
// Create a simple logger for Discovery phase
|
|
// In MCP mode, use NullHandler to suppress output
|
|
$isMcpMode = getenv('MCP_SERVER_MODE') === '1';
|
|
$handlers = $isMcpMode
|
|
? [new \App\Framework\Logging\Handlers\NullHandler()]
|
|
: [new \App\Framework\Logging\Handlers\ConsoleHandler()];
|
|
|
|
$logger = new \App\Framework\Logging\DefaultLogger(
|
|
minLevel: \App\Framework\Logging\LogLevel::DEBUG,
|
|
handlers: $handlers,
|
|
processorManager: new \App\Framework\Logging\ProcessorManager(),
|
|
contextManager: new \App\Framework\Logging\LogContextManager()
|
|
);
|
|
|
|
$bootstrapper = new DiscoveryServiceBootstrapper($container, $clock, $logger);
|
|
$results = $bootstrapper->bootstrap();
|
|
}
|
|
|
|
/**
|
|
* Clear compiled container cache files
|
|
*/
|
|
private function clearCompiledCache(string $compiledPath): void
|
|
{
|
|
try {
|
|
// Delete the compiled container file
|
|
if (file_exists($compiledPath)) {
|
|
@unlink($compiledPath);
|
|
}
|
|
|
|
// Also clear related cache directory if it exists
|
|
$cacheDir = dirname($compiledPath);
|
|
if (is_dir($cacheDir)) {
|
|
// Clear all cache files in the directory
|
|
$files = glob($cacheDir . '/*');
|
|
foreach ($files as $file) {
|
|
if (is_file($file)) {
|
|
@unlink($file);
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Ignore cache cleanup errors, just log them
|
|
error_log("Error clearing compiled cache: " . $e->getMessage());
|
|
}
|
|
}
|
|
}
|