Skip to content

Commit 4a910a8

Browse files
committed
Hydrate models from raw BSON
1 parent 402efbd commit 4a910a8

File tree

20 files changed

+180
-93
lines changed

20 files changed

+180
-93
lines changed

lib/Doctrine/ODM/MongoDB/Aggregation/Builder.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,10 @@ public function getAggregation(array $options = []): IterableResult
254254
{
255255
$class = $this->hydrationClass ? $this->dm->getClassMetadata($this->hydrationClass) : null;
256256

257+
if ($this->hydrationClass) {
258+
$options['typeMap'] = DocumentManager::HYDRATION_TYPEMAP;
259+
}
260+
257261
return new Aggregation($this->dm, $class, $this->collection, $this->getPipeline(), $options, $this->rewindable);
258262
}
259263

lib/Doctrine/ODM/MongoDB/DocumentManager.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,16 @@
5757
*/
5858
class DocumentManager implements ObjectManager
5959
{
60+
/**
61+
* TypeMap by default
62+
*/
6063
public const CLIENT_TYPEMAP = ['root' => 'array', 'document' => 'array'];
6164

65+
/**
66+
* TypeMap when result is hydrated
67+
*/
68+
public const HYDRATION_TYPEMAP = ['root' => 'bson', 'document' => 'bson', 'array' => 'bson'];
69+
6270
/**
6371
* The Doctrine MongoDB connection instance.
6472
*/

lib/Doctrine/ODM/MongoDB/Event/PreLoadEventArgs.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,52 @@
55
namespace Doctrine\ODM\MongoDB\Event;
66

77
use Doctrine\ODM\MongoDB\DocumentManager;
8+
use MongoDB\BSON\Document;
89
use MongoDB\Driver\Session;
910

1011
/**
1112
* Class that holds event arguments for a preLoad event.
1213
*/
1314
final class PreLoadEventArgs extends LifecycleEventArgs
1415
{
15-
/** @param array<string, mixed> $data */
16+
private array $deprecatedDataArray;
17+
1618
public function __construct(
1719
object $document,
1820
DocumentManager $dm,
19-
private array &$data,
21+
private Document $data,
2022
?Session $session = null,
2123
) {
2224
parent::__construct($document, $dm, $session);
2325
}
2426

27+
public function getRawData(): Document
28+
{
29+
if (isset($this->deprecatedDataArray)) {
30+
$this->data = Document::fromPHP($this->deprecatedDataArray);
31+
unset($this->deprecatedDataArray);
32+
}
33+
34+
return $this->data;
35+
}
36+
37+
public function setRawData(Document $document): void
38+
{
39+
$this->data = $document;
40+
unset($this->deprecatedDataArray);
41+
}
42+
2543
/**
2644
* Get the array of data to be loaded and hydrated.
2745
*
46+
* @deprecated Use {@see self::getBsonDocument()} and {@see self::setBsonDocument()}
47+
*
2848
* @return array<string, mixed>
2949
*/
3050
public function &getData(): array
3151
{
32-
return $this->data;
52+
$this->deprecatedDataArray ??= $this->data->toPHP(['root' => 'array']);
53+
54+
return $this->deprecatedDataArray;
3355
}
3456
}

lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
use Doctrine\ODM\MongoDB\Proxy\InternalProxy;
1515
use Doctrine\ODM\MongoDB\Types\Type;
1616
use Doctrine\ODM\MongoDB\UnitOfWork;
17+
use MongoDB\BSON\Document;
1718
use ProxyManager\Proxy\GhostObjectInterface;
1819

19-
use function array_key_exists;
2020
use function chmod;
2121
use function class_exists;
2222
use function dirname;
@@ -169,8 +169,11 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
169169
<<<EOF
170170
171171
// AlsoLoad("$name")
172-
if (! array_key_exists('%1\$s', \$data) && array_key_exists('$name', \$data)) {
172+
if (! \$data->has('%1\$s') && \$data->has('$name')) {
173+
// @todo extracting and repacking is not very efficient
174+
\$data = \$data->toPHP(['root' => 'array', 'document' => 'bson', 'array' => 'bson']);
173175
\$data['%1\$s'] = \$data['$name'];
176+
\$data = Document::fromPHP(\$data);
174177
}
175178
176179
EOF
@@ -203,8 +206,11 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
203206
<<<EOF
204207
205208
// Field(type: "{$mapping['type']}")
206-
if (isset(\$data['%1\$s']) || (! empty(\$this->class->fieldMappings['%2\$s']['nullable']) && array_key_exists('%1\$s', \$data))) {
207-
\$value = \$data['%1\$s'];
209+
if (\$data->has('%1\$s') || (! empty(\$this->class->fieldMappings['%2\$s']['nullable']) && \$data->has('%1\$s'))) {
210+
\$value = \$data->get('%1\$s');
211+
if (\$value instanceof PackedArray || \$value instanceof Document) {
212+
\$value = \$value->toPHP(DocumentManager::CLIENT_TYPEMAP);
213+
}
208214
if (\$value !== null) {
209215
\$typeIdentifier = \$this->class->fieldMappings['%2\$s']['type'];
210216
%3\$s
@@ -226,11 +232,11 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
226232
<<<'EOF'
227233
228234
// ReferenceOne
229-
if (isset($data['%1$s']) || (! empty($this->class->fieldMappings['%2$s']['nullable']) && array_key_exists('%1$s', $data))) {
230-
$return = $data['%1$s'];
235+
if ($data->has('%1$s') || (! empty($this->class->fieldMappings['%2$s']['nullable']) && $data->has('%1$s'))) {
236+
$return = $data->get('%1$s');
231237
if ($return !== null) {
232-
if ($this->class->fieldMappings['%2$s']['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID && ! is_array($return)) {
233-
throw HydratorException::associationTypeMismatch('%3$s', '%1$s', 'array', gettype($return));
238+
if ($this->class->fieldMappings['%2$s']['storeAs'] !== ClassMetadata::REFERENCE_STORE_AS_ID && ! $return instanceof Document) {
239+
throw HydratorException::associationTypeMismatch('%3$s', '%1$s', Document::class, get_debug_type($return));
234240
}
235241
236242
$className = $this->dm->getClassNameForAssociation($this->class->fieldMappings['%2$s'], $return);
@@ -295,10 +301,10 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
295301
<<<'EOF'
296302
297303
// ReferenceMany & EmbedMany
298-
$mongoData = $data['%1$s'] ?? null;
304+
$mongoData = $data->has('%1$s') ? $data->get('%1$s') : null;
299305
300-
if ($mongoData !== null && ! is_array($mongoData)) {
301-
throw HydratorException::associationTypeMismatch('%3$s', '%1$s', 'array', gettype($mongoData));
306+
if ($mongoData !== null && ! $mongoData instanceof PackedArray && ! $mongoData instanceof Document) {
307+
throw HydratorException::associationTypeMismatch('%3$s', '%1$s', Document::class . '|' . PackedArray::class, get_debug_type($mongoData));
302308
}
303309
304310
$return = $this->dm->getConfiguration()->getPersistentCollectionFactory()->create($this->dm, $this->class->fieldMappings['%2$s']);
@@ -322,13 +328,13 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
322328
<<<'EOF'
323329
324330
// EmbedOne
325-
if (isset($data['%1$s']) || (! empty($this->class->fieldMappings['%2$s']['nullable']) && array_key_exists('%1$s', $data))) {
326-
$return = $data['%1$s'];
331+
if ($data->has('%1$s') || (! empty($this->class->fieldMappings['%2$s']['nullable']) && $data->has('%1$s'))) {
332+
$return = $data->get('%1$s');
327333
if ($return !== null) {
328334
$embeddedDocument = $return;
329335
330-
if (! is_array($embeddedDocument)) {
331-
throw HydratorException::associationTypeMismatch('%3$s', '%1$s', 'array', gettype($embeddedDocument));
336+
if (! $embeddedDocument instanceof Document) {
337+
throw HydratorException::associationTypeMismatch('%3$s', '%1$s', Document::class, get_debug_type($embeddedDocument));
332338
}
333339
334340
$className = $this->dm->getClassNameForAssociation($this->class->fieldMappings['%2$s'], $embeddedDocument);
@@ -370,9 +376,11 @@ private function generateHydratorClass(ClassMetadata $class, string $hydratorCla
370376
use Doctrine\ODM\MongoDB\Hydrator\HydratorInterface;
371377
use Doctrine\ODM\MongoDB\Query\Query;
372378
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
379+
use MongoDB\BSON\Document;
380+
use MongoDB\BSON\PackedArray;
373381
374382
use function array_key_exists;
375-
use function gettype;
383+
use function get_debug_type;
376384
use function is_array;
377385
378386
/**
@@ -382,10 +390,11 @@ class $hydratorClassName implements HydratorInterface
382390
{
383391
public function __construct(private DocumentManager \$dm, private ClassMetadata \$class) {}
384392
385-
public function hydrate(object \$document, array \$data, array \$hints = []): array
393+
public function hydrate(object \$document, Document \$data, array \$hints = []): array
386394
{
387395
\$hydratedData = [];
388-
%s return \$hydratedData;
396+
%s
397+
return \$hydratedData;
389398
}
390399
}
391400
EOF
@@ -425,13 +434,14 @@ public function hydrate(object \$document, array \$data, array \$hints = []): ar
425434
*
426435
* @return array<string, mixed>
427436
*/
428-
public function hydrate(object $document, array $data, array $hints = []): array
437+
public function hydrate(object $document, Document $data, array $hints = []): array
429438
{
430439
$metadata = $this->dm->getClassMetadata($document::class);
431440
// Invoke preLoad lifecycle events and listeners
432441
if (! empty($metadata->lifecycleCallbacks[Events::preLoad])) {
433-
$args = [new PreLoadEventArgs($document, $this->dm, $data)];
434-
$metadata->invokeLifecycleCallbacks(Events::preLoad, $document, $args);
442+
$preloadEventArgs = new PreLoadEventArgs($document, $this->dm, $data);
443+
$metadata->invokeLifecycleCallbacks(Events::preLoad, $document, [$preloadEventArgs]);
444+
$data = $preloadEventArgs->getRawData();
435445
}
436446

437447
$this->evm->dispatchEvent(Events::preLoad, new PreLoadEventArgs($document, $this->dm, $data));
@@ -441,8 +451,8 @@ public function hydrate(object $document, array $data, array $hints = []): array
441451
foreach ($metadata->alsoLoadMethods as $method => $fieldNames) {
442452
foreach ($fieldNames as $fieldName) {
443453
// Invoke the method only once for the first field we find
444-
if (array_key_exists($fieldName, $data)) {
445-
$document->$method($data[$fieldName]);
454+
if ($data->has($fieldName)) {
455+
$document->$method($data->get($fieldName));
446456
continue 2;
447457
}
448458
}

lib/Doctrine/ODM/MongoDB/Hydrator/HydratorInterface.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Doctrine\ODM\MongoDB\Hydrator;
66

77
use Doctrine\ODM\MongoDB\UnitOfWork;
8+
use MongoDB\BSON\Document;
89

910
/**
1011
* The HydratorInterface defines methods all hydrator need to implement
@@ -16,10 +17,9 @@ interface HydratorInterface
1617
/**
1718
* Hydrate array of MongoDB document data into the given document object.
1819
*
19-
* @param array<string, mixed> $data
2020
* @phpstan-param Hints $hints
2121
*
2222
* @return array<string, mixed>
2323
*/
24-
public function hydrate(object $document, array $data, array $hints = []): array;
24+
public function hydrate(object $document, Document $data, array $hints = []): array;
2525
}

lib/Doctrine/ODM/MongoDB/Iterator/HydratingIterator.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Doctrine\ODM\MongoDB\UnitOfWork;
99
use Iterator;
1010
use IteratorIterator;
11+
use MongoDB\BSON\Document;
1112
use ReturnTypeWillChange;
1213
use RuntimeException;
1314
use Traversable;
@@ -87,7 +88,7 @@ private function getIterator(): Iterator
8788
*
8889
* @return TDocument|null
8990
*/
90-
private function hydrate(?array $document): ?object
91+
private function hydrate(?Document $document): ?object
9192
{
9293
return $document !== null ? $this->unitOfWork->getOrCreateDocument($this->class->name, $document, $this->unitOfWorkHints) : null;
9394
}

lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Doctrine\Common\Collections\Collection;
1212
use Doctrine\Instantiator\Instantiator;
1313
use Doctrine\Instantiator\InstantiatorInterface;
14+
use Doctrine\ODM\MongoDB\DocumentManager;
1415
use Doctrine\ODM\MongoDB\Id\IdGenerator;
1516
use Doctrine\ODM\MongoDB\LockException;
1617
use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries;
@@ -25,6 +26,8 @@
2526
use Doctrine\Persistence\Reflection\EnumReflectionProperty;
2627
use InvalidArgumentException;
2728
use LogicException;
29+
use MongoDB\BSON\Document;
30+
use MongoDB\BSON\PackedArray;
2831
use ProxyManager\Proxy\GhostObjectInterface;
2932
use ReflectionClass;
3033
use ReflectionEnum;
@@ -1825,6 +1828,10 @@ public function getPHPIdentifierValue($id)
18251828
{
18261829
$idType = $this->fieldMappings[$this->identifier]['type'];
18271830

1831+
if ($id instanceof Document || $id instanceof PackedArray) {
1832+
$id = $id->toPHP(DocumentManager::CLIENT_TYPEMAP);
1833+
}
1834+
18281835
return Type::getType($idType)->convertToPHPValue($id);
18291836
}
18301837

lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionInterface.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Doctrine\ODM\MongoDB\MongoDBException;
1010
use Doctrine\ODM\MongoDB\UnitOfWork;
1111
use Doctrine\Persistence\Mapping\ClassMetadata;
12+
use MongoDB\BSON\Document;
13+
use MongoDB\BSON\PackedArray;
1214

1315
/**
1416
* Interface for persistent collection classes.
@@ -34,16 +36,14 @@ public function setDocumentManager(DocumentManager $dm);
3436
/**
3537
* Sets the array of raw mongo data that will be used to initialize this collection.
3638
*
37-
* @param mixed[] $mongoData
38-
*
3939
* @return void
4040
*/
41-
public function setMongoData(array $mongoData);
41+
public function setMongoData(Document|PackedArray $mongoData);
4242

4343
/**
4444
* Gets the array of raw mongo data that will be used to initialize this collection.
4545
*
46-
* @return mixed[] $mongoData
46+
* @return Document|PackedArray|null $mongoData
4747
*/
4848
public function getMongoData();
4949

lib/Doctrine/ODM/MongoDB/PersistentCollection/PersistentCollectionTrait.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Doctrine\ODM\MongoDB\MongoDBException;
1313
use Doctrine\ODM\MongoDB\UnitOfWork;
1414
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
15+
use MongoDB\BSON\Document;
16+
use MongoDB\BSON\PackedArray;
1517
use ReturnTypeWillChange;
1618
use Traversable;
1719

@@ -84,10 +86,8 @@ trait PersistentCollectionTrait
8486

8587
/**
8688
* The raw mongo data that will be used to initialize this collection.
87-
*
88-
* @var mixed[]
8989
*/
90-
private array $mongoData = [];
90+
private Document|PackedArray $mongoData;
9191

9292
/**
9393
* Any hints to account for during reconstitution/lookup of the documents.
@@ -103,14 +103,14 @@ public function setDocumentManager(DocumentManager $dm)
103103
$this->uow = $dm->getUnitOfWork();
104104
}
105105

106-
public function setMongoData(array $mongoData)
106+
public function setMongoData(Document|PackedArray $mongoData)
107107
{
108108
$this->mongoData = $mongoData;
109109
}
110110

111111
public function getMongoData()
112112
{
113-
return $this->mongoData;
113+
return $this->mongoData ?? null;
114114
}
115115

116116
public function setHints(array $hints)
@@ -143,7 +143,7 @@ public function initialize()
143143
$this->uow->loadCollection($this);
144144
$this->takeSnapshot();
145145

146-
$this->mongoData = [];
146+
unset($this->mongoData);
147147

148148
// Reattach any NEW objects added through add()
149149
if (! $newObjects) {
@@ -480,7 +480,7 @@ public function clear()
480480
}
481481
}
482482

483-
$this->mongoData = [];
483+
unset($this->mongoData);
484484
$this->coll->clear();
485485

486486
// Nothing to do for inverse-side collections

0 commit comments

Comments
 (0)