1010namespace Nette \PhpGenerator ;
1111
1212use Nette ;
13- 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 ;
13+ use function addcslashes , array_filter , 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 ;
1414use const PREG_SPLIT_DELIM_CAPTURE , STR_PAD_LEFT ;
1515
1616
@@ -25,15 +25,20 @@ final class Dumper
2525 public int $ wrapLength = 120 ;
2626 public string $ indentation = "\t" ;
2727 public bool $ customObjects = true ;
28+ public bool $ references = false ;
2829 public DumpContext $ context = DumpContext::Expression;
2930
31+ /** @var array<string, int> */
32+ private array $ refMap = [];
33+
3034
3135 /**
3236 * Returns a PHP representation of a variable.
3337 */
3438 public function dump (mixed $ var , int $ column = 0 ): string
3539 {
36- return $ this ->dumpVar ($ var , [], 0 , $ column );
40+ return $ this ->dumpReferences ($ var )
41+ ?? $ this ->dumpVar ($ var , column: $ column );
3742 }
3843
3944
@@ -109,7 +114,7 @@ private function dumpArray(array $var, array $parents, int $level, int $column):
109114 if (empty ($ var )) {
110115 return '[] ' ;
111116
112- } elseif ($ level > $ this ->maxDepth || in_array ($ var , $ parents , strict: true )) {
117+ } elseif ($ level > $ this ->maxDepth || ! $ this -> references && in_array ($ var , $ parents , strict: true )) {
113118 throw new Nette \InvalidStateException ('Nesting level too deep or recursive dependency. ' );
114119 }
115120
@@ -121,7 +126,16 @@ private function dumpArray(array $var, array $parents, int $level, int $column):
121126 $ keyPart = $ hideKeys && ($ k !== $ keys [0 ] || $ k === 0 )
122127 ? ''
123128 : $ this ->dumpVar ($ k ) . ' => ' ;
124- $ pairs [] = $ keyPart . $ this ->dumpVar ($ v , $ parents , $ level + 1 , strlen ($ keyPart ) + 1 ); // 1 = comma after item
129+
130+ if (
131+ $ this ->references
132+ && ($ refId = (\ReflectionReference::fromArrayElement ($ var , $ k ))?->getId())
133+ && isset ($ this ->refMap [$ refId ])
134+ ) {
135+ $ pairs [] = $ keyPart . '&$r[ ' . $ this ->refMap [$ refId ] . '] ' ;
136+ } else {
137+ $ pairs [] = $ keyPart . $ this ->dumpVar ($ v , $ parents , $ level + 1 , strlen ($ keyPart ) + 1 ); // 1 = comma after item
138+ }
125139 }
126140
127141 $ line = '[ ' . implode (', ' , $ pairs ) . '] ' ;
@@ -226,6 +240,53 @@ private function dumpLiteral(Literal $var, int $level): string
226240 }
227241
228242
243+ private function dumpReferences (mixed $ var ): ?string
244+ {
245+ $ this ->refMap = $ refs = [];
246+ if (!$ this ->references || !is_array ($ var )) {
247+ return null ;
248+ }
249+
250+ $ this ->collectReferences ($ var , $ refs );
251+ $ refs = array_filter ($ refs , fn ($ ref ) => $ ref [0 ] >= 2 );
252+ if (!$ refs ) {
253+ return null ;
254+ }
255+
256+ $ n = 0 ;
257+ foreach ($ refs as $ refId => $ _ ) {
258+ $ this ->refMap [$ refId ] = ++$ n ;
259+ }
260+
261+ $ preamble = '' ;
262+ foreach ($ this ->refMap as $ refId => $ n ) {
263+ $ preamble .= '$r[ ' . $ n . '] = ' . $ this ->dumpVar ($ refs [$ refId ][1 ]) . '; ' ;
264+ }
265+
266+ return '(static function () { ' . $ preamble . 'return ' . $ this ->dumpVar ($ var ) . '; })() ' ;
267+ }
268+
269+
270+ /**
271+ * @param mixed[] $var
272+ * @param array<string, array{int, mixed}> $refs
273+ */
274+ private function collectReferences (array $ var , array &$ refs ): void
275+ {
276+ foreach ($ var as $ k => $ v ) {
277+ $ refId = (\ReflectionReference::fromArrayElement ($ var , $ k ))?->getId();
278+ if ($ refId !== null ) {
279+ $ refs [$ refId ] ??= [0 , $ v ];
280+ $ refs [$ refId ][0 ]++;
281+ }
282+
283+ if (is_array ($ v ) && ($ refId === null || $ refs [$ refId ][0 ] === 1 )) {
284+ $ this ->collectReferences ($ v , $ refs );
285+ }
286+ }
287+ }
288+
289+
229290 /**
230291 * Generates PHP statement. Supports placeholders: ? \? $? ->? ::? ...? ...?: ?*
231292 */
0 commit comments