Skip to content

Commit

Permalink
Merge pull request #9870 from popov-a-e/GH-9807
Browse files Browse the repository at this point in the history
[GH-9807] Fix: initialize potentially empty collections at the hydration complete
  • Loading branch information
greg0ire authored Jul 3, 2022
2 parents 3295ccf + 79447cb commit 83c1ad2
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 10 deletions.
31 changes: 21 additions & 10 deletions lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class ObjectHydrator extends AbstractHydrator
/** @var mixed[] */
private $initializedCollections = [];

/** @var array<string, PersistentCollection> */
private $uninitializedCollections = [];

/** @var mixed[] */
private $existingCollections = [];

Expand Down Expand Up @@ -112,10 +115,11 @@ protected function cleanup()

parent::cleanup();

$this->identifierMap =
$this->initializedCollections =
$this->existingCollections =
$this->resultPointers = [];
$this->identifierMap =
$this->initializedCollections =
$this->uninitializedCollections =
$this->existingCollections =
$this->resultPointers = [];

if ($eagerLoad) {
$this->_uow->triggerEagerLoads();
Expand All @@ -126,10 +130,11 @@ protected function cleanup()

protected function cleanupAfterRowIteration(): void
{
$this->identifierMap =
$this->initializedCollections =
$this->existingCollections =
$this->resultPointers = [];
$this->identifierMap =
$this->initializedCollections =
$this->uninitializedCollections =
$this->existingCollections =
$this->resultPointers = [];
}

/**
Expand All @@ -148,6 +153,12 @@ protected function hydrateAllData()
$coll->takeSnapshot();
}

foreach ($this->uninitializedCollections as $coll) {
if (! $coll->isInitialized()) {
$coll->setInitialized(true);
}
}

return $result;
}

Expand Down Expand Up @@ -411,8 +422,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
Expand Down
129 changes: 129 additions & 0 deletions tests/Doctrine/Tests/ORM/Functional/Ticket/GH9807Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional\Ticket;

use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\Mocks\ArrayResultFactory;
use Doctrine\Tests\OrmFunctionalTestCase;

final class GH9807Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

$this->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<int, GH9807Join>
*/
private $joins;

/**
* @return Collection<int, GH9807Join>
*/
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<int, GH9807Main>
*/
private $mains;

/**
* @ORM\Column(type="string", nullable=false)
*
* @var string
*/
private $value;
}

0 comments on commit 83c1ad2

Please sign in to comment.