Skip to content

Commit ec3610c

Browse files
committed
Support dot syntax when preparing nested query values
1 parent 1c2a146 commit ec3610c

File tree

2 files changed

+70
-17
lines changed

2 files changed

+70
-17
lines changed

lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use MongoDB\GridFS\Bucket;
4040
use stdClass;
4141

42+
use function array_column;
4243
use function array_combine;
4344
use function array_fill;
4445
use function array_intersect_key;
@@ -1067,10 +1068,10 @@ public function prepareQueryOrNewObj(array $query, bool $isNewObj = false): arra
10671068
}
10681069

10691070
$preparedQueryElements = $this->prepareQueryElement($key, $value, null, true, $isNewObj);
1070-
foreach ($preparedQueryElements as [$preparedKey, $preparedValue]) {
1071-
$preparedValue = $this->convertToDatabaseValue($key, $preparedValue);
1072-
$preparedQuery[$preparedKey] = $preparedValue;
1073-
}
1071+
$preparedQuery = array_combine(
1072+
array_column($preparedQueryElements, 0),
1073+
array_column($preparedQueryElements, 1),
1074+
);
10741075
}
10751076

10761077
return $preparedQuery;
@@ -1083,8 +1084,10 @@ public function prepareQueryOrNewObj(array $query, bool $isNewObj = false): arra
10831084
*
10841085
* @return mixed
10851086
*/
1086-
private function convertToDatabaseValue(string $fieldName, $value)
1087+
private function convertToDatabaseValue(string $fieldName, $value, ?ClassMetadata $class = null)
10871088
{
1089+
$class ??= $this->class;
1090+
10881091
if (is_array($value)) {
10891092
foreach ($value as $k => $v) {
10901093
if ($k === '$exists' || $k === '$type' || $k === '$currentDate') {
@@ -1097,15 +1100,15 @@ private function convertToDatabaseValue(string $fieldName, $value)
10971100
return $value;
10981101
}
10991102

1100-
if (! $this->class->hasField($fieldName)) {
1103+
if (! $class->hasField($fieldName)) {
11011104
if ($value instanceof BackedEnum) {
11021105
$value = $value->value;
11031106
}
11041107

11051108
return Type::convertPHPToDatabaseValue($value);
11061109
}
11071110

1108-
$mapping = $this->class->fieldMappings[$fieldName];
1111+
$mapping = $class->fieldMappings[$fieldName];
11091112
$typeName = $mapping['type'];
11101113

11111114
if (! empty($mapping['reference']) || ! empty($mapping['embedded'])) {
@@ -1143,15 +1146,15 @@ private function convertToDatabaseValue(string $fieldName, $value)
11431146
*
11441147
* @return array<array{string, mixed}>
11451148
*/
1146-
private function prepareQueryElement(string $fieldName, $value = null, ?ClassMetadata $class = null, bool $prepareValue = true, bool $inNewObj = false): array
1149+
private function prepareQueryElement(string $originalFieldName, $value = null, ?ClassMetadata $class = null, bool $prepareValue = true, bool $inNewObj = false): array
11471150
{
11481151
$class ??= $this->class;
11491152

11501153
// @todo Consider inlining calls to ClassMetadata methods
11511154

11521155
// Process all non-identifier fields by translating field names
1153-
if ($class->hasField($fieldName) && ! $class->isIdentifier($fieldName)) {
1154-
$mapping = $class->fieldMappings[$fieldName];
1156+
if ($class->hasField($originalFieldName) && ! $class->isIdentifier($originalFieldName)) {
1157+
$mapping = $class->fieldMappings[$originalFieldName];
11551158
$fieldName = $mapping['name'];
11561159

11571160
if (! $prepareValue) {
@@ -1176,7 +1179,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
11761179

11771180
// No further preparation unless we're dealing with a simple reference
11781181
if (empty($mapping['reference']) || $mapping['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID || empty((array) $value)) {
1179-
return [[$fieldName, $value]];
1182+
return [[$fieldName, $this->convertToDatabaseValue($originalFieldName, $value, $class)]];
11801183
}
11811184

11821185
// Additional preparation for one or more simple reference values
@@ -1195,7 +1198,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
11951198
}
11961199

11971200
// Process identifier fields
1198-
if (($class->hasField($fieldName) && $class->isIdentifier($fieldName)) || $fieldName === '_id') {
1201+
if (($class->hasField($originalFieldName) && $class->isIdentifier($originalFieldName)) || $originalFieldName === '_id') {
11991202
$fieldName = '_id';
12001203

12011204
if (! $prepareValue) {
@@ -1215,8 +1218,8 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12151218
}
12161219

12171220
// No processing for unmapped, non-identifier, non-dotted field names
1218-
if (strpos($fieldName, '.') === false) {
1219-
return [[$fieldName, $value]];
1221+
if (strpos($originalFieldName, '.') === false) {
1222+
return [[$originalFieldName, $value]];
12201223
}
12211224

12221225
/* Process "fieldName.objectProperty" queries (on arrays or objects).
@@ -1225,11 +1228,11 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12251228
* significant: "fieldName.objectProperty" with an optional index or key
12261229
* for collections stored as either BSON arrays or objects.
12271230
*/
1228-
$e = explode('.', $fieldName, 4);
1231+
$e = explode('.', $originalFieldName, 4);
12291232

12301233
// No further processing for unmapped fields
12311234
if (! isset($class->fieldMappings[$e[0]])) {
1232-
return [[$fieldName, $value]];
1235+
return [[$originalFieldName, $value]];
12331236
}
12341237

12351238
$mapping = $class->fieldMappings[$e[0]];
@@ -1246,6 +1249,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12461249
$mapping['type'] === ClassMetadata::MANY && CollectionHelper::isHash($mapping['strategy'])
12471250
&& isset($e[2])
12481251
) {
1252+
$fieldName = $originalFieldName;
12491253
$objectProperty = $e[2];
12501254
$objectPropertyPrefix = $e[1] . '.';
12511255
$nextObjectProperty = implode('.', array_slice($e, 3));
@@ -1339,7 +1343,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
13391343
}, $fieldNames);
13401344
}
13411345

1342-
return [[$fieldName, $value]];
1346+
return [[$fieldName, $this->convertToDatabaseValue($objectProperty, $value, $targetClass)]];
13431347
}
13441348

13451349
/**

tests/Doctrine/ODM/MongoDB/Tests/Query/BuilderTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,35 @@ public function testNonRewindable(): void
837837
self::assertInstanceOf(UnrewindableIterator::class, $query->execute());
838838
}
839839

840+
public function testQueryPreparesNestedValues(): void
841+
{
842+
$objectId = new ObjectId();
843+
$oidString = (string) $objectId;
844+
845+
$embedded = new EmbeddedForNestedFieldPreparation();
846+
$embedded->objectId = $oidString;
847+
848+
$builder = new Builder($this->dm, DocumentForNestedFieldPreparation::class);
849+
$builder
850+
->updateOne()
851+
->field('embedded.embedded.objectId')
852+
->equals($oidString)
853+
->field('embedded.embedded.embedded')
854+
->set($embedded);
855+
856+
$query = $builder->getQuery()->getQuery();
857+
$filter = $query['query'];
858+
$newObj = $query['newObj'];
859+
860+
self::assertArrayHasKey('embedded.embedded.test', $filter);
861+
self::assertInstanceOf(ObjectId::class, $filter['embedded.embedded.test']);
862+
863+
self::assertArrayHasKey('embedded.embedded.embedded', $newObj['$set']);
864+
self::assertIsArray($newObj['$set']['embedded.embedded.embedded']);
865+
self::assertArrayHasKey('test', $newObj['$set']['embedded.embedded.embedded']);
866+
self::assertInstanceOf(ObjectId::class, $newObj['$set']['embedded.embedded.embedded']['test']);
867+
}
868+
840869
private function getTestQueryBuilder(): Builder
841870
{
842871
return new Builder($this->dm, User::class);
@@ -922,3 +951,23 @@ class ChildC extends ParentClass
922951
#[ODM\ReferenceMany(storeAs: 'dbRef')]
923952
public $featurePartialMany;
924953
}
954+
955+
#[ODM\Document]
956+
class DocumentForNestedFieldPreparation
957+
{
958+
#[ODM\Id]
959+
public string $id;
960+
961+
#[ODM\EmbedOne(targetDocument: EmbeddedForNestedFieldPreparation::class)]
962+
public EmbeddedForNestedFieldPreparation $embedded;
963+
}
964+
965+
#[ODM\EmbeddedDocument]
966+
class EmbeddedForNestedFieldPreparation
967+
{
968+
#[ODM\Field(name: 'test', type: Type::OBJECTID)]
969+
public string $objectId;
970+
971+
#[ODM\EmbedOne(targetDocument: self::class)]
972+
public ?EmbeddedForNestedFieldPreparation $embedded = null;
973+
}

0 commit comments

Comments
 (0)