@@ -27,11 +27,18 @@ class Exporter_ToYamlArray implements ExporterInterface {
2727 private array $ exportersByClass = [];
2828
2929 /**
30- * Object paths as array keys by object hash .
30+ * Objects to export later .
3131 *
32- * @var array<int, string >
32+ * @var array<int, object >
3333 */
34- private array $ objectPaths = [];
34+ private array $ objects = [];
35+
36+ /**
37+ * Object occurences by object id and depth.
38+ *
39+ * @var array<int, array<int, list<array{key: int|string|null, path: string, ref: mixed}>>>
40+ */
41+ private array $ objectOccurences = [];
3542
3643 /**
3744 * Path in the tree.
@@ -128,55 +135,125 @@ public function withDefaultObjectFactory(string $class, \Closure $factory): stat
128135 */
129136 public function export (mixed $ value , int $ depth = 2 ): mixed {
130137 // Don't pollute the main object cache in the main instance.
131- $ clone = clone $ this ;
132- // Populate the object cache breadth-first.
133- for ($ i = 0 ; $ i < $ depth ; ++$ i ) {
134- $ clone ->exportRecursive ($ value , $ i , null );
135- }
136- $ export = $ clone ->exportRecursive ($ value , $ depth , null );
137- return $ export ;
138+ return (clone $ this )->doExport ($ value , $ depth );
138139 }
139140
140141 /**
141142 * @param mixed $value
142143 * @param int $depth
143- * @param string|int|null $key
144144 *
145145 * @return mixed
146146 */
147- protected function exportRecursive (mixed $ value , int $ depth , string |int |null $ key ): mixed {
148- if (\is_array ($ value )) {
149- if ($ value === []) {
150- return [];
151- }
152- if ($ depth <= 0 ) {
153- if (array_is_list ($ value )) {
154- return '[...] ' ;
155- }
156- else {
157- return '{...} ' ;
147+ public function doExport (mixed $ value , int $ depth ): mixed {
148+ // Populate the object cache breadth-first.
149+ $ export =& $ this ->exportRecursive ($ value , $ depth , null );
150+
151+ $ canonical_object_paths = [];
152+ while ($ objects = $ this ->objects ) {
153+ $ this ->objects = [];
154+ foreach ($ objects as $ id => $ object ) {
155+ if (isset ($ canonical_object_paths [$ id ])) {
156+ continue ;
158157 }
159- }
160- $ result = [];
161- foreach ($ value as $ k => $ v ) {
162- $ parents = $ this ->path ;
163- $ this ->path .= '[ ' . $ k . '] ' ;
158+ $ object_best_depth = max (array_keys ($ this ->objectOccurences [$ id ]));
159+ $ object_best_occurence = array_shift ($ this ->objectOccurences [$ id ][$ object_best_depth ]);
160+ assert ($ object_best_occurence !== null );
161+ // Overwrite the reference.
164162 try {
165- $ result [$ k ] = $ this ->exportRecursive ($ v , $ depth - 1 , $ k );
163+ $ original_path = $ this ->path ;
164+ $ this ->path = $ object_best_occurence ['path ' ];
165+ $ object_best_occurence ['ref ' ] = $ this ->exportObject ($ object , $ object_best_depth , $ object_best_occurence ['key ' ]);
166166 }
167167 finally {
168- $ this ->path = $ parents ;
168+ $ this ->path = $ original_path ;
169+ }
170+ $ canonical_object_paths [$ id ] = $ object_best_occurence ['path ' ];
171+ }
172+ }
173+
174+ foreach ($ this ->objectOccurences as $ id => $ occurences_by_depth ) {
175+ foreach ($ occurences_by_depth as $ occurences ) {
176+ foreach ($ occurences as $ occurence ) {
177+ // Overwrite the reference.
178+ $ occurence ['ref ' ] = ['_ref ' => $ canonical_object_paths [$ id ]];
169179 }
170180 }
181+ }
182+
183+ return $ export ;
184+ }
185+
186+ /**
187+ * @param mixed $value
188+ * @param int $depth
189+ * @param string|int|null $key
190+ *
191+ * @return mixed
192+ */
193+ protected function &exportRecursive (mixed $ value , int $ depth , string |int |null $ key ): mixed {
194+ if (is_array ($ value )) {
195+ $ result = $ this ->exportArrayRecursive ($ value , $ depth );
196+ }
197+ elseif (is_object ($ value )) {
198+ $ result =& $ this ->registerObject ($ value , $ depth , $ key );
199+ }
200+ elseif (\is_resource ($ value )) {
201+ $ result = 'resource ' ;
202+ }
203+ else {
204+ $ result = $ value ;
205+ }
206+
207+ return $ result ;
208+ }
209+
210+ /**
211+ * @param array $value
212+ * @param int $depth
213+ *
214+ * @return mixed
215+ */
216+ protected function exportArrayRecursive (array $ value , int $ depth ): mixed {
217+ if ($ value === []) {
218+ $ result = [];
171219 return $ result ;
172220 }
173- if (is_object ($ value )) {
174- return $ this ->exportObject ($ value , $ depth , $ key );
221+ if ($ depth <= 0 ) {
222+ if (array_is_list ($ value )) {
223+ $ result = '[...] ' ;
224+ }
225+ else {
226+ $ result = '{...} ' ;
227+ }
228+ return $ result ;
175229 }
176- if (\is_resource ($ value )) {
177- return 'resource ' ;
230+ $ result = [];
231+ foreach ($ value as $ k => $ v ) {
232+ $ parents = $ this ->path ;
233+ $ this ->path .= '[ ' . $ k . '] ' ;
234+ try {
235+ $ result [$ k ] =& $ this ->exportRecursive ($ v , $ depth - 1 , $ k );
236+ }
237+ finally {
238+ $ this ->path = $ parents ;
239+ }
178240 }
179- return $ value ;
241+ return $ result ;
242+ }
243+
244+ /**
245+ * @param object $object
246+ * @param int $depth
247+ * @param string|int|null $key
248+ *
249+ * @return int
250+ */
251+ protected function ®isterObject (object $ object , int $ depth , string |int |null $ key ): int {
252+ $ id = spl_object_id ($ object );
253+ $ this ->objects [$ id ] = $ object ;
254+ $ ref = $ id ;
255+ $ this ->objectOccurences [$ id ][$ depth ][] = ['key ' => $ key , 'path ' => $ this ->path , 'ref ' => &$ ref ];
256+ return $ ref ;
180257 }
181258
182259 /**
@@ -187,14 +264,6 @@ protected function exportRecursive(mixed $value, int $depth, string|int|null $ke
187264 * @return mixed
188265 */
189266 protected function exportObject (object $ object , int $ depth , int |string |null $ key = null ): mixed {
190- $ id = spl_object_id ($ object );
191- $ known_path = $ this ->objectPaths [$ id ] ?? null ;
192- if ($ known_path === NULL ) {
193- $ this ->objectPaths [$ id ] = $ this ->path ;
194- }
195- elseif ($ known_path !== $ this ->path ) {
196- return ['_ref ' => $ known_path ];
197- }
198267 foreach ($ this ->exportersByClass as $ class => $ callback ) {
199268 if ($ object instanceof $ class ) {
200269 $ export = $ callback ($ object , $ depth , $ key , $ this );
@@ -402,7 +471,7 @@ protected function exportObjectProperties(object $object, int $depth, bool $publ
402471 $ parents = $ this ->path ;
403472 $ this ->path .= '-> ' . $ property ->name ;
404473 try {
405- $ export ['$ ' . $ property ->name ] = $ this ->exportRecursive ($ propertyValue , $ depth - 1 , null );
474+ $ export ['$ ' . $ property ->name ] =& $ this ->exportRecursive ($ propertyValue , $ depth - 1 , null );
406475 }
407476 finally {
408477 $ this ->path = $ parents ;
@@ -433,7 +502,7 @@ protected function exportObjectGetterValues(object $object, int $depth): array {
433502 $ parents = $ this ->path ;
434503 $ this ->path .= '-> ' . $ method ->name . '() ' ;
435504 try {
436- $ result [$ method ->name . '() ' ] = $ this ->exportRecursive ($ value , $ depth - 1 , null );
505+ $ result [$ method ->name . '() ' ] =& $ this ->exportRecursive ($ value , $ depth - 1 , null );
437506 }
438507 finally {
439508 $ this ->path = $ parents ;
0 commit comments