addInitializer( 'ServiceA', ClassName::create('App\Test\ServiceAInitializer'), MethodName::create('__invoke') ); $graph->addInitializer( 'ServiceB', ClassName::create('App\Test\ServiceBInitializer'), MethodName::create('__invoke') ); expect($graph->hasNode('ServiceA'))->toBeTrue(); expect($graph->hasNode('ServiceB'))->toBeTrue(); expect($graph->hasNode('ServiceC'))->toBeFalse(); }); it('uses explicit dependencies when provided', function () { $reflectionService = new SimpleReflectionService(); $graph = new InitializerDependencyGraph($reflectionService); $graph->addInitializer( 'ServiceA', ClassName::create('App\Test\ServiceAInitializer'), MethodName::create('__invoke'), ['ServiceB', 'ServiceC'], // Explicit dependencies 100 // Priority ); $node = $graph->getNode('ServiceA'); expect($node)->not->toBeNull(); expect($node->dependencies)->toBe(['ServiceB', 'ServiceC']); }); it('detects circular dependencies', function () { $reflectionService = new SimpleReflectionService(); $graph = new InitializerDependencyGraph($reflectionService); // Create a cycle: A -> B -> A $graph->addInitializer( 'ServiceA', ClassName::create('App\Test\ServiceAInitializer'), MethodName::create('__invoke'), ['ServiceB'] // A depends on B ); $graph->addInitializer( 'ServiceB', ClassName::create('App\Test\ServiceBInitializer'), MethodName::create('__invoke'), ['ServiceA'] // B depends on A (cycle!) ); expect(fn() => $graph->getExecutionOrder())->toThrow(InitializerCycleException::class); }); it('provides cycle information with dependency paths', function () { $reflectionService = new SimpleReflectionService(); $graph = new InitializerDependencyGraph($reflectionService); // Create a cycle: A -> B -> A $graph->addInitializer( 'ServiceA', ClassName::create('App\Test\ServiceAInitializer'), MethodName::create('__invoke'), ['ServiceB'] ); $graph->addInitializer( 'ServiceB', ClassName::create('App\Test\ServiceBInitializer'), MethodName::create('__invoke'), ['ServiceA'] ); try { $graph->getExecutionOrder(); expect(false)->toBeTrue(); // Should not reach here } catch (InitializerCycleException $e) { $cycles = $e->getCycles(); expect($cycles)->not->toBeEmpty(); $paths = $e->getDependencyPaths(); expect($paths)->not->toBeEmpty(); } }); it('calculates correct execution order for linear dependencies', function () { $reflectionService = new SimpleReflectionService(); $graph = new InitializerDependencyGraph($reflectionService); // C depends on B, B depends on A $graph->addInitializer( 'ServiceA', ClassName::create('App\Test\ServiceAInitializer'), MethodName::create('__invoke'), [] // No dependencies ); $graph->addInitializer( 'ServiceB', ClassName::create('App\Test\ServiceBInitializer'), MethodName::create('__invoke'), ['ServiceA'] // B depends on A ); $graph->addInitializer( 'ServiceC', ClassName::create('App\Test\ServiceCInitializer'), MethodName::create('__invoke'), ['ServiceB'] // C depends on B ); $order = $graph->getExecutionOrder(); // A should come before B, B should come before C $indexA = array_search('ServiceA', $order, true); $indexB = array_search('ServiceB', $order, true); $indexC = array_search('ServiceC', $order, true); expect($indexA)->toBeLessThan($indexB); expect($indexB)->toBeLessThan($indexC); }); it('integrates with InitializerDependencyAnalyzer when provided', function () { $reflectionService = new SimpleReflectionService(); $analyzer = new InitializerDependencyAnalyzer(null); $graph = new InitializerDependencyGraph($reflectionService, $analyzer); // Graph should accept analyzer without errors expect($graph)->toBeInstanceOf(InitializerDependencyGraph::class); }); });