- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
282 lines
10 KiB
PHP
282 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\DI\ContainerCompiler;
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\DI\DependencyResolver;
|
|
use App\Framework\Reflection\CachedReflectionProvider;
|
|
|
|
beforeEach(function () {
|
|
$this->tempDir = sys_get_temp_dir() . '/container-compiler-test-' . uniqid();
|
|
mkdir($this->tempDir, 0755, true);
|
|
|
|
$this->container = new DefaultContainer();
|
|
$this->reflectionProvider = new CachedReflectionProvider();
|
|
$this->dependencyResolver = new DependencyResolver($this->reflectionProvider, $this->container);
|
|
$this->compiler = new ContainerCompiler($this->reflectionProvider, $this->dependencyResolver);
|
|
|
|
$this->compiledPath = $this->tempDir . '/compiled-container.php';
|
|
});
|
|
|
|
afterEach(function () {
|
|
// Clean up test directory
|
|
if (is_dir($this->tempDir)) {
|
|
array_map('unlink', glob($this->tempDir . '/*'));
|
|
rmdir($this->tempDir);
|
|
}
|
|
});
|
|
|
|
// Test classes
|
|
class ContainerCompilerTestSimpleService
|
|
{
|
|
public function getName(): string
|
|
{
|
|
return 'simple';
|
|
}
|
|
}
|
|
|
|
class ContainerCompilerTestServiceWithDependency
|
|
{
|
|
public function __construct(private ContainerCompilerTestSimpleService $service)
|
|
{
|
|
}
|
|
|
|
public function getServiceName(): string
|
|
{
|
|
return $this->service->getName();
|
|
}
|
|
}
|
|
|
|
interface ContainerCompilerTestServiceInterface
|
|
{
|
|
public function getValue(): string;
|
|
}
|
|
|
|
class ContainerCompilerTestConcreteService implements ContainerCompilerTestServiceInterface
|
|
{
|
|
public function getValue(): string
|
|
{
|
|
return 'concrete';
|
|
}
|
|
}
|
|
|
|
test('compiles container with simple binding', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
|
|
// Act
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Assert
|
|
expect(file_exists($this->compiledPath))->toBeTrue();
|
|
|
|
$content = file_get_contents($this->compiledPath);
|
|
expect($content)->toContain('class CompiledContainer implements Container');
|
|
expect($content)->toContain('createContainerCompilerTestSimpleService()');
|
|
});
|
|
|
|
test('compiles container with dependency injection', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);
|
|
|
|
// Act
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Assert
|
|
expect(file_exists($this->compiledPath))->toBeTrue();
|
|
|
|
$content = file_get_contents($this->compiledPath);
|
|
expect($content)->toContain('$this->get(\'ContainerCompilerTestSimpleService\')');
|
|
});
|
|
|
|
test('compiles container with singletons', function () {
|
|
// Arrange
|
|
$this->container->singleton(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
|
|
// Act
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Assert
|
|
expect(file_exists($this->compiledPath))->toBeTrue();
|
|
|
|
$content = file_get_contents($this->compiledPath);
|
|
expect($content)->toContain('$this->singletons[\'ContainerCompilerTestSimpleService\'] = true');
|
|
});
|
|
|
|
test('loads compiled container successfully', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Act
|
|
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
|
|
|
// Assert
|
|
expect($compiledContainer)->toBeInstanceOf(\App\Framework\DI\Container::class);
|
|
expect($compiledContainer->has(ContainerCompilerTestSimpleService::class))->toBeTrue();
|
|
|
|
$instance = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
|
expect($instance)->toBeInstanceOf(ContainerCompilerTestSimpleService::class);
|
|
expect($instance->getName())->toBe('simple');
|
|
});
|
|
|
|
test('compiled container resolves dependencies correctly', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Act
|
|
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
|
$instance = $compiledContainer->get(ContainerCompilerTestServiceWithDependency::class);
|
|
|
|
// Assert
|
|
expect($instance)->toBeInstanceOf(ContainerCompilerTestServiceWithDependency::class);
|
|
expect($instance->getServiceName())->toBe('simple');
|
|
});
|
|
|
|
test('compiled container handles singletons correctly', function () {
|
|
// Arrange
|
|
$this->container->singleton(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Act
|
|
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
|
$instance1 = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
|
$instance2 = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
|
|
|
// Assert
|
|
expect($instance1)->toBe($instance2);
|
|
});
|
|
|
|
test('validates compiled container hash correctly', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Act & Assert - should be valid initially
|
|
expect($this->compiler->isCompiledContainerValid($this->container, $this->compiledPath))->toBeTrue();
|
|
|
|
// Add new binding to change container state
|
|
$this->container->bind(ContainerCompilerTestServiceWithDependency::class, ContainerCompilerTestServiceWithDependency::class);
|
|
|
|
// Should now be invalid due to hash mismatch
|
|
expect($this->compiler->isCompiledContainerValid($this->container, $this->compiledPath))->toBeFalse();
|
|
});
|
|
|
|
test('returns false for non-existent compiled container', function () {
|
|
// Arrange
|
|
$nonExistentPath = $this->tempDir . '/non-existent.php';
|
|
|
|
// Act & Assert
|
|
expect($this->compiler->isCompiledContainerValid($this->container, $nonExistentPath))->toBeFalse();
|
|
});
|
|
|
|
test('creates directory if it does not exist', function () {
|
|
// Arrange
|
|
$nestedPath = $this->tempDir . '/nested/deep/compiled-container.php';
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
|
|
// Act
|
|
$this->compiler->compile($this->container, $nestedPath);
|
|
|
|
// Assert
|
|
expect(file_exists($nestedPath))->toBeTrue();
|
|
expect(is_dir(dirname($nestedPath)))->toBeTrue();
|
|
});
|
|
|
|
test('throws exception when loading non-existent compiled container', function () {
|
|
// Arrange
|
|
$nonExistentPath = $this->tempDir . '/non-existent.php';
|
|
|
|
// Act & Assert
|
|
expect(fn () => ContainerCompiler::load($nonExistentPath))
|
|
->toThrow(RuntimeException::class, 'Compiled container not found');
|
|
});
|
|
|
|
test('compiled container throws exception for runtime binding', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
|
|
|
// Act & Assert
|
|
expect(fn () => $compiledContainer->bind('NewClass', 'AnotherClass'))
|
|
->toThrow(RuntimeException::class, 'Cannot bind to compiled container');
|
|
});
|
|
|
|
test('compiled container throws exception for runtime singleton registration', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
|
|
|
// Act & Assert
|
|
expect(fn () => $compiledContainer->singleton('NewClass', 'AnotherClass'))
|
|
->toThrow(RuntimeException::class, 'Cannot add singletons to compiled container');
|
|
});
|
|
|
|
test('compiled container allows runtime instance registration', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
|
$customInstance = new ContainerCompilerTestSimpleService();
|
|
|
|
// Act
|
|
$compiledContainer->instance(ContainerCompilerTestSimpleService::class, $customInstance);
|
|
$retrievedInstance = $compiledContainer->get(ContainerCompilerTestSimpleService::class);
|
|
|
|
// Assert
|
|
expect($retrievedInstance)->toBe($customInstance);
|
|
});
|
|
|
|
test('gets default compiled container path', function () {
|
|
// Act
|
|
$path = ContainerCompiler::getCompiledContainerPath();
|
|
|
|
// Assert
|
|
expect($path)->toContain('compiled-container.php');
|
|
expect(is_dir(dirname($path)))->toBeTrue();
|
|
});
|
|
|
|
test('gets custom compiled container path', function () {
|
|
// Arrange
|
|
$customCacheDir = $this->tempDir . '/custom-cache';
|
|
|
|
// Act
|
|
$path = ContainerCompiler::getCompiledContainerPath($customCacheDir);
|
|
|
|
// Assert
|
|
expect($path)->toBe($customCacheDir . '/compiled-container.php');
|
|
expect(is_dir($customCacheDir))->toBeTrue();
|
|
});
|
|
|
|
test('compiled container handles unknown class gracefully', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
$compiledContainer = ContainerCompiler::load($this->compiledPath);
|
|
|
|
// Act & Assert
|
|
expect(fn () => $compiledContainer->get('UnknownClass'))
|
|
->toThrow(InvalidArgumentException::class, 'Class UnknownClass is not bound in the container');
|
|
});
|
|
|
|
test('generated code contains proper metadata', function () {
|
|
// Arrange
|
|
$this->container->bind(ContainerCompilerTestSimpleService::class, ContainerCompilerTestSimpleService::class);
|
|
|
|
// Act
|
|
$this->compiler->compile($this->container, $this->compiledPath);
|
|
|
|
// Assert
|
|
$content = file_get_contents($this->compiledPath);
|
|
expect($content)->toContain('Generated:');
|
|
expect($content)->toContain('Hash:');
|
|
expect($content)->toContain('WARNING: This file is auto-generated');
|
|
expect($content)->toMatch('/Hash: [a-f0-9]{64}/');
|
|
});
|