diff --git a/UPGRADE-5.7.md b/UPGRADE-5.7.md index 8595b0e04f9..95e641dbf71 100644 --- a/UPGRADE-5.7.md +++ b/UPGRADE-5.7.md @@ -12,7 +12,7 @@ This changelog references changes done in Shopware 5.7 patch versions. * Changed the test kernel, so PHPUnit tests do no longer ignore PHP warnings and notices and are failing instead * Updated `cocur/slugify` to version 4.4.0 -* Updated `doctrine/orm` to version 2.15.3 +* Updated `doctrine/orm` to version 2.16.2 * Updated `google/cloud-storage` to version 1.33.1 * Updated `guzzlehttp/guzzle` to version 7.8.0 * Updated `guzzlehttp/psr7` to version 2.6.1 diff --git a/composer.json b/composer.json index bb7bdfe9e0b..885b21701ad 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "doctrine/dbal": "2.13.9", "doctrine/event-manager": "1.2.0", "doctrine/inflector": "2.0.4", - "doctrine/orm": "2.15.3", + "doctrine/orm": "2.16.2", "doctrine/persistence": "3.2.0", "elasticsearch/elasticsearch": "^7", "fig/link-util": "1.1.2", diff --git a/composer.lock b/composer.lock index 2c4ee85c3fc..bcc1b0f4afc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fcf8b51a017cb3361cd1b5536803244a", + "content-hash": "0ceb5ce01b5f3561b5523c9cd988c81a", "packages": [ { "name": "aws/aws-crt-php", @@ -1285,16 +1285,16 @@ }, { "name": "doctrine/orm", - "version": "2.15.3", + "version": "2.16.2", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5" + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/4c3bd208018c26498e5f682aaad45fa00ea307d5", - "reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5", + "url": "https://api.github.com/repos/doctrine/orm/zipball/17500f56eaa930f5cd14d765bc2cd851c7d37cc0", + "reference": "17500f56eaa930f5cd14d765bc2cd851c7d37cc0", "shasum": "" }, "require": { @@ -1323,14 +1323,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.18", + "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.12.0" + "vimeo/psalm": "4.30.0 || 5.14.1" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", @@ -1380,9 +1380,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.15.3" + "source": "https://github.com/doctrine/orm/tree/2.16.2" }, - "time": "2023-06-22T12:36:06+00:00" + "time": "2023-08-27T18:21:56+00:00" }, { "name": "doctrine/persistence", diff --git a/engine/Library/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/engine/Library/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index e2b6484064f..7511227daae 100644 --- a/engine/Library/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/engine/Library/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -183,7 +183,7 @@ class BasicEntityPersister implements EntityPersister * * @var IdentifierFlattener */ - private $identifierFlattener; + protected $identifierFlattener; /** @var CachedPersisterContext */ protected $currentPersisterContext; @@ -256,17 +256,17 @@ public function getInserts() public function executeInserts() { if (! $this->queuedInserts) { - return []; + return; } - $postInsertIds = []; + $uow = $this->em->getUnitOfWork(); $idGenerator = $this->class->idGenerator; $isPostInsertId = $idGenerator->isPostInsertGenerator(); $stmt = $this->conn->prepare($this->getInsertSQL()); $tableName = $this->class->getTableName(); - foreach ($this->queuedInserts as $entity) { + foreach ($this->queuedInserts as $key => $entity) { $insertData = $this->prepareInsertData($entity); if (isset($insertData[$tableName])) { @@ -280,12 +280,10 @@ public function executeInserts() $stmt->executeStatement(); if ($isPostInsertId) { - $generatedId = $idGenerator->generateId($this->em, $entity); - $id = [$this->class->identifier[0] => $generatedId]; - $postInsertIds[] = [ - 'generatedId' => $generatedId, - 'entity' => $entity, - ]; + $generatedId = $idGenerator->generateId($this->em, $entity); + $id = [$this->class->identifier[0] => $generatedId]; + + $uow->assignPostInsertId($entity, $generatedId); } else { $id = $this->class->getIdentifierValues($entity); } @@ -293,11 +291,16 @@ public function executeInserts() if ($this->class->requiresFetchAfterChange) { $this->assignDefaultVersionAndUpsertableValues($entity, $id); } - } - - $this->queuedInserts = []; - return $postInsertIds; + // Unset this queued insert, so that the prepareUpdateData() method knows right away + // (for the next entity already) that the current entity has been written to the database + // and no extra updates need to be scheduled to refer to it. + // + // In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities + // from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they + // were given to our addInsert() method. + unset($this->queuedInserts[$key]); + } } /** @@ -376,7 +379,7 @@ protected function fetchVersionAndNotUpsertableValues($versionedClass, array $id * @return int[]|null[]|string[] * @psalm-return list */ - private function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array + final protected function extractIdentifierTypes(array $id, ClassMetadata $versionedClass): array { $types = []; @@ -675,10 +678,30 @@ protected function prepareUpdateData($entity, bool $isInsert = false) if ($newVal !== null) { $oid = spl_object_id($newVal); - if (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) { - // The associated entity $newVal is not yet persisted, so we must - // set $newVal = null, in order to insert a null value and schedule an - // extra update on the UnitOfWork. + // If the associated entity $newVal is not yet persisted and/or does not yet have + // an ID assigned, we must set $newVal = null. This will insert a null value and + // schedule an extra update on the UnitOfWork. + // + // This gives us extra time to a) possibly obtain a database-generated identifier + // value for $newVal, and b) insert $newVal into the database before the foreign + // key reference is being made. + // + // When looking at $this->queuedInserts and $uow->isScheduledForInsert, be aware + // of the implementation details that our own executeInserts() method will remove + // entities from the former as soon as the insert statement has been executed and + // a post-insert ID has been assigned (if necessary), and that the UnitOfWork has + // already removed entities from its own list at the time they were passed to our + // addInsert() method. + // + // Then, there is one extra exception we can make: An entity that references back to itself + // _and_ uses an application-provided ID (the "NONE" generator strategy) also does not + // need the extra update, although it is still in the list of insertions itself. + // This looks like a minor optimization at first, but is the capstone for being able to + // use non-NULLable, self-referencing associations in applications that provide IDs (like UUIDs). + if ( + (isset($this->queuedInserts[$oid]) || $uow->isScheduledForInsert($newVal)) + && ! ($newVal === $entity && $this->class->isIdentifierNatural()) + ) { $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]); $newVal = null;