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}/'); });