mirror of
https://github.com/doctrine/orm.git
synced 2026-04-25 07:28:04 +02:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 597a63a86c | |||
| 6b220e3c90 | |||
| 6de4b68705 | |||
| 16c0151831 | |||
| 440b244ebc | |||
| a616914887 | |||
| fd0bdc69b0 | |||
| f50803ccb9 | |||
| eeefc6bc0f | |||
| 710dde83aa |
+12
-6
@@ -12,21 +12,27 @@
|
||||
"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",
|
||||
"stable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2.15",
|
||||
"branchName": "2.15.x",
|
||||
"slug": "2.15",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
|
||||
+26
@@ -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
|
||||
|
||||
+2
-2
@@ -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"
|
||||
|
||||
@@ -29,7 +29,7 @@ steps of configuration.
|
||||
|
||||
$config = new Configuration;
|
||||
$config->setMetadataCache($metadataCache);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCache($queryCache);
|
||||
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
|
||||
@@ -134,7 +134,7 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
The path information to the entities is required for the attribute
|
||||
|
||||
@@ -192,6 +192,11 @@ be properly synchronized with the database when
|
||||
database in the most efficient way and a single, short transaction,
|
||||
taking care of maintaining referential integrity.
|
||||
|
||||
.. note::
|
||||
|
||||
Do not make any assumptions in your code about the number of queries
|
||||
it takes to flush changes, about the ordering of ``INSERT``, ``UPDATE``
|
||||
and ``DELETE`` queries or the order in which entities will be processed.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -1010,9 +1010,8 @@ abstract class AbstractQuery
|
||||
*
|
||||
* 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()
|
||||
|
||||
@@ -1117,4 +1117,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
$this->_attributes['isLazyGhostObjectEnabled'] = $flag;
|
||||
}
|
||||
|
||||
public function setRejectIdCollisionInIdentityMap(bool $flag): void
|
||||
{
|
||||
$this->_attributes['rejectIdCollisionInIdentityMap'] = $flag;
|
||||
}
|
||||
|
||||
public function isRejectIdCollisionInIdentityMapEnabled(): bool
|
||||
{
|
||||
return $this->_attributes['rejectIdCollisionInIdentityMap'] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Exception;
|
||||
|
||||
use function get_class;
|
||||
use function sprintf;
|
||||
|
||||
final class EntityIdentityCollisionException extends ORMException
|
||||
{
|
||||
/**
|
||||
* @param object $existingEntity
|
||||
* @param object $newEntity
|
||||
*/
|
||||
public static function create($existingEntity, $newEntity, string $idHash): self
|
||||
{
|
||||
return new self(
|
||||
sprintf(
|
||||
<<<'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:
|
||||
|
||||
- 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.
|
||||
EXCEPTION
|
||||
,
|
||||
get_class($newEntity),
|
||||
$idHash,
|
||||
get_class($existingEntity)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -944,7 +944,10 @@ class XmlDriver extends FileDriver
|
||||
private function getCascadeMappings(SimpleXMLElement $cascadeElement): array
|
||||
{
|
||||
$cascades = [];
|
||||
foreach ($cascadeElement->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"
|
||||
|
||||
@@ -30,7 +30,10 @@ class UnderscoreNamingStrategy implements NamingStrategy
|
||||
/** @var int */
|
||||
private $case;
|
||||
|
||||
/** @var string */
|
||||
/**
|
||||
* @var string
|
||||
* @psalm-var non-empty-string
|
||||
*/
|
||||
private $pattern;
|
||||
|
||||
/**
|
||||
|
||||
@@ -101,10 +101,11 @@ final class ORMSetup
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1376,7 +1376,7 @@ public function __construct(<params>)
|
||||
$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 @@ public function __construct(<params>)
|
||||
}
|
||||
|
||||
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']) {
|
||||
|
||||
@@ -404,7 +404,7 @@ class LimitSubqueryOutputWalker extends SqlWalker
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
* @psalm-return array{0: list<string>, 1: list<string>}
|
||||
* @psalm-return array{0: list<non-empty-string>, 1: list<string>}
|
||||
*/
|
||||
private function generateSqlAliasReplacements(): array
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
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 @@ class UnitOfWork implements PropertyChangedListener
|
||||
if ($class->isCollectionValuedAssociation($name) && $value !== null) {
|
||||
if ($value instanceof PersistentCollection) {
|
||||
if ($value->getOwner() === $entity) {
|
||||
$actualData[$name] = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1623,6 +1625,7 @@ class UnitOfWork implements PropertyChangedListener
|
||||
* the entity in question is already managed.
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws EntityIdentityCollisionException
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
@@ -1634,27 +1637,38 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
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.
|
||||
|
||||
IDs should uniquely map to entity object instances. This problem may occur if:
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
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 @@ EXCEPTION
|
||||
// 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 @@ EXCEPTION
|
||||
$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 @@ EXCEPTION
|
||||
$newValue = $this->em->find($assoc['targetEntity'], $normalizedAssociatedId);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($newValue === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->registerManaged($newValue, $associatedId, []);
|
||||
break;
|
||||
}
|
||||
|
||||
$this->originalEntityData[$oid][$field] = $newValue;
|
||||
|
||||
+1
-4
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<files psalm-version="5.13.1@086b94371304750d1c673315321a55d15fc59015">
|
||||
<files psalm-version="5.14.1@b9d355e0829c397b9b3b47d0c0ed042a8a70284d">
|
||||
<file src="lib/Doctrine/ORM/AbstractQuery.php">
|
||||
<DeprecatedClass>
|
||||
<code>IterableResult</code>
|
||||
@@ -964,9 +964,6 @@
|
||||
<code><![CDATA[$joinColumnElement['options']->children()]]></code>
|
||||
<code><![CDATA[$option->children()]]></code>
|
||||
</PossiblyNullArgument>
|
||||
<PossiblyNullIterator>
|
||||
<code><![CDATA[$cascadeElement->children()]]></code>
|
||||
</PossiblyNullIterator>
|
||||
<TypeDoesNotContainType>
|
||||
<code><![CDATA[$xmlRoot->getName() === 'embeddable']]></code>
|
||||
<code><![CDATA[$xmlRoot->getName() === 'entity']]></code>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Issue9300;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToMany;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Issue9300Child
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Issue9300Parent>
|
||||
* @ManyToMany(targetEntity="Issue9300Parent")
|
||||
*/
|
||||
public $parents;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @Column(type="string")
|
||||
*/
|
||||
public $name;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->parents = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Issue9300;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Issue9300Parent
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @Column(type="string")
|
||||
*/
|
||||
public $name;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use Doctrine\ORM\Exception\EntityIdentityCollisionException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
@@ -20,7 +21,6 @@ use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
use function get_class;
|
||||
|
||||
@@ -1329,6 +1329,8 @@ class BasicFunctionalTest extends OrmFunctionalTestCase
|
||||
|
||||
public function testItThrowsWhenReferenceUsesIdAssignedByDatabase(): void
|
||||
{
|
||||
$this->_em->getConfiguration()->setRejectIdCollisionInIdentityMap(true);
|
||||
|
||||
$user = new CmsUser();
|
||||
$user->name = 'test';
|
||||
$user->username = 'test';
|
||||
@@ -1345,7 +1347,7 @@ class BasicFunctionalTest extends OrmFunctionalTestCase
|
||||
|
||||
// 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
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function reset;
|
||||
|
||||
class GH10880Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->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
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Tests\Models\Issue9300\Issue9300Child;
|
||||
use Doctrine\Tests\Models\Issue9300\Issue9300Parent;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
/**
|
||||
* @group GH-9300
|
||||
*/
|
||||
class Issue9300Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
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\Models\GeoNames\Country;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use Doctrine\Tests\PHPUnitCompatibility\MockBuilderCompatibilityTools;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
use function assert;
|
||||
@@ -927,7 +927,7 @@ class UnitOfWorkTest extends OrmTestCase
|
||||
self::assertEmpty($user->phonenumbers->getSnapshot());
|
||||
}
|
||||
|
||||
public function testItThrowsWhenApplicationProvidedIdsCollide(): void
|
||||
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.
|
||||
@@ -940,7 +940,27 @@ class UnitOfWorkTest extends OrmTestCase
|
||||
$phone2 = new CmsPhonenumber();
|
||||
$phone2->phonenumber = '1234';
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$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.
|
||||
|
||||
$phone1 = new CmsPhonenumber();
|
||||
$phone1->phonenumber = '1234';
|
||||
$this->_unitOfWork->persist($phone1);
|
||||
|
||||
$phone2 = new CmsPhonenumber();
|
||||
$phone2->phonenumber = '1234';
|
||||
|
||||
$this->expectException(EntityIdentityCollisionException::class);
|
||||
$this->expectExceptionMessageMatches('/another object .* was already present for the same ID/');
|
||||
|
||||
$this->_unitOfWork->persist($phone2);
|
||||
|
||||
@@ -338,6 +338,10 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
Models\Issue5989\Issue5989Employee::class,
|
||||
Models\Issue5989\Issue5989Manager::class,
|
||||
],
|
||||
'issue9300' => [
|
||||
Models\Issue9300\Issue9300Child::class,
|
||||
Models\Issue9300\Issue9300Parent::class,
|
||||
],
|
||||
];
|
||||
|
||||
/** @param class-string ...$models */
|
||||
|
||||
Reference in New Issue
Block a user