Skip to content

Commit 533d1b7

Browse files
committed
V5
Query Builder return stdClass: https://github.com/laravel/framework/blob/213a370b703592587bafcd52d38a0ad772ff7442/src/Illuminate/Database/Connection.php#L118C16-L118C25 Alias id for _id everywhere Don't expose _id field: No _id attribute in Eloquent. Deprecate reading and writing _id attribute. Always convert datetime to UTCDateTime: https://github.com/laravel/framework/blob/213a370b703592587bafcd52d38a0ad772ff7442/src/Illuminate/Database/Connection.php#L733-L734 Timezone : using Carbon instead of system timezone.
1 parent 6a5124f commit 533d1b7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+585
-676
lines changed

docs/includes/fundamentals/aggregation/AggregationsBuilderTest.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function testAggregationBuilderSortStage(): void
6868
// end aggregation sort stage
6969

7070
$this->assertEquals(6, $result->count());
71-
$this->assertEquals('Janet Doe', $result->first()['name']);
71+
$this->assertEquals('Janet Doe', $result->first()->name);
7272
}
7373

7474
public function testAggregationBuilderProjectStage(): void
@@ -80,8 +80,9 @@ public function testAggregationBuilderProjectStage(): void
8080
// end aggregation project stage
8181

8282
$this->assertEquals(6, $result->count());
83-
$this->assertNotNull($result->first()['name']);
84-
$this->assertArrayNotHasKey('_id', $result->first());
83+
$this->assertNotNull($result->first()->name);
84+
$this->assertObjectNotHasProperty('_id', $result->first());
85+
$this->assertObjectNotHasProperty('id', $result->first());
8586
}
8687

8788
public function testAggregationBuilderPipeline(): void
@@ -104,7 +105,7 @@ public function testAggregationBuilderPipeline(): void
104105
$result = $pipeline->get();
105106

106107
$this->assertEquals(2, $result->count());
107-
$this->assertNotNull($result->first()['birth_year_avg']);
108+
$this->assertNotNull($result->first()->birth_year_avg);
108109
}
109110

110111
// phpcs:disable Squiz.Commenting.FunctionComment.WrongStyle
@@ -130,6 +131,6 @@ public function testCustomOperatorFactory(): void
130131
$result = $pipeline->get();
131132

132133
$this->assertEquals(6, $result->count());
133-
$this->assertNotNull($result->first()['birth_year']);
134+
$this->assertNotNull($result->first()->birth_year);
134135
}
135136
}

src/Auth/User.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@ class User extends BaseUser
1111
{
1212
use DocumentModel;
1313

14-
protected $primaryKey = '_id';
1514
protected $keyType = 'string';
1615
}

src/Collection.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Exception;
88
use MongoDB\BSON\ObjectID;
9+
use MongoDB\BSON\UTCDateTime;
910
use MongoDB\Collection as MongoCollection;
1011

1112
use function array_walk_recursive;

src/Eloquent/Builder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public function raw($value = null)
191191
}
192192

193193
// The result is a single object.
194-
if (is_array($results) && array_key_exists('_id', $results)) {
194+
if (is_array($results) && array_key_exists('id', $results)) {
195195
return $this->model->newFromBuilder((array) $results);
196196
}
197197

src/Eloquent/DocumentModel.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ public function getIdAttribute($value = null)
7676
{
7777
// If we don't have a value for 'id', we will use the MongoDB '_id' value.
7878
// This allows us to work with models in a more sql-like way.
79-
if (! $value && array_key_exists('_id', $this->attributes)) {
80-
$value = $this->attributes['_id'];
79+
if (! $value && array_key_exists('id', $this->attributes)) {
80+
$value = $this->attributes['id'];
8181
}
8282

8383
// Convert ObjectID to string.
@@ -238,8 +238,8 @@ public function setAttribute($key, $value)
238238
};
239239
}
240240

241-
// Convert _id to ObjectID.
242-
if ($key === '_id' && is_string($value)) {
241+
// Convert "id" to ObjectID.
242+
if ($key === 'id' && is_string($value)) {
243243
$builder = $this->newBaseQueryBuilder();
244244

245245
$value = $builder->convertKey($value);
@@ -721,9 +721,9 @@ protected function isBSON(mixed $value): bool
721721
public function save(array $options = [])
722722
{
723723
// SQL databases would use autoincrement the id field if set to null.
724-
// Apply the same behavior to MongoDB with _id only, otherwise null would be stored.
725-
if (array_key_exists('_id', $this->attributes) && $this->attributes['_id'] === null) {
726-
unset($this->attributes['_id']);
724+
// Apply the same behavior to MongoDB with "id" only, otherwise null would be stored.
725+
if (array_key_exists('id', $this->attributes) && $this->attributes['id'] === null) {
726+
unset($this->attributes['id']);
727727
}
728728

729729
$saved = parent::save($options);

src/Eloquent/Model.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ abstract class Model extends BaseModel
1616
{
1717
use DocumentModel;
1818

19-
/**
20-
* The primary key for the model.
21-
*
22-
* @var string
23-
*/
24-
protected $primaryKey = '_id';
25-
2619
/**
2720
* The primary key type.
2821
*

src/MongoDBQueueServiceProvider.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace MongoDB\Laravel;
66

7+
use Illuminate\Queue\Failed\DatabaseFailedJobProvider;
78
use Illuminate\Queue\Failed\NullFailedJobProvider;
89
use Illuminate\Queue\QueueServiceProvider;
910
use MongoDB\Laravel\Queue\Failed\MongoFailedJobProvider;
@@ -52,14 +53,14 @@ protected function registerFailedJobServices()
5253
/**
5354
* Create a new MongoDB failed job provider.
5455
*/
55-
protected function mongoFailedJobProvider(array $config): MongoFailedJobProvider
56+
protected function mongoFailedJobProvider(array $config): DatabaseFailedJobProvider
5657
{
5758
if (! isset($config['collection']) && isset($config['table'])) {
5859
trigger_error('Since mongodb/laravel-mongodb 4.4: Using "table" option for the queue is deprecated. Use "collection" instead.', E_USER_DEPRECATED);
5960
$config['collection'] = $config['table'];
6061
}
6162

62-
return new MongoFailedJobProvider(
63+
return new DatabaseFailedJobProvider(
6364
$this->app['db'],
6465
$config['database'] ?? null,
6566
$config['collection'] ?? 'failed_jobs',

src/Query/AggregationBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ private function execute(array $options): CursorInterface&Iterator
8888
$pipeline = $encoder->encode($this->getPipeline());
8989

9090
$options = array_replace(
91-
['typeMap' => ['root' => 'array', 'document' => 'array']],
91+
['typeMap' => ['root' => 'object', 'document' => 'object']],
9292
$this->options,
9393
$options,
9494
);

src/Query/Builder.php

Lines changed: 70 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use ArgumentCountError;
88
use BadMethodCallException;
9+
use Carbon\CarbonImmutable;
910
use Carbon\CarbonPeriod;
1011
use Closure;
1112
use DateTimeInterface;
@@ -25,6 +26,7 @@
2526
use MongoDB\Driver\Cursor;
2627
use Override;
2728
use RuntimeException;
29+
use stdClass;
2830

2931
use function array_fill_keys;
3032
use function array_is_list;
@@ -39,20 +41,23 @@
3941
use function call_user_func_array;
4042
use function count;
4143
use function ctype_xdigit;
44+
use function date_default_timezone_get;
4245
use function dd;
4346
use function dump;
4447
use function end;
4548
use function explode;
4649
use function func_get_args;
4750
use function func_num_args;
4851
use function get_debug_type;
52+
use function get_object_vars;
4953
use function implode;
5054
use function in_array;
5155
use function is_array;
5256
use function is_bool;
5357
use function is_callable;
5458
use function is_float;
5559
use function is_int;
60+
use function is_object;
5661
use function is_string;
5762
use function md5;
5863
use function preg_match;
@@ -227,7 +232,7 @@ public function hint($index)
227232
/** @inheritdoc */
228233
public function find($id, $columns = [])
229234
{
230-
return $this->where('_id', '=', $this->convertKey($id))->first($columns);
235+
return $this->where('id', '=', $this->convertKey($id))->first($columns);
231236
}
232237

233238
/** @inheritdoc */
@@ -391,7 +396,7 @@ public function toMql(): array
391396
}
392397

393398
$options = [
394-
'typeMap' => ['root' => 'array', 'document' => 'array'],
399+
'typeMap' => ['root' => 'object', 'document' => 'array'],
395400
];
396401

397402
// Add custom query options
@@ -451,7 +456,7 @@ public function toMql(): array
451456
}
452457

453458
// Fix for legacy support, converts the results to arrays instead of objects.
454-
$options['typeMap'] = ['root' => 'array', 'document' => 'array'];
459+
$options['typeMap'] = ['root' => 'object', 'document' => 'array'];
455460

456461
// Add custom query options
457462
if (count($this->options)) {
@@ -506,7 +511,7 @@ public function getFresh($columns = [], $returnLazy = false)
506511
if ($returnLazy) {
507512
return LazyCollection::make(function () use ($result) {
508513
foreach ($result as $item) {
509-
yield $this->aliasIdForResult($item);
514+
yield is_object($item) ? $this->aliasIdForResult($item) : $item;
510515
}
511516
});
512517
}
@@ -515,8 +520,8 @@ public function getFresh($columns = [], $returnLazy = false)
515520
$result = $result->toArray();
516521
}
517522

518-
foreach ($result as &$document) {
519-
$document = $this->aliasIdForResult($document);
523+
foreach ($result as &$item) {
524+
$item = is_object($item) ? $this->aliasIdForResult($item) : $item;
520525
}
521526

522527
return new Collection($result);
@@ -590,7 +595,7 @@ public function aggregate($function = null, $columns = ['*'])
590595
if (isset($results[0])) {
591596
$result = (array) $results[0];
592597

593-
return $result['aggregate'];
598+
return $this->aliasIdForResult($result['aggregate']);
594599
}
595600
}
596601

@@ -628,6 +633,7 @@ public function orderBy($column, $direction = 'asc')
628633
}
629634

630635
$column = (string) $column;
636+
631637
if ($column === 'natural') {
632638
$this->orders['$natural'] = $direction;
633639
} else {
@@ -692,10 +698,9 @@ public function insert(array $values)
692698
if (isset($document['_id']) && $document['_id'] !== $document['id']) {
693699
throw new InvalidArgumentException('Cannot insert document with different "id" and "_id" values');
694700
}
695-
696-
$document['_id'] = $document['id'];
697-
unset($document['id']);
698701
}
702+
703+
$document = $this->aliasIdForQuery($document);
699704
}
700705

701706
$options = $this->inheritConnectionOptions();
@@ -710,6 +715,8 @@ public function insertGetId(array $values, $sequence = null)
710715
{
711716
$options = $this->inheritConnectionOptions();
712717

718+
$values = $this->aliasIdForQuery($values);
719+
713720
$result = $this->collection->insertOne($values, $options);
714721

715722
if (! $result->isAcknowledged()) {
@@ -735,13 +742,6 @@ public function update(array $values, array $options = [])
735742
unset($values[$key]);
736743
}
737744

738-
// Since "id" is an alias for "_id", we prevent updating it
739-
foreach ($values as $fields) {
740-
if (array_key_exists('id', $fields)) {
741-
throw new InvalidArgumentException('Cannot update "id" field.');
742-
}
743-
}
744-
745745
return $this->performUpdate($values, $options);
746746
}
747747

@@ -778,9 +778,9 @@ public function pluck($column, $key = null)
778778
$results = $this->get($key === null ? [$column] : [$column, $key]);
779779

780780
// Convert ObjectID's to strings
781-
if (((string) $key) === '_id') {
781+
if (((string) $key) === 'id') {
782782
$results = $results->map(function ($item) {
783-
$item['_id'] = (string) $item['_id'];
783+
$item->id = (string) $item->id;
784784

785785
return $item;
786786
});
@@ -798,13 +798,14 @@ public function delete($id = null)
798798
// the ID to allow developers to simply and quickly remove a single row
799799
// from their database without manually specifying the where clauses.
800800
if ($id !== null) {
801-
$this->where('_id', '=', $id);
801+
$this->where('id', '=', $id);
802802
}
803803

804804
$wheres = $this->compileWheres();
805805
$options = $this->inheritConnectionOptions();
806806

807-
if (is_int($this->limit)) {
807+
/** 1000 is a large value used by Laravel {@see DatabaseFailedJobProvider} */
808+
if (is_int($this->limit) && $this->limit !== 1000) {
808809
if ($this->limit !== 1) {
809810
throw new LogicException(sprintf('Delete limit can be 1 or null (unlimited). Got %d', $this->limit));
810811
}
@@ -997,15 +998,19 @@ protected function performUpdate(array $update, array $options = [])
997998
}
998999

9991000
// Since "id" is an alias for "_id", we prevent updating it
1000-
foreach ($update as $operator => $fields) {
1001+
foreach ($update as &$fields) {
10011002
if (array_key_exists('id', $fields)) {
10021003
throw new InvalidArgumentException('Cannot update "id" field.');
10031004
}
1005+
1006+
// Rename "id" to "_id" for embedded documents
1007+
$fields = $this->aliasIdForQuery($fields);
10041008
}
10051009

10061010
$options = $this->inheritConnectionOptions($options);
10071011

10081012
$wheres = $this->compileWheres();
1013+
10091014
$result = $this->collection->updateMany($wheres, $update, $options);
10101015
if ($result->isAcknowledged()) {
10111016
return $result->getModifiedCount() ? $result->getModifiedCount() : $result->getUpsertedCount();
@@ -1188,7 +1193,7 @@ protected function compileWheres(): array
11881193
}
11891194
}
11901195

1191-
return $compiled;
1196+
return $this->aliasIdForQuery($compiled);
11921197
}
11931198

11941199
protected function compileWhereBasic(array $where): array
@@ -1561,13 +1566,52 @@ private function aliasIdForQuery(array $values): array
15611566
unset($values['id']);
15621567
}
15631568

1569+
foreach ($values as $key => $value) {
1570+
if (is_string($key) && str_ends_with($key, '.id')) {
1571+
$values[substr($key, 0, -3) . '._id'] = $value;
1572+
unset($values[$key]);
1573+
}
1574+
}
1575+
1576+
foreach ($values as &$value) {
1577+
if ($value instanceof DateTimeInterface) {
1578+
$value = new UTCDateTime($value);
1579+
} elseif (is_array($value)) {
1580+
$value = $this->aliasIdForQuery($value);
1581+
}
1582+
}
1583+
15641584
return $values;
15651585
}
15661586

1567-
private function aliasIdForResult(array $values): array
1587+
private function aliasIdForResult(stdClass|array $values): stdClass|array
15681588
{
1569-
if (isset($values['_id'])) {
1570-
$values['id'] = $values['_id'];
1589+
if (is_array($values)) {
1590+
if (isset($values['_id'])) {
1591+
$values['id'] = $values['_id'];
1592+
unset($values['_id']);
1593+
}
1594+
1595+
foreach ($values as $key => $value) {
1596+
if ($value instanceof UTCDateTime) {
1597+
$values[$key] = CarbonImmutable::createFromTimestamp($value->toDateTime()->getTimestamp(), 'UTC')->setTimezone(date_default_timezone_get());
1598+
} elseif (is_array($value) || $value instanceof stdClass) {
1599+
$values[$key] = $this->aliasIdForResult($value);
1600+
}
1601+
}
1602+
} else {
1603+
if (isset($values->_id)) {
1604+
$values->id = $values->_id;
1605+
unset($values->_id);
1606+
}
1607+
1608+
foreach (get_object_vars($values) as $key => $value) {
1609+
if ($value instanceof UTCDateTime) {
1610+
$values->{$key} = CarbonImmutable::createFromTimestamp($value->toDateTime()->getTimestamp(), 'UTC')->setTimezone(date_default_timezone_get());
1611+
} elseif (is_array($value) || $value instanceof stdClass) {
1612+
$values->{$key} = $this->aliasIdForResult($value);
1613+
}
1614+
}
15711615
}
15721616

15731617
return $values;

0 commit comments

Comments
 (0)