Skip to content

Commit 45bedf3

Browse files
authored
Merge pull request #32 from utopia-php/fix/toarray-recursive-conversion
2 parents 34bc0cd + a9bf079 commit 45bedf3

3 files changed

Lines changed: 129 additions & 2 deletions

File tree

.coderabbit.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
reviews:
2+
path_filters:
3+
- "!dev/**"
4+
- "!docs/**"
5+
auto_review:
6+
base_branches:
7+
- main

src/Client.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,8 +1488,10 @@ public function toArray(mixed $obj): ?array
14881488

14891489
if (is_object($obj) || is_array($obj)) {
14901490
$ret = (array)$obj;
1491-
foreach ($ret as $item) {
1492-
$item = $this->toArray($item);
1491+
foreach ($ret as $key => $item) {
1492+
if ($item instanceof stdClass || is_array($item)) {
1493+
$ret[$key] = $this->toArray($item);
1494+
}
14931495
}
14941496

14951497
return $ret;

tests/MongoTest.php

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,124 @@ public function testUpsert()
271271
self::assertEquals('English', $documents[1]->language);
272272
}
273273

274+
public function testToArrayWithNestedDocumentFromMongo()
275+
{
276+
$client = $this->getDatabase();
277+
278+
// Insert a document with nested object data
279+
$client->insert('movies_nested', [
280+
'_id' => 'nested-test-1',
281+
'title' => 'Inception',
282+
'director' => [
283+
'name' => 'Christopher Nolan',
284+
'born' => 1970,
285+
],
286+
'cast' => [
287+
['name' => 'Leonardo DiCaprio', 'role' => 'Cobb'],
288+
['name' => 'Tom Hardy', 'role' => 'Eames'],
289+
],
290+
]);
291+
292+
// Read back from MongoDB — cursor returns stdClass with nested stdClass
293+
$result = $client->find('movies_nested', ['_id' => 'nested-test-1'])->cursor->firstBatch[0] ?? null;
294+
self::assertNotNull($result);
295+
self::assertInstanceOf(\stdClass::class, $result);
296+
self::assertInstanceOf(\stdClass::class, $result->director);
297+
298+
// Convert via toArray — nested stdClass must become arrays
299+
$array = $client->toArray($result);
300+
self::assertIsArray($array);
301+
self::assertIsArray($array['director']);
302+
self::assertEquals('Christopher Nolan', $array['director']['name']);
303+
self::assertEquals(1970, $array['director']['born']);
304+
self::assertIsArray($array['cast']);
305+
self::assertIsArray($array['cast'][0]);
306+
self::assertEquals('Leonardo DiCaprio', $array['cast'][0]['name']);
307+
self::assertIsArray($array['cast'][1]);
308+
self::assertEquals('Tom Hardy', $array['cast'][1]['name']);
309+
310+
// Also test via lastDocument
311+
$last = $client->lastDocument('movies_nested');
312+
self::assertIsArray($last);
313+
self::assertIsArray($last['director']);
314+
self::assertEquals('Christopher Nolan', $last['director']['name']);
315+
316+
$client->dropCollection('movies_nested');
317+
}
318+
319+
public function testToArrayNestedConversion()
320+
{
321+
$client = $this->getDatabase();
322+
323+
// Nested stdClass (simulates MongoDB BSON result)
324+
$nested = new \stdClass();
325+
$nested->name = 'John';
326+
$nested->age = 30;
327+
328+
$root = new \stdClass();
329+
$root->id = 'doc-1';
330+
$root->author = $nested;
331+
$root->tags = ['php', 'mongo'];
332+
333+
$result = $client->toArray($root);
334+
335+
self::assertIsArray($result);
336+
self::assertEquals('doc-1', $result['id']);
337+
self::assertIsArray($result['author']);
338+
self::assertEquals('John', $result['author']['name']);
339+
self::assertEquals(30, $result['author']['age']);
340+
self::assertIsArray($result['tags']);
341+
342+
// Deeply nested stdClass
343+
$deep = new \stdClass();
344+
$deep->value = 'deep';
345+
346+
$mid = new \stdClass();
347+
$mid->child = $deep;
348+
349+
$top = new \stdClass();
350+
$top->mid = $mid;
351+
352+
$result = $client->toArray($top);
353+
354+
self::assertIsArray($result);
355+
self::assertIsArray($result['mid']);
356+
self::assertIsArray($result['mid']['child']);
357+
self::assertEquals('deep', $result['mid']['child']['value']);
358+
359+
// Non-stdClass objects should NOT be converted
360+
$root = new \stdClass();
361+
$root->created = new \DateTime('2024-01-01');
362+
$root->name = 'test';
363+
364+
$result = $client->toArray($root);
365+
366+
self::assertIsArray($result);
367+
self::assertInstanceOf(\DateTime::class, $result['created']);
368+
369+
// stdClass inside an array (simulates nested documents in a list)
370+
$item = new \stdClass();
371+
$item->id = 'item-1';
372+
$item->label = 'Tag';
373+
374+
$root = new \stdClass();
375+
$root->id = 'doc-5';
376+
$root->items = [$item];
377+
378+
$result = $client->toArray($root);
379+
380+
self::assertIsArray($result);
381+
self::assertIsArray($result['items']);
382+
self::assertIsArray($result['items'][0]);
383+
self::assertEquals('item-1', $result['items'][0]['id']);
384+
385+
// Null handling
386+
self::assertNull($client->toArray(null));
387+
388+
// Scalar wrapping
389+
self::assertEquals([42], $client->toArray(42));
390+
}
391+
274392
public function testCountMethod()
275393
{
276394
$collectionName = 'count_test';

0 commit comments

Comments
 (0)