88namespace Nette \PhpGenerator ;
99
1010use Nette ;
11- use function addcslashes , array_keys , array_shift , count , dechex , get_mangled_object_vars , implode , in_array , is_array , is_int , is_object , is_resource , is_string , ltrim , method_exists , ord , preg_match , preg_replace , preg_replace_callback , preg_split , range , serialize , str_contains , str_pad , str_repeat , str_replace , strlen , strrpos , strtoupper , substr , trim , unserialize , var_export ;
12- use const PREG_SPLIT_DELIM_CAPTURE , STR_PAD_LEFT ;
11+ use function addcslashes , array_filter , array_keys , array_shift , count , dechex , implode , in_array , is_array , is_int , is_object , is_resource , is_string , ltrim , method_exists , ord , preg_match , preg_replace , preg_replace_callback , preg_split , range , serialize , str_contains , str_pad , str_repeat , str_replace , strlen , strrpos , strtoupper , substr , trim , unserialize , var_export ;
1312
1413
1514/**
@@ -23,16 +22,21 @@ final class Dumper
2322 public int $ wrapLength = 120 ;
2423 public string $ indentation = "\t" ;
2524 public bool $ customObjects = true ;
25+ public bool $ references = false ;
2626 public DumpContext $ context = DumpContext::Expression;
2727
28+ /** @var array<string, int> */
29+ private array $ refMap = [];
30+
2831
2932 /**
3033 * Converts a value to its PHP code representation.
3134 * @param int $column current column for array wrapping decisions
3235 */
3336 public function dump (mixed $ var , int $ column = 0 ): string
3437 {
35- return $ this ->dumpVar ($ var , [], 0 , $ column );
38+ return $ this ->dumpReferences ($ var )
39+ ?? $ this ->dumpVar ($ var , column: $ column );
3640 }
3741
3842
@@ -108,7 +112,7 @@ private function dumpArray(array $var, array $parents, int $level, int $column):
108112 if (empty ($ var )) {
109113 return '[] ' ;
110114
111- } elseif ($ level > $ this ->maxDepth || in_array ($ var , $ parents , strict: true )) {
115+ } elseif ($ level > $ this ->maxDepth || ! $ this -> references && in_array ($ var , $ parents , strict: true )) {
112116 throw new Nette \InvalidStateException ('Nesting level too deep or recursive dependency. ' );
113117 }
114118
@@ -120,7 +124,16 @@ private function dumpArray(array $var, array $parents, int $level, int $column):
120124 $ keyPart = $ hideKeys && ($ k !== $ keys [0 ] || $ k === 0 )
121125 ? ''
122126 : $ this ->dumpVar ($ k ) . ' => ' ;
123- $ pairs [] = $ keyPart . $ this ->dumpVar ($ v , $ parents , $ level + 1 , strlen ($ keyPart ) + 1 ); // 1 = comma after item
127+
128+ if (
129+ $ this ->references
130+ && ($ refId = (\ReflectionReference::fromArrayElement ($ var , $ k ))?->getId())
131+ && isset ($ this ->refMap [$ refId ])
132+ ) {
133+ $ pairs [] = $ keyPart . '&$r[ ' . $ this ->refMap [$ refId ] . '] ' ;
134+ } else {
135+ $ pairs [] = $ keyPart . $ this ->dumpVar ($ v , $ parents , $ level + 1 , strlen ($ keyPart ) + 1 ); // 1 = comma after item
136+ }
124137 }
125138
126139 $ line = '[ ' . implode (', ' , $ pairs ) . '] ' ;
@@ -225,6 +238,53 @@ private function dumpLiteral(Literal $var, int $level): string
225238 }
226239
227240
241+ private function dumpReferences (mixed $ var ): ?string
242+ {
243+ $ this ->refMap = $ refs = [];
244+ if (!$ this ->references || !is_array ($ var )) {
245+ return null ;
246+ }
247+
248+ $ this ->collectReferences ($ var , $ refs );
249+ $ refs = array_filter ($ refs , fn ($ ref ) => $ ref [0 ] >= 2 );
250+ if (!$ refs ) {
251+ return null ;
252+ }
253+
254+ $ n = 0 ;
255+ foreach ($ refs as $ refId => $ _ ) {
256+ $ this ->refMap [$ refId ] = ++$ n ;
257+ }
258+
259+ $ preamble = '' ;
260+ foreach ($ this ->refMap as $ refId => $ n ) {
261+ $ preamble .= '$r[ ' . $ n . '] = ' . $ this ->dumpVar ($ refs [$ refId ][1 ]) . '; ' ;
262+ }
263+
264+ return '(static function () { ' . $ preamble . 'return ' . $ this ->dumpVar ($ var ) . '; })() ' ;
265+ }
266+
267+
268+ /**
269+ * @param mixed[] $var
270+ * @param array<string, array{int, mixed}> $refs
271+ */
272+ private function collectReferences (array $ var , array &$ refs ): void
273+ {
274+ foreach ($ var as $ k => $ v ) {
275+ $ refId = (\ReflectionReference::fromArrayElement ($ var , $ k ))?->getId();
276+ if ($ refId !== null ) {
277+ $ refs [$ refId ] ??= [0 , $ v ];
278+ $ refs [$ refId ][0 ]++;
279+ }
280+
281+ if (is_array ($ v ) && ($ refId === null || $ refs [$ refId ][0 ] === 1 )) {
282+ $ this ->collectReferences ($ v , $ refs );
283+ }
284+ }
285+ }
286+
287+
228288 /**
229289 * Formats a PHP expression using placeholders.
230290 * Supported placeholders: ? (value), \? (literal ?), $? (variable), ->? (property access),
0 commit comments