@@ -180,25 +180,7 @@ public function dump(array $options = [])
180180 }
181181 }
182182
183- (new AnalyzeServiceReferencesPass (false , !$ this ->getProxyDumper () instanceof NullDumper))->process ($ this ->container );
184- $ checkedNodes = [];
185- $ this ->circularReferences = [];
186- $ this ->singleUsePrivateIds = [];
187- foreach ($ this ->container ->getCompiler ()->getServiceReferenceGraph ()->getNodes () as $ id => $ node ) {
188- if (!$ node ->getValue () instanceof Definition) {
189- continue ;
190- }
191- if (!isset ($ checkedNodes [$ id ])) {
192- $ this ->analyzeCircularReferences ($ id , $ node ->getOutEdges (), $ checkedNodes );
193- }
194- if ($ this ->isSingleUsePrivateNode ($ node )) {
195- $ this ->singleUsePrivateIds [$ id ] = $ id ;
196- }
197- }
198- $ this ->container ->getCompiler ()->getServiceReferenceGraph ()->clear ();
199- $ checkedNodes = [];
200- $ this ->singleUsePrivateIds = array_diff_key ($ this ->singleUsePrivateIds , $ this ->circularReferences );
201-
183+ $ this ->analyzeReferences ();
202184 $ this ->docStar = $ options ['debug ' ] ? '* ' : '' ;
203185
204186 if (!empty ($ options ['file ' ]) && is_dir ($ dir = \dirname ($ options ['file ' ]))) {
@@ -409,58 +391,92 @@ private function getProxyDumper(): ProxyDumper
409391 return $ this ->proxyDumper ;
410392 }
411393
412- private function analyzeCircularReferences ( string $ sourceId , array $ edges , array & $ checkedNodes , array & $ currentPath = [], bool $ byConstructor = true )
394+ private function analyzeReferences ( )
413395 {
414- $ checkedNodes [$ sourceId ] = true ;
415- $ currentPath [$ sourceId ] = $ byConstructor ;
396+ (new AnalyzeServiceReferencesPass (false , !$ this ->getProxyDumper () instanceof NullDumper))->process ($ this ->container );
397+ $ checkedNodes = [];
398+ $ this ->circularReferences = [];
399+ $ this ->singleUsePrivateIds = [];
400+ foreach ($ this ->container ->getCompiler ()->getServiceReferenceGraph ()->getNodes () as $ id => $ node ) {
401+ if (!$ node ->getValue () instanceof Definition) {
402+ continue ;
403+ }
416404
417- foreach ( $ edges as $ edge ) {
418- $ node = $ edge -> getDestNode () ;
419- $ id = $ node ->getId ( );
405+ $ circular = false ;
406+ $ newNodes = [] ;
407+ $ this -> collectReferences ( $ id , $ node ->getOutEdges (), $ checkedNodes , $ newNodes , $ circular );
420408
421- if (!$ node ->getValue () instanceof Definition || $ sourceId === $ id || $ edge ->isLazy () || $ edge ->isWeak ()) {
422- // no-op
423- } elseif (isset ($ currentPath [$ id ])) {
424- $ this ->addCircularReferences ($ id , $ currentPath , $ edge ->isReferencedByConstructor ());
425- } elseif (!isset ($ checkedNodes [$ id ])) {
426- $ this ->analyzeCircularReferences ($ id , $ node ->getOutEdges (), $ checkedNodes , $ currentPath , $ edge ->isReferencedByConstructor ());
427- } elseif (isset ($ this ->circularReferences [$ id ])) {
428- $ this ->connectCircularReferences ($ id , $ currentPath , $ edge ->isReferencedByConstructor ());
429- }
430- }
431- unset($ currentPath [$ sourceId ]);
432- }
409+ if ($ circular ) {
410+ $ nodesToFlatten = $ newNodes ;
411+ do {
412+ $ changedNodes = [];
413+ foreach ($ nodesToFlatten as $ newNodeId => $ _ ) {
414+ $ deps = &$ checkedNodes [$ newNodeId ];
415+ foreach ($ deps as $ id => [$ path , $ depsByConstructor ]) {
416+ foreach ($ checkedNodes [$ id ] ?: [] as $ depsId => [$ subPath , $ subDepsByConstructor ]) {
417+ if (!isset ($ deps [$ depsId ]) || ($ depsByConstructor && $ subDepsByConstructor && !$ deps [$ depsId ][1 ])) {
418+ array_unshift ($ subPath , $ id );
419+ $ deps [$ depsId ] = [$ subPath , $ depsByConstructor && $ subDepsByConstructor ];
420+ $ changedNodes += $ newNodes [$ newNodeId ] ?? [];
421+ }
422+ }
423+ }
424+ }
425+ } while ($ nodesToFlatten = $ changedNodes );
433426
434- private function connectCircularReferences (string $ sourceId , array &$ currentPath , bool $ byConstructor , array &$ subPath = [])
435- {
436- $ currentPath [$ sourceId ] = $ subPath [$ sourceId ] = $ byConstructor ;
427+ foreach ($ newNodes as $ newNodeId => $ _ ) {
428+ if (null !== $ n = $ checkedNodes [$ newNodeId ][$ newNodeId ] ?? null ) {
429+ $ this ->addCircularReferences ($ newNodeId , $ n [0 ], $ n [1 ]);
430+ }
431+ }
432+ } else {
433+ foreach ($ newNodes as $ newNode => $ _ ) {
434+ $ checkedNodes [$ newNode ] = false ;
435+ }
436+ }
437437
438- foreach ($ this ->circularReferences [$ sourceId ] as $ id => $ byConstructor ) {
439- if (isset ($ currentPath [$ id ])) {
440- $ this ->addCircularReferences ($ id , $ currentPath , $ byConstructor );
441- } elseif (!isset ($ subPath [$ id ]) && isset ($ this ->circularReferences [$ id ])) {
442- $ this ->connectCircularReferences ($ id , $ currentPath , $ byConstructor , $ subPath );
438+ if ($ this ->isSingleUsePrivateNode ($ node )) {
439+ $ this ->singleUsePrivateIds [$ id ] = $ id ;
443440 }
444441 }
445- unset($ currentPath [$ sourceId ], $ subPath [$ sourceId ]);
442+
443+ $ this ->container ->getCompiler ()->getServiceReferenceGraph ()->clear ();
444+ $ this ->singleUsePrivateIds = array_diff_key ($ this ->singleUsePrivateIds , $ this ->circularReferences );
446445 }
447446
448- private function addCircularReferences (string $ id , array $ currentPath , bool $ byConstructor )
447+ private function collectReferences (string $ sourceId , array $ edges , array & $ checkedNodes , array & $ newNodes , bool & $ circular , array $ path = [] )
449448 {
450- $ currentPath [$ id ] = $ byConstructor ;
451- $ circularRefs = [];
449+ $ path [$ sourceId ] = true ;
450+ $ checkedNodes [$ sourceId ] = [];
451+ $ newNodes [$ sourceId ] = [];
452+ foreach ($ edges as $ edge ) {
453+ $ node = $ edge ->getDestNode ();
454+ $ id = $ node ->getId ();
455+ if (!$ node ->getValue () instanceof Definition || $ sourceId === $ id || $ edge ->isLazy () || $ edge ->isWeak ()) {
456+ continue ;
457+ }
452458
453- foreach (array_reverse ($ currentPath ) as $ parentId => $ v ) {
454- $ byConstructor = $ byConstructor && $ v ;
455- $ circularRefs [] = $ parentId ;
459+ if (isset ($ path [$ id ])) {
460+ $ circular = true ;
461+ }
462+ if (!isset ($ checkedNodes [$ id ])) {
463+ $ this ->collectReferences ($ id , $ node ->getOutEdges (), $ checkedNodes , $ newNodes , $ circular , $ path );
464+ }
456465
457- if ($ parentId === $ id ) {
458- break ;
466+ $ checkedNodes [$ sourceId ][$ id ] = [[], $ edge ->isReferencedByConstructor ()];
467+ if (isset ($ newNodes [$ id ])) {
468+ $ newNodes [$ id ][$ sourceId ] = true ;
459469 }
460470 }
471+ unset($ path [$ sourceId ]);
472+ }
461473
462- $ currentId = $ id ;
463- foreach ($ circularRefs as $ parentId ) {
474+ private function addCircularReferences (string $ sourceId , array $ currentPath , bool $ byConstructor )
475+ {
476+ $ currentId = $ sourceId ;
477+ $ currentPath = array_reverse ($ currentPath );
478+ $ currentPath [] = $ currentId ;
479+ foreach ($ currentPath as $ parentId ) {
464480 if (empty ($ this ->circularReferences [$ parentId ][$ currentId ])) {
465481 $ this ->circularReferences [$ parentId ][$ currentId ] = $ byConstructor ;
466482 }
0 commit comments