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 ;
11+ 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 ;
1212use const PREG_SPLIT_DELIM_CAPTURE , STR_PAD_LEFT ;
1313
1414
@@ -23,15 +23,20 @@ final class Dumper
2323 public int $ wrapLength = 120 ;
2424 public string $ indentation = "\t" ;
2525 public bool $ customObjects = true ;
26+ public bool $ references = false ;
2627 public DumpContext $ context = DumpContext::Expression;
2728
29+ /** @var array<string, int> */
30+ private array $ refMap = [];
31+
2832
2933 /**
3034 * Returns a PHP representation of a variable.
3135 */
3236 public function dump (mixed $ var , int $ column = 0 ): string
3337 {
34- return $ this ->dumpVar ($ var , [], 0 , $ column );
38+ return $ this ->dumpReferences ($ var )
39+ ?? $ this ->dumpVar ($ var , column: $ column );
3540 }
3641
3742
@@ -107,7 +112,7 @@ private function dumpArray(array $var, array $parents, int $level, int $column):
107112 if (empty ($ var )) {
108113 return '[] ' ;
109114
110- } elseif ($ level > $ this ->maxDepth || in_array ($ var , $ parents , strict: true )) {
115+ } elseif ($ level > $ this ->maxDepth || ! $ this -> references && in_array ($ var , $ parents , strict: true )) {
111116 throw new Nette \InvalidStateException ('Nesting level too deep or recursive dependency. ' );
112117 }
113118
@@ -119,7 +124,16 @@ private function dumpArray(array $var, array $parents, int $level, int $column):
119124 $ keyPart = $ hideKeys && ($ k !== $ keys [0 ] || $ k === 0 )
120125 ? ''
121126 : $ this ->dumpVar ($ k ) . ' => ' ;
122- $ 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+ }
123137 }
124138
125139 $ line = '[ ' . implode (', ' , $ pairs ) . '] ' ;
@@ -224,6 +238,53 @@ private function dumpLiteral(Literal $var, int $level): string
224238 }
225239
226240
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+
227288 /**
228289 * Generates PHP statement. Supports placeholders: ? \? $? ->? ::? ...? ...?: ?*
229290 */
0 commit comments