Skip to content

Commit 5c88046

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

File tree

2 files changed

+70
-19
lines changed

2 files changed

+70
-19
lines changed

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

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,6 @@ public function prepareQueryOrNewObj(array $query, bool $isNewObj = false): arra
10681068

10691069
$preparedQueryElements = $this->prepareQueryElement($key, $value, null, true, $isNewObj);
10701070
foreach ($preparedQueryElements as [$preparedKey, $preparedValue]) {
1071-
$preparedValue = $this->convertToDatabaseValue($key, $preparedValue);
10721071
$preparedQuery[$preparedKey] = $preparedValue;
10731072
}
10741073
}
@@ -1083,29 +1082,31 @@ public function prepareQueryOrNewObj(array $query, bool $isNewObj = false): arra
10831082
*
10841083
* @return mixed
10851084
*/
1086-
private function convertToDatabaseValue(string $fieldName, $value)
1085+
private function convertToDatabaseValue(string $fieldName, $value, ?ClassMetadata $class = null)
10871086
{
1087+
$class ??= $this->class;
1088+
10881089
if (is_array($value)) {
10891090
foreach ($value as $k => $v) {
10901091
if ($k === '$exists' || $k === '$type' || $k === '$currentDate') {
10911092
continue;
10921093
}
10931094

1094-
$value[$k] = $this->convertToDatabaseValue($fieldName, $v);
1095+
$value[$k] = $this->convertToDatabaseValue($fieldName, $v, $class);
10951096
}
10961097

10971098
return $value;
10981099
}
10991100

1100-
if (! $this->class->hasField($fieldName)) {
1101+
if (! $class->hasField($fieldName)) {
11011102
if ($value instanceof BackedEnum) {
11021103
$value = $value->value;
11031104
}
11041105

11051106
return Type::convertPHPToDatabaseValue($value);
11061107
}
11071108

1108-
$mapping = $this->class->fieldMappings[$fieldName];
1109+
$mapping = $class->fieldMappings[$fieldName];
11091110
$typeName = $mapping['type'];
11101111

11111112
if (! empty($mapping['reference']) || ! empty($mapping['embedded'])) {
@@ -1143,15 +1144,15 @@ private function convertToDatabaseValue(string $fieldName, $value)
11431144
*
11441145
* @return array<array{string, mixed}>
11451146
*/
1146-
private function prepareQueryElement(string $fieldName, $value = null, ?ClassMetadata $class = null, bool $prepareValue = true, bool $inNewObj = false): array
1147+
private function prepareQueryElement(string $originalFieldName, $value = null, ?ClassMetadata $class = null, bool $prepareValue = true, bool $inNewObj = false): array
11471148
{
11481149
$class ??= $this->class;
11491150

11501151
// @todo Consider inlining calls to ClassMetadata methods
11511152

11521153
// Process all non-identifier fields by translating field names
1153-
if ($class->hasField($fieldName) && ! $class->isIdentifier($fieldName)) {
1154-
$mapping = $class->fieldMappings[$fieldName];
1154+
if ($class->hasField($originalFieldName) && ! $class->isIdentifier($originalFieldName)) {
1155+
$mapping = $class->fieldMappings[$originalFieldName];
11551156
$fieldName = $mapping['name'];
11561157

11571158
if (! $prepareValue) {
@@ -1176,7 +1177,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
11761177

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

11821183
// Additional preparation for one or more simple reference values
@@ -1195,7 +1196,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
11951196
}
11961197

11971198
// Process identifier fields
1198-
if (($class->hasField($fieldName) && $class->isIdentifier($fieldName)) || $fieldName === '_id') {
1199+
if (($class->hasField($originalFieldName) && $class->isIdentifier($originalFieldName)) || $originalFieldName === '_id') {
11991200
$fieldName = '_id';
12001201

12011202
if (! $prepareValue) {
@@ -1215,8 +1216,8 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12151216
}
12161217

12171218
// No processing for unmapped, non-identifier, non-dotted field names
1218-
if (strpos($fieldName, '.') === false) {
1219-
return [[$fieldName, $value]];
1219+
if (strpos($originalFieldName, '.') === false) {
1220+
return [[$originalFieldName, $prepareValue ? $this->convertToDatabaseValue($originalFieldName, $value, $class) : $value]];
12201221
}
12211222

12221223
/* Process "fieldName.objectProperty" queries (on arrays or objects).
@@ -1225,11 +1226,11 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12251226
* significant: "fieldName.objectProperty" with an optional index or key
12261227
* for collections stored as either BSON arrays or objects.
12271228
*/
1228-
$e = explode('.', $fieldName, 4);
1229+
$e = explode('.', $originalFieldName, 4);
12291230

12301231
// No further processing for unmapped fields
12311232
if (! isset($class->fieldMappings[$e[0]])) {
1232-
return [[$fieldName, $value]];
1233+
return [[$originalFieldName, $prepareValue ? $this->convertToDatabaseValue($e[0], $value, $class) : $value]];
12331234
}
12341235

12351236
$mapping = $class->fieldMappings[$e[0]];
@@ -1246,6 +1247,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12461247
$mapping['type'] === ClassMetadata::MANY && CollectionHelper::isHash($mapping['strategy'])
12471248
&& isset($e[2])
12481249
) {
1250+
$fieldName = $originalFieldName;
12491251
$objectProperty = $e[2];
12501252
$objectPropertyPrefix = $e[1] . '.';
12511253
$nextObjectProperty = implode('.', array_slice($e, 3));
@@ -1262,7 +1264,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12621264
} else {
12631265
$fieldName = $e[0] . '.' . $e[1];
12641266

1265-
return [[$fieldName, $value]];
1267+
return [[$fieldName, $prepareValue ? $this->convertToDatabaseValue($e[0], $value, $class) : $value]];
12661268
}
12671269

12681270
// No further processing for fields without a targetDocument mapping
@@ -1271,7 +1273,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12711273
$fieldName .= '.' . $nextObjectProperty;
12721274
}
12731275

1274-
return [[$fieldName, $value]];
1276+
return [[$fieldName, $prepareValue ? $this->convertToDatabaseValue($e[0], $value, $class) : $value]];
12751277
}
12761278

12771279
$targetClass = $this->dm->getClassMetadata($mapping['targetDocument']);
@@ -1282,7 +1284,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
12821284
$fieldName .= '.' . $nextObjectProperty;
12831285
}
12841286

1285-
return [[$fieldName, $value]];
1287+
return [[$fieldName, $prepareValue ? $this->convertToDatabaseValue($objectProperty, $value, $targetClass) : $value]];
12861288
}
12871289

12881290
$targetMapping = $targetClass->getFieldMapping($objectProperty);
@@ -1329,7 +1331,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
13291331
$nextObjectProperty = '$' . $nextObjectProperty;
13301332
}
13311333

1332-
$fieldNames = [[$nextObjectProperty, $value]];
1334+
$fieldNames = [[$nextObjectProperty, $prepareValue ? $this->convertToDatabaseValue($nextObjectProperty, $value, $nextTargetClass) : $value]];
13331335
}
13341336

13351337
return array_map(static function ($preparedTuple) use ($fieldName) {
@@ -1339,7 +1341,7 @@ private function prepareQueryElement(string $fieldName, $value = null, ?ClassMet
13391341
}, $fieldNames);
13401342
}
13411343

1342-
return [[$fieldName, $value]];
1344+
return [[$fieldName, $this->convertToDatabaseValue($objectProperty, $value, $targetClass)]];
13431345
}
13441346

13451347
/**

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)