Skip to content

Commit f1483f8

Browse files
derrabusmpdudeMalteWunschFeoliusIvanStrygin
authored
Merge 2.10.x into 2.11.x (#9287)
* Better explain limitations of DQL "DELETE" (#9281) We think the current documentation does not stress these details enough, so that they are easily overlooked. Co-authored-by: Malte Wunsch <mw@webfactory.de> Co-authored-by: Malte Wunsch <mw@webfactory.de> * Put actual value instead of index inside $originalEntityData. (#9244) This fixes a bug with redundant UPDATE queries, that are executed when some entity uses foreign index of other entity as a primary key. This happens when after inserting related entities with $em->flush() call, you do the second $em->flush() without changing any data inside entities. Fixes GH8217. Co-authored-by: ivan <ivan.strygin@managinglife.com> * Allow symfony/cache 6 (#9283) * Fix XML export for `change-tracking-policy` (#9285) * Whitelist composer plugins used by this repository (#9286) Co-authored-by: Matthias Pigulla <mp@webfactory.de> Co-authored-by: Malte Wunsch <mw@webfactory.de> Co-authored-by: Ivan Strygin <feolius@gmail.com> Co-authored-by: ivan <ivan.strygin@managinglife.com> Co-authored-by: Fedir Zinchuk <getthesite@gmail.com>
2 parents ea4c9b2 + 72edfbc commit f1483f8

File tree

6 files changed

+178
-10
lines changed

6 files changed

+178
-10
lines changed

composer.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
1414
],
1515
"config": {
16+
"allow-plugins": {
17+
"composer/package-versions-deprecated": true,
18+
"dealerdirect/phpcodesniffer-composer-installer": true
19+
},
1620
"sort-packages": true
1721
},
1822
"require": {
@@ -42,7 +46,7 @@
4246
"phpstan/phpstan": "1.2.0",
4347
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
4448
"squizlabs/php_codesniffer": "3.6.2",
45-
"symfony/cache": "^4.4 || ^5.2",
49+
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
4650
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
4751
"vimeo/psalm": "4.15.0"
4852
},

docs/en/reference/dql-doctrine-query-language.rst

+17-5
Original file line numberDiff line numberDiff line change
@@ -670,11 +670,23 @@ The same restrictions apply for the reference of related entities.
670670

671671
.. warning::
672672

673-
DQL DELETE statements are ported directly into a
674-
Database DELETE statement and therefore bypass any events and checks for the
675-
version column if they are not explicitly added to the WHERE clause
676-
of the query. Additionally Deletes of specified entities are *NOT*
677-
cascaded to related entities even if specified in the metadata.
673+
DQL DELETE statements are ported directly into an SQL DELETE statement.
674+
Therefore, some limitations apply:
675+
676+
- Lifecycle events for the affected entities are not executed.
677+
- A cascading ``remove`` operation (as indicated e. g. by ``cascade={"remove"}``
678+
or ``cascade={"all"}`` in the mapping configuration) is not being performed
679+
for associated entities. You can rely on database level cascade operations by
680+
configuring each join column with the ``onDelete`` option.
681+
- Checks for the version column are bypassed if they are not explicitly added
682+
to the WHERE clause of the query.
683+
684+
When you rely on one of these features, one option is to use the
685+
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
686+
It means collections and related entities are fetched into memory
687+
(even if they are marked as lazy). Pulling object graphs into memory on cascade
688+
can cause considerable performance overhead, especially when the cascaded collections
689+
are large. Make sure to weigh the benefits and downsides.
678690

679691
Comments in queries
680692
-------------------

lib/Doctrine/ORM/Tools/Export/Driver/XmlExporter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public function exportClassMetadata(ClassMetadataInfo $metadata)
9090
$trackingPolicy = $this->_getChangeTrackingPolicyString($metadata->changeTrackingPolicy);
9191

9292
if ($trackingPolicy !== 'DEFERRED_IMPLICIT') {
93-
$root->addChild('change-tracking-policy', $trackingPolicy);
93+
$root->addAttribute('change-tracking-policy', $trackingPolicy);
9494
}
9595

9696
if (isset($metadata->table['indexes'])) {

lib/Doctrine/ORM/UnitOfWork.php

+5-3
Original file line numberDiff line numberDiff line change
@@ -1166,14 +1166,16 @@ private function addToEntityIdentifiersAndEntityMap(
11661166
$identifier = [];
11671167

11681168
foreach ($class->getIdentifierFieldNames() as $idField) {
1169-
$value = $class->getFieldValue($entity, $idField);
1169+
$origValue = $class->getFieldValue($entity, $idField);
11701170

1171+
$value = null;
11711172
if (isset($class->associationMappings[$idField])) {
11721173
// NOTE: Single Columns as associated identifiers only allowed - this constraint it is enforced.
1173-
$value = $this->getSingleIdentifierValue($value);
1174+
$value = $this->getSingleIdentifierValue($origValue);
11741175
}
11751176

1176-
$identifier[$idField] = $this->originalEntityData[$oid][$idField] = $value;
1177+
$identifier[$idField] = $value ?? $origValue;
1178+
$this->originalEntityData[$oid][$idField] = $origValue;
11771179
}
11781180

11791181
$this->entityStates[$oid] = self::STATE_MANAGED;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Tests\ORM\Functional\Ticket;
6+
7+
use Doctrine\Common\Collections\ArrayCollection;
8+
use Doctrine\Common\Collections\Collection;
9+
use Doctrine\ORM\Mapping\Column;
10+
use Doctrine\ORM\Mapping\Entity;
11+
use Doctrine\ORM\Mapping\GeneratedValue;
12+
use Doctrine\ORM\Mapping\Id;
13+
use Doctrine\ORM\Mapping\JoinColumn;
14+
use Doctrine\ORM\Mapping\ManyToOne;
15+
use Doctrine\ORM\Mapping\OneToMany;
16+
use Doctrine\Tests\OrmFunctionalTestCase;
17+
18+
use function count;
19+
20+
final class GH8217Test extends OrmFunctionalTestCase
21+
{
22+
protected function setUp(): void
23+
{
24+
parent::setUp();
25+
$this->setUpEntitySchema(
26+
[
27+
GH8217Collection::class,
28+
GH8217CollectionItem::class,
29+
]
30+
);
31+
}
32+
33+
/**
34+
* @group GH-8217
35+
*/
36+
public function testNoQueriesAfterSecondFlush(): void
37+
{
38+
$collection = new GH8217Collection();
39+
$collection->addItem(new GH8217CollectionItem($collection, 0));
40+
$collection->addItem(new GH8217CollectionItem($collection, 1));
41+
$this->_em->persist($collection);
42+
$this->_em->flush();
43+
44+
$logger = $this->_sqlLoggerStack;
45+
$queriesNumberBeforeSecondFlush = count($logger->queries);
46+
$this->_em->flush();
47+
$queriesNumberAfterSecondFlush = count($logger->queries);
48+
self::assertEquals($queriesNumberBeforeSecondFlush, $queriesNumberAfterSecondFlush);
49+
}
50+
}
51+
52+
/**
53+
* @Entity
54+
*/
55+
class GH8217Collection
56+
{
57+
/**
58+
* @var int
59+
* @Id
60+
* @Column(type="integer")
61+
* @GeneratedValue
62+
*/
63+
public $id;
64+
65+
/**
66+
* @psalm-var Collection<int, GH8217CollectionItem>
67+
* @OneToMany(targetEntity="GH8217CollectionItem", mappedBy="collection",
68+
* cascade={"persist", "remove"}, orphanRemoval=true)
69+
*/
70+
public $items;
71+
72+
public function __construct()
73+
{
74+
$this->items = new ArrayCollection();
75+
}
76+
77+
public function addItem(GH8217CollectionItem $item): void
78+
{
79+
$this->items->add($item);
80+
}
81+
}
82+
83+
/**
84+
* @Entity
85+
*/
86+
class GH8217CollectionItem
87+
{
88+
/**
89+
* @var GH8217Collection
90+
* @Id
91+
* @ManyToOne(targetEntity="GH8217Collection", inversedBy="items")
92+
* @JoinColumn(name="id", referencedColumnName="id")
93+
*/
94+
public $collection;
95+
96+
/**
97+
* @var int
98+
* @Id
99+
* @Column(type="integer", options={"unsigned": true})
100+
*/
101+
public $collectionIndex;
102+
103+
public function __construct(GH8217Collection $collection, int $collectionIndex)
104+
{
105+
$this->collection = $collection;
106+
$this->collectionIndex = $collectionIndex;
107+
}
108+
}

tests/Doctrine/Tests/ORM/Tools/Export/XmlClassMetadataExporterTest.php

+42
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,48 @@ public function testFieldOptionsExport(): void
9999
</field>
100100
</entity>
101101
</doctrine-mapping>
102+
XML;
103+
104+
self::assertXmlStringEqualsXmlString($expectedFileContent, $exporter->exportClassMetadata($metadata));
105+
}
106+
107+
public function testPolicyExport(): void
108+
{
109+
$exporter = new XmlExporter();
110+
$metadata = new ClassMetadata('entityTest');
111+
112+
// DEFERRED_IMPLICIT
113+
$metadata->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_IMPLICIT);
114+
115+
$expectedFileContent = <<<'XML'
116+
<?xml version="1.0" encoding="utf-8"?>
117+
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
118+
<entity name="entityTest"/>
119+
</doctrine-mapping>
120+
XML;
121+
122+
self::assertXmlStringEqualsXmlString($expectedFileContent, $exporter->exportClassMetadata($metadata));
123+
124+
// DEFERRED_EXPLICIT
125+
$metadata->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_DEFERRED_EXPLICIT);
126+
127+
$expectedFileContent = <<<'XML'
128+
<?xml version="1.0" encoding="utf-8"?>
129+
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
130+
<entity name="entityTest" change-tracking-policy="DEFERRED_EXPLICIT"/>
131+
</doctrine-mapping>
132+
XML;
133+
134+
self::assertXmlStringEqualsXmlString($expectedFileContent, $exporter->exportClassMetadata($metadata));
135+
136+
// NOTIFY
137+
$metadata->setChangeTrackingPolicy(ClassMetadata::CHANGETRACKING_NOTIFY);
138+
139+
$expectedFileContent = <<<'XML'
140+
<?xml version="1.0" encoding="utf-8"?>
141+
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
142+
<entity name="entityTest" change-tracking-policy="NOTIFY"/>
143+
</doctrine-mapping>
102144
XML;
103145

104146
self::assertXmlStringEqualsXmlString($expectedFileContent, $exporter->exportClassMetadata($metadata));

0 commit comments

Comments
 (0)