diff -Nru doctrine-2.16.0+dfsg/composer.json doctrine-2.16.1+dfsg/composer.json --- doctrine-2.16.0+dfsg/composer.json 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/composer.json 2023-08-09 13:05:08.000000000 +0000 @@ -42,14 +42,14 @@ "doctrine/annotations": "^1.13 || ^2", "doctrine/coding-standard": "^9.0.2 || ^12.0", "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.10.25", + "phpstan/phpstan": "~1.4.10 || 1.10.28", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2", "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.13.1" + "vimeo/psalm": "4.30.0 || 5.14.1" }, "conflict": { "doctrine/annotations": "<1.13 || >= 3.0" diff -Nru doctrine-2.16.0+dfsg/debian/changelog doctrine-2.16.1+dfsg/debian/changelog --- doctrine-2.16.0+dfsg/debian/changelog 2023-08-05 09:14:12.000000000 +0000 +++ doctrine-2.16.1+dfsg/debian/changelog 2023-08-12 06:36:05.000000000 +0000 @@ -1,3 +1,24 @@ +doctrine (2.16.1+dfsg-1) unstable; urgency=medium + + [ Matthias Pigulla ] + * Add an UPGRADE notice about the potential changes in commit order (#10866) + * Turn identity map collisions from exception to deprecation notice (#10878) + * Fix broken changeset computation for entities loaded through fetch=EAGER + + using inheritance (#10884) + * Document more clearly that the insert order is an implementation detail + (#10883) + * Use a dedicated exception for the check added in #10785 (#10881) + + [ Michael Olšavský ] + * Fix UnitOfWork->originalEntityData is missing not-modified collections + after computeChangeSet (#9301) + + [ Dieter Beck ] + * Add possibility to set reportFieldsWhereDeclared to true in ORMSetup + (#10865) + + -- David Prévot Sat, 12 Aug 2023 08:36:05 +0200 + doctrine (2.16.0+dfsg-1) unstable; urgency=medium [ Matthias Pigulla ] diff -Nru doctrine-2.16.0+dfsg/debian/patches/0004-Update-Conflicts-version.patch doctrine-2.16.1+dfsg/debian/patches/0004-Update-Conflicts-version.patch --- doctrine-2.16.0+dfsg/debian/patches/0004-Update-Conflicts-version.patch 2023-08-05 08:57:07.000000000 +0000 +++ doctrine-2.16.1+dfsg/debian/patches/0004-Update-Conflicts-version.patch 2023-08-12 06:29:00.000000000 +0000 @@ -10,11 +10,11 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json -index a897bbb..8f63e6d 100644 +index 1464616..136d52c 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ - "vimeo/psalm": "4.30.0 || 5.13.1" + "vimeo/psalm": "4.30.0 || 5.14.1" }, "conflict": { - "doctrine/annotations": "<1.13 || >= 3.0" diff -Nru doctrine-2.16.0+dfsg/debian/patches/0005-Drop-composer-runtime-api.patch doctrine-2.16.1+dfsg/debian/patches/0005-Drop-composer-runtime-api.patch --- doctrine-2.16.0+dfsg/debian/patches/0005-Drop-composer-runtime-api.patch 2023-08-05 08:57:07.000000000 +0000 +++ doctrine-2.16.1+dfsg/debian/patches/0005-Drop-composer-runtime-api.patch 2023-08-12 06:29:00.000000000 +0000 @@ -10,7 +10,7 @@ 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json -index 8f63e6d..c2680dd 100644 +index 136d52c..de786ee 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ diff -Nru doctrine-2.16.0+dfsg/debian/patches/0006-Use-installed-schema.patch doctrine-2.16.1+dfsg/debian/patches/0006-Use-installed-schema.patch --- doctrine-2.16.0+dfsg/debian/patches/0006-Use-installed-schema.patch 2023-08-05 08:57:07.000000000 +0000 +++ doctrine-2.16.1+dfsg/debian/patches/0006-Use-installed-schema.patch 2023-08-12 06:29:00.000000000 +0000 @@ -8,10 +8,10 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php -index abd94fd..98f588b 100644 +index 827ef87..46c4b9c 100644 --- a/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php +++ b/lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php -@@ -1002,7 +1002,7 @@ class XmlDriver extends FileDriver +@@ -1005,7 +1005,7 @@ class XmlDriver extends FileDriver $document = new DOMDocument(); $document->load($file); diff -Nru doctrine-2.16.0+dfsg/debian/patches/0007-Compatibility-with-recent-PHPUnit-10.patch doctrine-2.16.1+dfsg/debian/patches/0007-Compatibility-with-recent-PHPUnit-10.patch --- doctrine-2.16.0+dfsg/debian/patches/0007-Compatibility-with-recent-PHPUnit-10.patch 2023-08-05 08:57:07.000000000 +0000 +++ doctrine-2.16.1+dfsg/debian/patches/0007-Compatibility-with-recent-PHPUnit-10.patch 2023-08-12 06:29:00.000000000 +0000 @@ -8,10 +8,10 @@ 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Doctrine/Tests/OrmFunctionalTestCase.php b/tests/Doctrine/Tests/OrmFunctionalTestCase.php -index 07bf94e..1bb1dc0 100644 +index 4a64136..36d7e73 100644 --- a/tests/Doctrine/Tests/OrmFunctionalTestCase.php +++ b/tests/Doctrine/Tests/OrmFunctionalTestCase.php -@@ -841,7 +841,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase +@@ -845,7 +845,7 @@ abstract class OrmFunctionalTestCase extends OrmTestCase } /** @throws Throwable */ diff -Nru doctrine-2.16.0+dfsg/debian/upstream/changelog doctrine-2.16.1+dfsg/debian/upstream/changelog --- doctrine-2.16.0+dfsg/debian/upstream/changelog 2023-08-05 09:10:55.000000000 +0000 +++ doctrine-2.16.1+dfsg/debian/upstream/changelog 2023-08-12 06:35:31.000000000 +0000 @@ -1,3 +1,34 @@ +2.16.1 - 9 August 2023 + + Total issues resolved: 0 + Total pull requests resolved: 10 + Total contributors: 6 + +Static Analysis + + 10895: PHPStan 1.10.28, Psalm 5.14.1 thanks to @derrabus + 10870: Fix return type of getSingleScalarResult thanks to @whatUwant + +Bug,Regression + + 10884: Fix broken changeset computation for entities loaded through fetch=EAGER + using inheritance thanks to @mpdude + +Documentation + + 10883: Document more clearly that the insert order is an implementation detail thanks to @mpdude + 10866: Add an UPGRADE notice about the potential changes in commit order thanks to @mpdude + 10862: Update branch metadata thanks to @greg0ire + +Bug + + 10881: Use a dedicated exception for the check added in #10785 thanks to @mpdude + 10865: Add possibility to set reportFieldsWhereDeclared in ORMSetup thanks to @W0rma + 9301: Fix UnitOfWork->originalEntityData is missing not-modified collections after computeChangeSet thanks to @olsavmic + +Bug,Deprecation + + 10878: Turn identity map collisions from exception to deprecation notice thanks to @mpdude + 2.16.0 - 1 August 2023 Total issues resolved: 0 diff -Nru doctrine-2.16.0+dfsg/.doctrine-project.json doctrine-2.16.1+dfsg/.doctrine-project.json --- doctrine-2.16.0+dfsg/.doctrine-project.json 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/.doctrine-project.json 2023-08-09 13:05:08.000000000 +0000 @@ -12,15 +12,15 @@ "upcoming": true }, { - "name": "2.16", - "branchName": "2.16.x", - "slug": "2.16", + "name": "2.17", + "branchName": "2.17.x", + "slug": "2.17", "upcoming": true }, { - "name": "2.15", - "branchName": "2.15.x", - "slug": "2.15", + "name": "2.16", + "branchName": "2.16.x", + "slug": "2.16", "current": true, "aliases": [ "current", @@ -28,6 +28,12 @@ ] }, { + "name": "2.15", + "branchName": "2.15.x", + "slug": "2.15", + "maintained": false + }, + { "name": "2.14", "branchName": "2.14.x", "slug": "2.14", diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/AbstractQuery.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/AbstractQuery.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/AbstractQuery.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/AbstractQuery.php 2023-08-09 13:05:08.000000000 +0000 @@ -1010,9 +1010,8 @@ * * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR). * - * @return bool|float|int|string The scalar result. + * @return bool|float|int|string|null The scalar result. * - * @throws NoResultException If the query returned no result. * @throws NonUniqueResultException If the query result is not unique. */ public function getSingleScalarResult() diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Configuration.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Configuration.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Configuration.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Configuration.php 2023-08-09 13:05:08.000000000 +0000 @@ -1117,4 +1117,14 @@ $this->_attributes['isLazyGhostObjectEnabled'] = $flag; } + + public function setRejectIdCollisionInIdentityMap(bool $flag): void + { + $this->_attributes['rejectIdCollisionInIdentityMap'] = $flag; + } + + public function isRejectIdCollisionInIdentityMapEnabled(): bool + { + return $this->_attributes['rejectIdCollisionInIdentityMap'] ?? false; + } } diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Exception/EntityIdentityCollisionException.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Exception/EntityIdentityCollisionException.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Exception/EntityIdentityCollisionException.php 1970-01-01 00:00:00.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Exception/EntityIdentityCollisionException.php 2023-08-09 13:05:08.000000000 +0000 @@ -0,0 +1,42 @@ +children() as $action) { + $children = $cascadeElement->children(); + assert($children !== null); + + foreach ($children as $action) { // According to the JPA specifications, XML uses "cascade-persist" // instead of "persist". Here, both variations // are supported because YAML, Annotation and Attribute use "persist" diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Mapping/UnderscoreNamingStrategy.php 2023-08-09 13:05:08.000000000 +0000 @@ -30,7 +30,10 @@ /** @var int */ private $case; - /** @var string */ + /** + * @var string + * @psalm-var non-empty-string + */ private $pattern; /** diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/ORMSetup.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/ORMSetup.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/ORMSetup.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/ORMSetup.php 2023-08-09 13:05:08.000000000 +0000 @@ -101,10 +101,11 @@ array $paths, bool $isDevMode = false, ?string $proxyDir = null, - ?CacheItemPoolInterface $cache = null + ?CacheItemPoolInterface $cache = null, + bool $reportFieldsWhereDeclared = false ): Configuration { $config = self::createConfiguration($isDevMode, $proxyDir, $cache); - $config->setMetadataDriverImpl(new AttributeDriver($paths)); + $config->setMetadataDriverImpl(new AttributeDriver($paths, $reportFieldsWhereDeclared)); return $config; } diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Tools/EntityGenerator.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Tools/EntityGenerator.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Tools/EntityGenerator.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Tools/EntityGenerator.php 2023-08-09 13:05:08.000000000 +0000 @@ -1376,7 +1376,7 @@ $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName); $var = sprintf('%sMethodTemplate', $type); - $template = static::$$var; + $template = (string) static::$$var; $methodTypeHint = ''; $types = Type::getTypesMap(); @@ -1695,7 +1695,7 @@ } if (isset($fieldMapping['options']['comment']) && $fieldMapping['options']['comment']) { - $options[] = '"comment"="' . str_replace('"', '""', $fieldMapping['options']['comment']) . '"'; + $options[] = '"comment"="' . str_replace('"', '""', (string) $fieldMapping['options']['comment']) . '"'; } if (isset($fieldMapping['options']['collation']) && $fieldMapping['options']['collation']) { diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/Tools/Pagination/LimitSubqueryOutputWalker.php 2023-08-09 13:05:08.000000000 +0000 @@ -404,7 +404,7 @@ /** * @return string[][] - * @psalm-return array{0: list, 1: list} + * @psalm-return array{0: list, 1: list} */ private function generateSqlAliasReplacements(): array { diff -Nru doctrine-2.16.0+dfsg/lib/Doctrine/ORM/UnitOfWork.php doctrine-2.16.1+dfsg/lib/Doctrine/ORM/UnitOfWork.php --- doctrine-2.16.0+dfsg/lib/Doctrine/ORM/UnitOfWork.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/lib/Doctrine/ORM/UnitOfWork.php 2023-08-09 13:05:08.000000000 +0000 @@ -23,6 +23,7 @@ use Doctrine\ORM\Event\PrePersistEventArgs; use Doctrine\ORM\Event\PreRemoveEventArgs; use Doctrine\ORM\Event\PreUpdateEventArgs; +use Doctrine\ORM\Exception\EntityIdentityCollisionException; use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Exception\UnexpectedAssociationValue; use Doctrine\ORM\Id\AssignedGenerator; @@ -692,6 +693,7 @@ if ($class->isCollectionValuedAssociation($name) && $value !== null) { if ($value instanceof PersistentCollection) { if ($value->getOwner() === $entity) { + $actualData[$name] = $value; continue; } @@ -1623,6 +1625,7 @@ * the entity in question is already managed. * * @throws ORMInvalidArgumentException + * @throws EntityIdentityCollisionException * * @ignore */ @@ -1634,27 +1637,38 @@ if (isset($this->identityMap[$className][$idHash])) { if ($this->identityMap[$className][$idHash] !== $entity) { - throw new RuntimeException(sprintf( + if ($this->em->getConfiguration()->isRejectIdCollisionInIdentityMapEnabled()) { + throw EntityIdentityCollisionException::create($this->identityMap[$className][$idHash], $entity, $idHash); + } + + Deprecation::trigger( + 'doctrine/orm', + 'https://github.com/doctrine/orm/pull/10785', <<<'EXCEPTION' While adding an entity of class %s with an ID hash of "%s" to the identity map, -another object of class %s was already present for the same ID. This exception -is a safeguard against an internal inconsistency - IDs should uniquely map to -entity object instances. This problem may occur if: +another object of class %s was already present for the same ID. This will trigger +an exception in ORM 3.0. -- you use application-provided IDs and reuse ID values; -- database-provided IDs are reassigned after truncating the database without - clearing the EntityManager; -- you might have been using EntityManager#getReference() to create a reference - for a nonexistent ID that was subsequently (by the RDBMS) assigned to another - entity. +IDs should uniquely map to entity object instances. This problem may occur if: -Otherwise, it might be an ORM-internal inconsistency, please report it. +- you use application-provided IDs and reuse ID values; +- database-provided IDs are reassigned after truncating the database without +clearing the EntityManager; +- you might have been using EntityManager#getReference() to create a reference +for a nonexistent ID that was subsequently (by the RDBMS) assigned to another +entity. + +Otherwise, it might be an ORM-internal inconsistency, please report it. + +To opt-in to the new exception, call +\Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap on the entity +manager's configuration. EXCEPTION , get_class($entity), $idHash, get_class($this->identityMap[$className][$idHash]) - )); + ); } return false; @@ -3014,6 +3028,7 @@ // We are negating the condition here. Other cases will assume it is valid! case $hints['fetchMode'][$class->name][$field] !== ClassMetadata::FETCH_EAGER: $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $normalizedAssociatedId); + $this->registerManaged($newValue, $associatedId, []); break; // Deferred eager load only works for single identifier classes @@ -3022,6 +3037,7 @@ $this->eagerLoadingEntities[$targetClass->rootEntityName][$relatedIdHash] = current($normalizedAssociatedId); $newValue = $this->em->getProxyFactory()->getProxy($assoc['targetEntity'], $normalizedAssociatedId); + $this->registerManaged($newValue, $associatedId, []); break; default: @@ -3029,13 +3045,6 @@ $newValue = $this->em->find($assoc['targetEntity'], $normalizedAssociatedId); break; } - - if ($newValue === null) { - break; - } - - $this->registerManaged($newValue, $associatedId, []); - break; } $this->originalEntityData[$oid][$field] = $newValue; diff -Nru doctrine-2.16.0+dfsg/psalm-baseline.xml doctrine-2.16.1+dfsg/psalm-baseline.xml --- doctrine-2.16.0+dfsg/psalm-baseline.xml 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/psalm-baseline.xml 2023-08-09 13:05:08.000000000 +0000 @@ -1,5 +1,5 @@ - + IterableResult @@ -964,9 +964,6 @@ children()]]> children()]]> - - children()]]> - getName() === 'embeddable']]> getName() === 'entity']]> diff -Nru doctrine-2.16.0+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Child.php doctrine-2.16.1+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Child.php --- doctrine-2.16.0+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Child.php 1970-01-01 00:00:00.000000000 +0000 +++ doctrine-2.16.1+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Child.php 2023-08-09 13:05:08.000000000 +0000 @@ -0,0 +1,44 @@ + + * @ManyToMany(targetEntity="Issue9300Parent") + */ + public $parents; + + /** + * @var string + * @Column(type="string") + */ + public $name; + + public function __construct() + { + $this->parents = new ArrayCollection(); + } +} diff -Nru doctrine-2.16.0+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Parent.php doctrine-2.16.1+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Parent.php --- doctrine-2.16.0+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Parent.php 1970-01-01 00:00:00.000000000 +0000 +++ doctrine-2.16.1+dfsg/tests/Doctrine/Tests/Models/Issue9300/Issue9300Parent.php 2023-08-09 13:05:08.000000000 +0000 @@ -0,0 +1,30 @@ +_em->getConfiguration()->setRejectIdCollisionInIdentityMap(true); + $user = new CmsUser(); $user->name = 'test'; $user->username = 'test'; @@ -1345,7 +1347,7 @@ // Now the database will assign an ID to the $user2 entity, but that place // in the identity map is already taken by user error. - $this->expectException(RuntimeException::class); + $this->expectException(EntityIdentityCollisionException::class); $this->expectExceptionMessageMatches('/another object .* was already present for the same ID/'); // depending on ID generation strategy, the ID may be asssigned already here diff -Nru doctrine-2.16.0+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10880Test.php doctrine-2.16.1+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10880Test.php --- doctrine-2.16.0+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10880Test.php 1970-01-01 00:00:00.000000000 +0000 +++ doctrine-2.16.1+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/GH10880Test.php 2023-08-09 13:05:08.000000000 +0000 @@ -0,0 +1,119 @@ +setUpEntitySchema([ + GH10880BaseProcess::class, + GH10880Process::class, + GH10880ProcessOwner::class, + ]); + } + + public function testProcessShouldBeUpdated(): void + { + $process = new GH10880Process(); + $process->description = 'first value'; + + $owner = new GH10880ProcessOwner(); + $owner->process = $process; + + $this->_em->persist($process); + $this->_em->persist($owner); + $this->_em->flush(); + $this->_em->clear(); + + $ownerLoaded = $this->_em->getRepository(GH10880ProcessOwner::class)->find($owner->id); + $ownerLoaded->process->description = 'other description'; + + $queryLog = $this->getQueryLog(); + $queryLog->reset()->enable(); + $this->_em->flush(); + + $this->removeTransactionCommandsFromQueryLog(); + + self::assertCount(1, $queryLog->queries); + $query = reset($queryLog->queries); + self::assertSame('UPDATE GH10880BaseProcess SET description = ? WHERE id = ?', $query['sql']); + } + + private function removeTransactionCommandsFromQueryLog(): void + { + $log = $this->getQueryLog(); + + foreach ($log->queries as $key => $entry) { + if ($entry['sql'] === '"START TRANSACTION"' || $entry['sql'] === '"COMMIT"') { + unset($log->queries[$key]); + } + } + } +} + +/** + * @ORM\Entity + */ +class GH10880ProcessOwner +{ + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + * + * @var int + */ + public $id; + + /** + * fetch=EAGER is important to reach the part of \Doctrine\ORM\UnitOfWork::createEntity() + * that is important for this regression test + * + * @ORM\ManyToOne(targetEntity="GH10880Process", fetch="EAGER") + * + * @var GH10880Process + */ + public $process; +} + +/** + * @ORM\Entity() + * @ORM\InheritanceType("SINGLE_TABLE") + * @ORM\DiscriminatorColumn(name="type", type="string") + * @ORM\DiscriminatorMap({"process" = "GH10880Process"}) + */ +abstract class GH10880BaseProcess +{ + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + * + * @var int + */ + public $id; + + /** + * @ORM\Column(type="text") + * + * @var string + */ + public $description; +} + +/** + * @ORM\Entity + */ +class GH10880Process extends GH10880BaseProcess +{ +} diff -Nru doctrine-2.16.0+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue9300Test.php doctrine-2.16.1+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue9300Test.php --- doctrine-2.16.0+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue9300Test.php 1970-01-01 00:00:00.000000000 +0000 +++ doctrine-2.16.1+dfsg/tests/Doctrine/Tests/ORM/Functional/Ticket/Issue9300Test.php 2023-08-09 13:05:08.000000000 +0000 @@ -0,0 +1,80 @@ +useModelSet('issue9300'); + + parent::setUp(); + } + + /** + * @group GH-9300 + */ + public function testPersistedCollectionIsPresentInOriginalDataAfterFlush(): void + { + $parent = new Issue9300Parent(); + $child = new Issue9300Child(); + $child->parents->add($parent); + + $parent->name = 'abc'; + $child->name = 'abc'; + + $this->_em->persist($parent); + $this->_em->persist($child); + $this->_em->flush(); + + $parent->name = 'abcd'; + $child->name = 'abcd'; + + $this->_em->flush(); + + self::assertArrayHasKey('parents', $this->_em->getUnitOfWork()->getOriginalEntityData($child)); + } + + /** + * @group GH-9300 + */ + public function testPersistingCollectionAfterFlushWorksAsExpected(): void + { + $parentOne = new Issue9300Parent(); + $parentTwo = new Issue9300Parent(); + $childOne = new Issue9300Child(); + + $parentOne->name = 'abc'; + $parentTwo->name = 'abc'; + $childOne->name = 'abc'; + $childOne->parents = new ArrayCollection([$parentOne]); + + $this->_em->persist($parentOne); + $this->_em->persist($parentTwo); + $this->_em->persist($childOne); + $this->_em->flush(); + + // Recalculate change-set -> new original data + $childOne->name = 'abcd'; + $this->_em->flush(); + + $childOne->parents = new ArrayCollection([$parentTwo]); + + $this->_em->flush(); + $this->_em->clear(); + + $childOneFresh = $this->_em->find(Issue9300Child::class, $childOne->id); + self::assertCount(1, $childOneFresh->parents); + self::assertEquals($parentTwo->id, $childOneFresh->parents[0]->id); + } +} diff -Nru doctrine-2.16.0+dfsg/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php doctrine-2.16.1+dfsg/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php --- doctrine-2.16.0+dfsg/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/tests/Doctrine/Tests/ORM/UnitOfWorkTest.php 2023-08-09 13:05:08.000000000 +0000 @@ -13,6 +13,7 @@ use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; use Doctrine\ORM\EntityNotFoundException; use Doctrine\ORM\Events; +use Doctrine\ORM\Exception\EntityIdentityCollisionException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; @@ -41,7 +42,6 @@ use Doctrine\Tests\OrmTestCase; use Doctrine\Tests\PHPUnitCompatibility\MockBuilderCompatibilityTools; use PHPUnit\Framework\MockObject\MockObject; -use RuntimeException; use stdClass; use function assert; @@ -927,8 +927,28 @@ self::assertEmpty($user->phonenumbers->getSnapshot()); } + public function testItTriggersADeprecationNoticeWhenApplicationProvidedIdsCollide(): void + { + // We're using application-provided IDs and assign the same ID twice + // Note this is about colliding IDs in the identity map in memory. + // Duplicate database-level IDs would be spotted when the EM is flushed. + + $phone1 = new CmsPhonenumber(); + $phone1->phonenumber = '1234'; + $this->_unitOfWork->persist($phone1); + + $phone2 = new CmsPhonenumber(); + $phone2->phonenumber = '1234'; + + $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/10785'); + + $this->_unitOfWork->persist($phone2); + } + public function testItThrowsWhenApplicationProvidedIdsCollide(): void { + $this->_emMock->getConfiguration()->setRejectIdCollisionInIdentityMap(true); + // We're using application-provided IDs and assign the same ID twice // Note this is about colliding IDs in the identity map in memory. // Duplicate database-level IDs would be spotted when the EM is flushed. @@ -940,7 +960,7 @@ $phone2 = new CmsPhonenumber(); $phone2->phonenumber = '1234'; - $this->expectException(RuntimeException::class); + $this->expectException(EntityIdentityCollisionException::class); $this->expectExceptionMessageMatches('/another object .* was already present for the same ID/'); $this->_unitOfWork->persist($phone2); diff -Nru doctrine-2.16.0+dfsg/tests/Doctrine/Tests/OrmFunctionalTestCase.php doctrine-2.16.1+dfsg/tests/Doctrine/Tests/OrmFunctionalTestCase.php --- doctrine-2.16.0+dfsg/tests/Doctrine/Tests/OrmFunctionalTestCase.php 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/tests/Doctrine/Tests/OrmFunctionalTestCase.php 2023-08-09 13:05:08.000000000 +0000 @@ -338,6 +338,10 @@ Models\Issue5989\Issue5989Employee::class, Models\Issue5989\Issue5989Manager::class, ], + 'issue9300' => [ + Models\Issue9300\Issue9300Child::class, + Models\Issue9300\Issue9300Parent::class, + ], ]; /** @param class-string ...$models */ diff -Nru doctrine-2.16.0+dfsg/UPGRADE.md doctrine-2.16.1+dfsg/UPGRADE.md --- doctrine-2.16.0+dfsg/UPGRADE.md 2023-08-01 12:07:04.000000000 +0000 +++ doctrine-2.16.1+dfsg/UPGRADE.md 2023-08-09 13:05:08.000000000 +0000 @@ -1,5 +1,31 @@ # Upgrade to 2.16 +## Deprecated accepting duplicate IDs in the identity map + +For any given entity class and ID value, there should be only one object instance +representing the entity. + +In https://github.com/doctrine/orm/pull/10785, a check was added that will guard this +in the identity map. The most probable cause for violations of this rule are collisions +of application-provided IDs. + +In ORM 2.16.0, the check was added by throwing an exception. In ORM 2.16.1, this will be +changed to a deprecation notice. ORM 3.0 will make it an exception again. Use +`\Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` if you want to opt-in +to the new mode. + +## Potential changes to the order in which `INSERT`s are executed + +In https://github.com/doctrine/orm/pull/10547, the commit order computation was improved +to fix a series of bugs where a correct (working) commit order was previously not found. +Also, the new computation may get away with fewer queries being executed: By inserting +referred-to entities first and using their ID values for foreign key fields in subsequent +`INSERT` statements, additional `UPDATE` statements that were previously necessary can be +avoided. + +When using database-provided, auto-incrementing IDs, this may lead to IDs being assigned +to entities in a different order than it was previously the case. + ## Deprecated `\Doctrine\ORM\Internal\CommitOrderCalculator` and related classes With changes made to the commit order computation, the internal classes