Skip to content

Commit 4fb2e6b

Browse files
committed
PHPORM-255 Add a method to disable renaming the id to _id field in embedded documents
1 parent 1bef2de commit 4fb2e6b

File tree

3 files changed

+68
-18
lines changed

3 files changed

+68
-18
lines changed

Diff for: src/Connection.php

+15
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ class Connection extends BaseConnection
5353

5454
private ?CommandSubscriber $commandSubscriber = null;
5555

56+
/** @var bool Whether to rename the rename "id" into "_id" for embedded documents. */
57+
private bool $renameEmbeddedIdField = true;
58+
5659
/**
5760
* Create a new database connection instance.
5861
*/
@@ -395,6 +398,18 @@ public function __call($method, $parameters)
395398
return $this->db->$method(...$parameters);
396399
}
397400

401+
/** Set whether to rename "id" field into "_id" for embedded documents. */
402+
public function setRenameEmbeddedIdField(bool $rename): void
403+
{
404+
$this->renameEmbeddedIdField = $rename;
405+
}
406+
407+
/** Get whether to rename "id" field into "_id" for embedded documents. */
408+
public function getRenameEmbeddedIdField(): bool
409+
{
410+
return $this->renameEmbeddedIdField;
411+
}
412+
398413
/**
399414
* Return the server version of one of the MongoDB servers: primary for
400415
* replica sets and standalone, and the selected server for sharded clusters.

Diff for: src/Query/Builder.php

+19-9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use MongoDB\Builder\Type\SearchOperatorInterface;
3030
use MongoDB\Driver\Cursor;
3131
use MongoDB\Driver\ReadPreference;
32+
use MongoDB\Laravel\Connection;
3233
use Override;
3334
use RuntimeException;
3435
use stdClass;
@@ -82,6 +83,7 @@
8283
use function trait_exists;
8384
use function var_export;
8485

86+
/** @method Connection getConnection() */
8587
class Builder extends BaseBuilder
8688
{
8789
private const REGEX_DELIMITERS = ['/', '#', '~'];
@@ -123,6 +125,8 @@ class Builder extends BaseBuilder
123125
*/
124126
public $options = [];
125127

128+
private ?bool $renameEmbeddedIdField;
129+
126130
/**
127131
* All of the available clause operators.
128132
*
@@ -1763,9 +1767,9 @@ public function orWhereIntegerNotInRaw($column, $values, $boolean = 'and')
17631767
throw new BadMethodCallException('This method is not supported by MongoDB');
17641768
}
17651769

1766-
private function aliasIdForQuery(array $values): array
1770+
private function aliasIdForQuery(array $values, bool $root = true): array
17671771
{
1768-
if (array_key_exists('id', $values)) {
1772+
if (array_key_exists('id', $values) && ($root || $this->getConnection()->getRenameEmbeddedIdField())) {
17691773
if (array_key_exists('_id', $values) && $values['id'] !== $values['_id']) {
17701774
throw new InvalidArgumentException('Cannot have both "id" and "_id" fields.');
17711775
}
@@ -1792,7 +1796,7 @@ private function aliasIdForQuery(array $values): array
17921796
}
17931797

17941798
// ".id" subfield are alias for "._id"
1795-
if (str_ends_with($key, '.id')) {
1799+
if (str_ends_with($key, '.id') && ($root || $this->getConnection()->getRenameEmbeddedIdField())) {
17961800
$newkey = substr($key, 0, -3) . '._id';
17971801
if (array_key_exists($newkey, $values) && $value !== $values[$newkey]) {
17981802
throw new InvalidArgumentException(sprintf('Cannot have both "%s" and "%s" fields.', $key, $newkey));
@@ -1805,7 +1809,7 @@ private function aliasIdForQuery(array $values): array
18051809

18061810
foreach ($values as &$value) {
18071811
if (is_array($value)) {
1808-
$value = $this->aliasIdForQuery($value);
1812+
$value = $this->aliasIdForQuery($value, false);
18091813
} elseif ($value instanceof DateTimeInterface) {
18101814
$value = new UTCDateTime($value);
18111815
}
@@ -1823,10 +1827,13 @@ private function aliasIdForQuery(array $values): array
18231827
*
18241828
* @template T of array|object
18251829
*/
1826-
public function aliasIdForResult(array|object $values): array|object
1830+
public function aliasIdForResult(array|object $values, bool $root = true): array|object
18271831
{
18281832
if (is_array($values)) {
1829-
if (array_key_exists('_id', $values) && ! array_key_exists('id', $values)) {
1833+
if (
1834+
array_key_exists('_id', $values) && ! array_key_exists('id', $values)
1835+
&& ($root || $this->getConnection()->getRenameEmbeddedIdField())
1836+
) {
18301837
$values['id'] = $values['_id'];
18311838
unset($values['_id']);
18321839
}
@@ -1836,13 +1843,16 @@ public function aliasIdForResult(array|object $values): array|object
18361843
$values[$key] = Date::instance($value->toDateTime())
18371844
->setTimezone(new DateTimeZone(date_default_timezone_get()));
18381845
} elseif (is_array($value) || is_object($value)) {
1839-
$values[$key] = $this->aliasIdForResult($value);
1846+
$values[$key] = $this->aliasIdForResult($value, false);
18401847
}
18411848
}
18421849
}
18431850

18441851
if ($values instanceof stdClass) {
1845-
if (property_exists($values, '_id') && ! property_exists($values, 'id')) {
1852+
if (
1853+
property_exists($values, '_id') && ! property_exists($values, 'id')
1854+
&& ($root || $this->getConnection()->getRenameEmbeddedIdField())
1855+
) {
18461856
$values->id = $values->_id;
18471857
unset($values->_id);
18481858
}
@@ -1852,7 +1862,7 @@ public function aliasIdForResult(array|object $values): array|object
18521862
$values->{$key} = Date::instance($value->toDateTime())
18531863
->setTimezone(new DateTimeZone(date_default_timezone_get()));
18541864
} elseif (is_array($value) || is_object($value)) {
1855-
$values->{$key} = $this->aliasIdForResult($value);
1865+
$values->{$key} = $this->aliasIdForResult($value, false);
18561866
}
18571867
}
18581868
}

Diff for: tests/Query/BuilderTest.php

+34-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use Illuminate\Tests\Database\DatabaseQueryBuilderTest;
1313
use InvalidArgumentException;
1414
use LogicException;
15-
use Mockery as m;
1615
use MongoDB\BSON\Regex;
1716
use MongoDB\BSON\UTCDateTime;
1817
use MongoDB\Driver\ReadPreference;
@@ -39,7 +38,7 @@ public function testMql(array $expected, Closure $build, ?string $requiredMethod
3938
$this->markTestSkipped(sprintf('Method "%s::%s()" does not exist.', Builder::class, $requiredMethod));
4039
}
4140

42-
$builder = $build(self::getBuilder());
41+
$builder = $build($this->getBuilder());
4342
$this->assertInstanceOf(Builder::class, $builder);
4443
$mql = $builder->toMql();
4544

@@ -1447,7 +1446,7 @@ function (Builder $elemMatchQuery): void {
14471446
#[DataProvider('provideExceptions')]
14481447
public function testException($class, $message, Closure $build): void
14491448
{
1450-
$builder = self::getBuilder();
1449+
$builder = $this->getBuilder();
14511450

14521451
$this->expectException($class);
14531452
$this->expectExceptionMessage($message);
@@ -1545,7 +1544,7 @@ public static function provideExceptions(): iterable
15451544
#[DataProvider('getEloquentMethodsNotSupported')]
15461545
public function testEloquentMethodsNotSupported(Closure $callback)
15471546
{
1548-
$builder = self::getBuilder();
1547+
$builder = $this->getBuilder();
15491548

15501549
$this->expectException(BadMethodCallException::class);
15511550
$this->expectExceptionMessage('This method is not supported by MongoDB');
@@ -1600,12 +1599,38 @@ public static function getEloquentMethodsNotSupported()
16001599
yield 'orWhereIntegerNotInRaw' => [fn (Builder $builder) => $builder->orWhereIntegerNotInRaw('id', ['1a', 2])];
16011600
}
16021601

1603-
private static function getBuilder(): Builder
1602+
public function testRenameEmbeddedIdFieldCanBeDisabled()
16041603
{
1605-
$connection = m::mock(Connection::class);
1606-
$processor = m::mock(Processor::class);
1607-
$connection->shouldReceive('getSession')->andReturn(null);
1608-
$connection->shouldReceive('getQueryGrammar')->andReturn(new Grammar($connection));
1604+
$builder = $this->getBuilder(false);
1605+
$this->assertFalse($builder->getConnection()->getRenameEmbeddedIdField());
1606+
1607+
$mql = $builder
1608+
->where('id', '=', 10)
1609+
->where('nested.id', '=', 20)
1610+
->where('embed', '=', ['id' => 30])
1611+
->toMql();
1612+
1613+
$this->assertEquals([
1614+
'find' => [
1615+
[
1616+
'$and' => [
1617+
['_id' => 10],
1618+
['nested.id' => 20],
1619+
['embed' => ['id' => 30]],
1620+
],
1621+
],
1622+
['typeMap' => ['root' => 'object', 'document' => 'array']],
1623+
],
1624+
], $mql);
1625+
}
1626+
1627+
private function getBuilder(bool $renameEmbeddedIdField = true): Builder
1628+
{
1629+
$connection = $this->createStub(Connection::class);
1630+
$connection->method('getRenameEmbeddedIdField')->willReturn($renameEmbeddedIdField);
1631+
$processor = $this->createStub(Processor::class);
1632+
$connection->method('getSession')->willReturn(null);
1633+
$connection->method('getQueryGrammar')->willReturn(new Grammar($connection));
16091634

16101635
return new Builder($connection, null, $processor);
16111636
}

0 commit comments

Comments
 (0)