Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 38dc1e3

Browse files
authored
PHPORM-239 Convert _id and UTCDateTime in results of Model::raw() before hydratation (#3152)
1 parent d6ac34a commit 38dc1e3

File tree

5 files changed

+106
-18
lines changed

5 files changed

+106
-18
lines changed

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Changelog
22
All notable changes to this project will be documented in this file.
33

4+
## [5.1.0] - next
5+
6+
* Convert `_id` and `UTCDateTime` in results of `Model::raw()` before hydratation by @GromNaN in [#3152](https://github.com/mongodb/laravel-mongodb/pull/3152)
7+
48
## [5.0.2] - 2024年09月17日
59

610
* Fix missing return types in CommandSubscriber by @GromNaN in [#3158](https://github.com/mongodb/laravel-mongodb/pull/3158)

‎src/Eloquent/Builder.php‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
namespace MongoDB\Laravel\Eloquent;
66

77
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
8-
use MongoDB\Driver\Cursor;
8+
use MongoDB\BSON\Document;
9+
use MongoDB\Driver\CursorInterface;
910
use MongoDB\Driver\Exception\WriteException;
1011
use MongoDB\Laravel\Connection;
1112
use MongoDB\Laravel\Helpers\QueriesRelationships;
@@ -16,7 +17,9 @@
1617
use function array_merge;
1718
use function collect;
1819
use function is_array;
20+
use function is_object;
1921
use function iterator_to_array;
22+
use function property_exists;
2023

2124
/** @method \MongoDB\Laravel\Query\Builder toBase() */
2225
class Builder extends EloquentBuilder
@@ -177,22 +180,27 @@ public function raw($value = null)
177180
$results = $this->query->raw($value);
178181

179182
// Convert MongoCursor results to a collection of models.
180-
if ($results instanceof Cursor) {
181-
$results = iterator_to_array($results, false);
183+
if ($results instanceof CursorInterface) {
184+
$results->setTypeMap(['root' => 'array', 'document' => 'array', 'array' => 'array']);
185+
$results = $this->query->aliasIdForResult(iterator_to_array($results));
182186

183187
return $this->model->hydrate($results);
184188
}
185189

186-
// Convert MongoDB BSONDocument to a single object.
187-
if ($results instanceof BSONDocument) {
188-
$results = $results->getArrayCopy();
189-
190-
return $this->model->newFromBuilder((array) $results);
190+
// Convert MongoDB Document to a single object.
191+
if (is_object($results) && (property_exists($results, '_id') || property_exists($results, 'id'))) {
192+
$results = (array) match (true) {
193+
$results instanceof BSONDocument => $results->getArrayCopy(),
194+
$results instanceof Document => $results->toPHP(['root' => 'array', 'document' => 'array', 'array' => 'array']),
195+
default => $results,
196+
};
191197
}
192198

193199
// The result is a single object.
194-
if (is_array($results) && array_key_exists('_id', $results)) {
195-
return $this->model->newFromBuilder((array) $results);
200+
if (is_array($results) && (array_key_exists('_id', $results) || array_key_exists('id', $results))) {
201+
$results = $this->query->aliasIdForResult($results);
202+
203+
return $this->model->newFromBuilder($results);
196204
}
197205

198206
return $results;

‎src/Query/Builder.php‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1648,13 +1648,15 @@ private function aliasIdForQuery(array $values): array
16481648
}
16491649

16501650
/**
1651+
* @internal
1652+
*
16511653
* @psalm-param T $values
16521654
*
16531655
* @psalm-return T
16541656
*
16551657
* @template T of array|object
16561658
*/
1657-
private function aliasIdForResult(array|object $values): array|object
1659+
public function aliasIdForResult(array|object $values): array|object
16581660
{
16591661
if (is_array($values)) {
16601662
if (array_key_exists('_id', $values) && ! array_key_exists('id', $values)) {

‎tests/ModelTest.php‎

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
use MongoDB\Laravel\Tests\Models\Soft;
2929
use MongoDB\Laravel\Tests\Models\SqlUser;
3030
use MongoDB\Laravel\Tests\Models\User;
31+
use MongoDB\Model\BSONArray;
32+
use MongoDB\Model\BSONDocument;
3133
use PHPUnit\Framework\Attributes\DataProvider;
3234
use PHPUnit\Framework\Attributes\TestWith;
3335

@@ -907,14 +909,8 @@ public function testRaw(): void
907909
$this->assertInstanceOf(EloquentCollection::class, $users);
908910
$this->assertInstanceOf(User::class, $users[0]);
909911

910-
$user = User::raw(function (Collection $collection) {
911-
return $collection->findOne(['age' => 35]);
912-
});
913-
914-
$this->assertTrue(Model::isDocumentModel($user));
915-
916912
$count = User::raw(function (Collection $collection) {
917-
return $collection->count();
913+
return $collection->estimatedDocumentCount();
918914
});
919915
$this->assertEquals(3, $count);
920916

@@ -924,6 +920,59 @@ public function testRaw(): void
924920
$this->assertNotNull($result);
925921
}
926922

923+
#[DataProvider('provideTypeMap')]
924+
public function testRawHyradeModel(array $typeMap): void
925+
{
926+
User::insert([
927+
['name' => 'John Doe', 'age' => 35, 'embed' => ['foo' => 'bar'], 'list' => [1, 2, 3]],
928+
['name' => 'Jane Doe', 'age' => 35, 'embed' => ['foo' => 'bar'], 'list' => [1, 2, 3]],
929+
['name' => 'Harry Hoe', 'age' => 15, 'embed' => ['foo' => 'bar'], 'list' => [1, 2, 3]],
930+
]);
931+
932+
// Single document result
933+
$user = User::raw(fn (Collection $collection) => $collection->findOne(
934+
['age' => 35],
935+
[
936+
'projection' => ['_id' => 1, 'name' => 1, 'age' => 1, 'now' => '$$NOW', 'embed' => 1, 'list' => 1],
937+
'typeMap' => $typeMap,
938+
],
939+
));
940+
941+
$this->assertInstanceOf(User::class, $user);
942+
$this->assertArrayNotHasKey('_id', $user->getAttributes());
943+
$this->assertArrayHasKey('id', $user->getAttributes());
944+
$this->assertNotEmpty($user->id);
945+
$this->assertInstanceOf(Carbon::class, $user->now);
946+
$this->assertEquals(['foo' => 'bar'], (array) $user->embed);
947+
$this->assertEquals([1, 2, 3], (array) $user->list);
948+
949+
// Cursor result
950+
$result = User::raw(fn (Collection $collection) => $collection->aggregate([
951+
['$set' => ['now' => '$$NOW']],
952+
['$limit' => 2],
953+
], ['typeMap' => $typeMap]));
954+
955+
$this->assertInstanceOf(EloquentCollection::class, $result);
956+
$this->assertCount(2, $result);
957+
$user = $result->first();
958+
$this->assertInstanceOf(User::class, $user);
959+
$this->assertArrayNotHasKey('_id', $user->getAttributes());
960+
$this->assertArrayHasKey('id', $user->getAttributes());
961+
$this->assertNotEmpty($user->id);
962+
$this->assertInstanceOf(Carbon::class, $user->now);
963+
$this->assertEquals(['foo' => 'bar'], $user->embed);
964+
$this->assertEquals([1, 2, 3], $user->list);
965+
}
966+
967+
public static function provideTypeMap(): Generator
968+
{
969+
yield 'default' => [[]];
970+
yield 'array' => [['root' => 'array', 'document' => 'array', 'array' => 'array']];
971+
yield 'object' => [['root' => 'object', 'document' => 'object', 'array' => 'array']];
972+
yield 'Library BSON' => [['root' => BSONDocument::class, 'document' => BSONDocument::class, 'array' => BSONArray::class]];
973+
yield 'Driver BSON' => [['root' => 'bson', 'document' => 'bson', 'array' => 'bson']];
974+
}
975+
927976
public function testDotNotation(): void
928977
{
929978
$user = User::create([

‎tests/Query/BuilderTest.php‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,6 +1381,31 @@ function (Builder $elemMatchQuery): void {
13811381
->orWhereAny(['last_name', 'email'], 'not like', '%Doe%'),
13821382
'orWhereAny',
13831383
];
1384+
1385+
yield 'raw filter with _id and date' => [
1386+
[
1387+
'find' => [
1388+
[
1389+
'$and' => [
1390+
[
1391+
'$or' => [
1392+
['foo._id' => 1],
1393+
['created_at' => ['$gte' => new UTCDateTime(new DateTimeImmutable('2018年09月30日 00:00:00.000 +00:00'))]],
1394+
],
1395+
],
1396+
['age' => 15],
1397+
],
1398+
],
1399+
[], // options
1400+
],
1401+
],
1402+
fn (Builder $builder) => $builder->where([
1403+
'$or' => [
1404+
['foo.id' => 1],
1405+
['created_at' => ['$gte' => new DateTimeImmutable('2018年09月30日 00:00:00 +00:00')]],
1406+
],
1407+
])->where('age', 15),
1408+
];
13841409
}
13851410

13861411
#[DataProvider('provideExceptions')]

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /