diff --git a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php index 4f21a578a9..477c359a5e 100644 --- a/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php +++ b/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php @@ -44,6 +44,9 @@ class ObjectHydrator extends AbstractHydrator /** @var mixed[] */ private $initializedCollections = []; + /** @var array */ + private $uninitializedCollections = []; + /** @var mixed[] */ private $existingCollections = []; @@ -148,6 +151,12 @@ protected function hydrateAllData() $coll->takeSnapshot(); } + foreach ($this->uninitializedCollections as $coll) { + if (! $coll->isInitialized()) { + $coll->setInitialized(true); + } + } + return $result; } @@ -411,8 +420,8 @@ protected function hydrateRowData(array $row, array &$result) } } elseif (! $reflFieldValue) { $this->initRelatedCollection($parentObject, $parentClass, $relationField, $parentAlias); - } elseif ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false) { - $reflFieldValue->setInitialized(true); + } elseif ($reflFieldValue instanceof PersistentCollection && $reflFieldValue->isInitialized() === false && ! isset($this->uninitializedCollections[$oid . $relationField])) { + $this->uninitializedCollections[$oid . $relationField] = $reflFieldValue; } } else { // PATH B: Single-valued association diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9807Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9807Test.php new file mode 100644 index 0000000000..c7cb744d1c --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH9807Test.php @@ -0,0 +1,129 @@ +createSchemaForModels(GH9807Main::class, GH9807Join::class); + } + + public function testHydrateJoinedCollectionWithFirstNullishRow(): void + { + $rsm = new ResultSetMapping(); + $rsm->addEntityResult(GH9807Main::class, 'm'); + $rsm->addJoinedEntityResult(GH9807Join::class, 'j', 'm', 'joins'); + + $rsm->addFieldResult('m', 'id_0', 'id'); + $rsm->addFieldResult('j', 'id_1', 'id'); + $rsm->addFieldResult('j', 'value_2', 'value'); + + $hydrator = new ObjectHydrator($this->_em); + + $uow = $this->_em->getUnitOfWork(); + + $uow->createEntity( + GH9807Main::class, + ['id' => 1] + ); + + $resultSet = [ + [ + 'id_0' => 1, + 'id_1' => null, + 'value_2' => null, + ], + [ + 'id_0' => 1, + 'id_1' => 1, + 'value_2' => '2', + ], + [ + 'id_0' => 1, + 'id_1' => 2, + 'value_2' => '2', + ], + ]; + + $stmt = ArrayResultFactory::createFromArray($resultSet); + + /** @var GH9807Main[] $result */ + $result = $hydrator->hydrateAll($stmt, $rsm); + + self::assertInstanceOf(GH9807Main::class, $result[0]); + self::assertCount(2, $result[0]->getJoins()); + } +} + +/** + * @Entity + */ +class GH9807Main +{ + /** + * @var int + * @Column(type="integer") + * @Id + * @GeneratedValue + */ + private $id; + + /** + * @ORM\ManyToMany(targetEntity="GH9807Join", inversedBy="starts") + * + * @var Collection + */ + private $joins; + + /** + * @return Collection + */ + public function getJoins(): Collection + { + return $this->joins; + } +} + +/** + * @Entity + */ +class GH9807Join +{ + /** + * @var int + * @Column(type="integer") + * @Id + * @GeneratedValue + */ + private $id; + + /** + * @ORM\ManyToMany(targetEntity="GH9807Main", mappedBy="bases") + * + * @var Collection + */ + private $mains; + + /** + * @ORM\Column(type="string", nullable=false) + * + * @var string + */ + private $value; +}