@@ -185,25 +185,7 @@ public function dump(array $options = [])
185185 }
186186 }
187187
188- (new AnalyzeServiceReferencesPass (false , !$ this ->getProxyDumper () instanceof NullDumper))->process ($ this ->container );
189- $ checkedNodes = [];
190- $ this ->circularReferences = [];
191- $ this ->singleUsePrivateIds = [];
192- foreach ($ this ->container ->getCompiler ()->getServiceReferenceGraph ()->getNodes () as $ id => $ node ) {
193- if (!$ node ->getValue () instanceof Definition) {
194- continue ;
195- }
196- if (!isset ($ checkedNodes [$ id ])) {
197- $ this ->analyzeCircularReferences ($ id , $ node ->getOutEdges (), $ checkedNodes );
198- }
199- if ($ this ->isSingleUsePrivateNode ($ node )) {
200- $ this ->singleUsePrivateIds [$ id ] = $ id ;
201- }
202- }
203- $ this ->container ->getCompiler ()->getServiceReferenceGraph ()->clear ();
204- $ checkedNodes = [];
205- $ this ->singleUsePrivateIds = array_diff_key ($ this ->singleUsePrivateIds , $ this ->circularReferences );
206-
188+ $ this ->analyzeReferences ();
207189 $ this ->docStar = $ options ['debug ' ] ? '* ' : '' ;
208190
209191 if (!empty ($ options ['file ' ]) && is_dir ($ dir = \dirname ($ options ['file ' ]))) {
@@ -429,61 +411,92 @@ private function getProxyDumper(): ProxyDumper
429411 return $ this ->proxyDumper ;
430412 }
431413
432- /**
433- * @param ServiceReferenceGraphEdge[] $edges
434- */
435- private function analyzeCircularReferences (string $ sourceId , array $ edges , array &$ checkedNodes , array &$ currentPath = [], bool $ byConstructor = true )
414+ private function analyzeReferences ()
436415 {
437- $ checkedNodes [$ sourceId ] = true ;
438- $ currentPath [$ sourceId ] = $ byConstructor ;
416+ (new AnalyzeServiceReferencesPass (false , !$ this ->getProxyDumper () instanceof NullDumper))->process ($ this ->container );
417+ $ checkedNodes = [];
418+ $ this ->circularReferences = [];
419+ $ this ->singleUsePrivateIds = [];
420+ foreach ($ this ->container ->getCompiler ()->getServiceReferenceGraph ()->getNodes () as $ id => $ node ) {
421+ if (!$ node ->getValue () instanceof Definition) {
422+ continue ;
423+ }
439424
440- foreach ( $ edges as $ edge ) {
441- $ node = $ edge -> getDestNode () ;
442- $ id = $ node -> getId ();
425+ if ( $ this -> isSingleUsePrivateNode ( $ node ) ) {
426+ $ this -> singleUsePrivateIds [ $ id ] = $ id ;
427+ }
443428
444- if (!$ node ->getValue () instanceof Definition || $ sourceId === $ id || $ edge ->isLazy () || $ edge ->isWeak ()) {
445- // no-op
446- } elseif (isset ($ currentPath [$ id ])) {
447- $ this ->addCircularReferences ($ id , $ currentPath , $ edge ->isReferencedByConstructor ());
448- } elseif (!isset ($ checkedNodes [$ id ])) {
449- $ this ->analyzeCircularReferences ($ id , $ node ->getOutEdges (), $ checkedNodes , $ currentPath , $ edge ->isReferencedByConstructor ());
450- } elseif (isset ($ this ->circularReferences [$ id ])) {
451- $ this ->connectCircularReferences ($ id , $ currentPath , $ edge ->isReferencedByConstructor ());
429+ $ newNodes = [];
430+ if (!$ this ->collectCircularReferences ($ id , $ node ->getOutEdges (), $ checkedNodes , $ newNodes )) {
431+ foreach ($ newNodes as $ newNodeId => $ _ ) {
432+ $ checkedNodes [$ newNodeId ] = [];
433+ }
434+ continue ;
452435 }
453- }
454- unset($ currentPath [$ sourceId ]);
455- }
456436
457- private function connectCircularReferences (string $ sourceId , array &$ currentPath , bool $ byConstructor , array &$ subPath = [])
458- {
459- $ currentPath [$ sourceId ] = $ subPath [$ sourceId ] = $ byConstructor ;
437+ $ nodesToFlatten = $ newNodes ;
438+ do {
439+ $ changedNodes = [];
440+ foreach ($ nodesToFlatten as $ newNodeId => $ _ ) {
441+ $ deps = &$ checkedNodes [$ newNodeId ];
442+ foreach ($ deps as $ id => [$ path , $ depsByConstructor ]) {
443+ foreach ($ checkedNodes [$ id ] as $ depsId => [$ subPath , $ subDepsByConstructor ]) {
444+ if (!isset ($ deps [$ depsId ]) || ($ depsByConstructor && $ subDepsByConstructor && !$ deps [$ depsId ][1 ])) {
445+ array_unshift ($ subPath , $ id );
446+ $ deps [$ depsId ] = [$ subPath , $ depsByConstructor && $ subDepsByConstructor ];
447+ $ changedNodes += $ newNodes [$ newNodeId ] ?? [];
448+ }
449+ }
450+ }
451+ }
452+ } while ($ nodesToFlatten = $ changedNodes );
460453
461- foreach ($ this ->circularReferences [$ sourceId ] as $ id => $ byConstructor ) {
462- if (isset ($ currentPath [$ id ])) {
463- $ this ->addCircularReferences ($ id , $ currentPath , $ byConstructor );
464- } elseif (!isset ($ subPath [$ id ]) && isset ($ this ->circularReferences [$ id ])) {
465- $ this ->connectCircularReferences ($ id , $ currentPath , $ byConstructor , $ subPath );
454+ foreach ($ newNodes as $ newNodeId => $ _ ) {
455+ if (null !== $ n = $ checkedNodes [$ newNodeId ][$ newNodeId ] ?? null ) {
456+ $ this ->addCircularReferences ($ newNodeId , $ n [0 ], $ n [1 ]);
457+ }
466458 }
467459 }
468- unset($ currentPath [$ sourceId ], $ subPath [$ sourceId ]);
460+
461+ $ this ->container ->getCompiler ()->getServiceReferenceGraph ()->clear ();
462+ $ this ->singleUsePrivateIds = array_diff_key ($ this ->singleUsePrivateIds , $ this ->circularReferences );
469463 }
470464
471- private function addCircularReferences (string $ id , array $ currentPath , bool $ byConstructor )
465+ private function collectCircularReferences (string $ sourceId , array $ edges , array & $ checkedNodes , array & $ newNodes , array $ path = []): bool
472466 {
473- $ currentPath [$ id ] = $ byConstructor ;
474- $ circularRefs = [];
467+ $ path [$ sourceId ] = true ;
468+ $ checkedNodes [$ sourceId ] = [];
469+ $ newNodes [$ sourceId ] = [];
470+ $ circular = false ;
471+ foreach ($ edges as $ edge ) {
472+ $ node = $ edge ->getDestNode ();
473+ $ id = $ node ->getId ();
474+ if (!$ node ->getValue () instanceof Definition || $ sourceId === $ id || $ edge ->isLazy () || $ edge ->isWeak ()) {
475+ continue ;
476+ }
475477
476- foreach (array_reverse ($ currentPath ) as $ parentId => $ v ) {
477- $ byConstructor = $ byConstructor && $ v ;
478- $ circularRefs [] = $ parentId ;
478+ if (isset ($ path [$ id ])) {
479+ $ circular = true ;
480+ } elseif (!isset ($ checkedNodes [$ id ])) {
481+ $ circular = $ this ->collectCircularReferences ($ id , $ node ->getOutEdges (), $ checkedNodes , $ newNodes , $ path ) || $ circular ;
482+ }
479483
480- if ($ parentId === $ id ) {
481- break ;
484+ $ checkedNodes [$ sourceId ][$ id ] = [[], $ edge ->isReferencedByConstructor ()];
485+ if (isset ($ newNodes [$ id ])) {
486+ $ newNodes [$ id ][$ sourceId ] = true ;
482487 }
483488 }
489+ unset($ path [$ sourceId ]);
484490
485- $ currentId = $ id ;
486- foreach ($ circularRefs as $ parentId ) {
491+ return $ circular ;
492+ }
493+
494+ private function addCircularReferences (string $ sourceId , array $ currentPath , bool $ byConstructor )
495+ {
496+ $ currentId = $ sourceId ;
497+ $ currentPath = array_reverse ($ currentPath );
498+ $ currentPath [] = $ currentId ;
499+ foreach ($ currentPath as $ parentId ) {
487500 if (empty ($ this ->circularReferences [$ parentId ][$ currentId ])) {
488501 $ this ->circularReferences [$ parentId ][$ currentId ] = $ byConstructor ;
489502 }
0 commit comments