Skip to content

Commit f08159e

Browse files
authored
Merge pull request #11027 from greg0ire/fw-compat-serialization
Make serialized SQL executors forward compatible
2 parents 16028e4 + 2a9390d commit f08159e

File tree

9 files changed

+107
-29
lines changed

9 files changed

+107
-29
lines changed

UPGRADE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Upgrade to 2.17
22

3+
## Deprecate `Doctrine\ORM\Query\Exec\AbstractSqlExecutor::_sqlStatements`
4+
5+
Use `Doctrine\ORM\Query\Exec\AbstractSqlExecutor::sqlStatements` instead.
6+
37
## Undeprecate `Doctrine\ORM\Proxy\Autoloader`
48

59
It will be a full-fledged class, no longer extending

lib/Doctrine/ORM/Query/Exec/AbstractSqlExecutor.php

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
use Doctrine\DBAL\Result;
1010
use Doctrine\DBAL\Types\Type;
1111

12+
use function array_diff;
13+
use function array_keys;
14+
use function array_values;
15+
1216
/**
1317
* Base class for SQL statement executors.
1418
*
@@ -18,34 +22,43 @@
1822
*/
1923
abstract class AbstractSqlExecutor
2024
{
21-
/** @var list<string>|string */
25+
/**
26+
* @deprecated use $sqlStatements instead
27+
*
28+
* @var list<string>|string
29+
*/
2230
protected $_sqlStatements;
2331

32+
/** @var list<string>|string */
33+
protected $sqlStatements;
34+
2435
/** @var QueryCacheProfile */
2536
protected $queryCacheProfile;
2637

38+
public function __construct()
39+
{
40+
$this->_sqlStatements = &$this->sqlStatements;
41+
}
42+
2743
/**
2844
* Gets the SQL statements that are executed by the executor.
2945
*
3046
* @return mixed[]|string All the SQL update statements.
3147
*/
3248
public function getSqlStatements()
3349
{
34-
return $this->_sqlStatements;
50+
return $this->sqlStatements;
3551
}
3652

37-
/** @return void */
38-
public function setQueryCacheProfile(QueryCacheProfile $qcp)
53+
public function setQueryCacheProfile(QueryCacheProfile $qcp): void
3954
{
4055
$this->queryCacheProfile = $qcp;
4156
}
4257

4358
/**
4459
* Do not use query cache
45-
*
46-
* @return void
4760
*/
48-
public function removeQueryCacheProfile()
61+
public function removeQueryCacheProfile(): void
4962
{
5063
$this->queryCacheProfile = null;
5164
}
@@ -60,4 +73,22 @@ public function removeQueryCacheProfile()
6073
* @return Result|int
6174
*/
6275
abstract public function execute(Connection $conn, array $params, array $types);
76+
77+
/** @return list<string> */
78+
public function __sleep(): array
79+
{
80+
/* Two reasons for this:
81+
- we do not need to serialize the deprecated property, we can
82+
rebuild the reference to the new property in __wakeup()
83+
- not having the legacy property in the serialized data means the
84+
serialized representation becomes compatible with 3.0.x, meaning
85+
there will not be a deprecation warning about a missing property
86+
when unserializing data */
87+
return array_values(array_diff(array_keys((array) $this), ["\0*\0_sqlStatements"]));
88+
}
89+
90+
public function __wakeup(): void
91+
{
92+
$this->_sqlStatements = &$this->sqlStatements;
93+
}
6394
}

lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class MultiTableDeleteExecutor extends AbstractSqlExecutor
4545
*/
4646
public function __construct(AST\Node $AST, $sqlWalker)
4747
{
48+
parent::__construct();
49+
4850
$em = $sqlWalker->getEntityManager();
4951
$conn = $em->getConnection();
5052
$platform = $conn->getDatabasePlatform();
@@ -83,8 +85,8 @@ public function __construct(AST\Node $AST, $sqlWalker)
8385
// 3. Create and store DELETE statements
8486
$classNames = array_merge($primaryClass->parentClasses, [$primaryClass->name], $primaryClass->subClasses);
8587
foreach (array_reverse($classNames) as $className) {
86-
$tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform);
87-
$this->_sqlStatements[] = 'DELETE FROM ' . $tableName
88+
$tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform);
89+
$this->sqlStatements[] = 'DELETE FROM ' . $tableName
8890
. ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
8991
}
9092

@@ -117,7 +119,7 @@ public function execute(Connection $conn, array $params, array $types)
117119
$numDeleted = $conn->executeStatement($this->insertSql, $params, $types);
118120

119121
// Execute DELETE statements
120-
foreach ($this->_sqlStatements as $sql) {
122+
foreach ($this->sqlStatements as $sql) {
121123
$conn->executeStatement($sql);
122124
}
123125
} catch (Throwable $exception) {

lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class MultiTableUpdateExecutor extends AbstractSqlExecutor
5050
*/
5151
public function __construct(AST\Node $AST, $sqlWalker)
5252
{
53+
parent::__construct();
54+
5355
$em = $sqlWalker->getEntityManager();
5456
$conn = $em->getConnection();
5557
$platform = $conn->getDatabasePlatform();
@@ -119,7 +121,7 @@ public function __construct(AST\Node $AST, $sqlWalker)
119121
}
120122

121123
if ($affected) {
122-
$this->_sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
124+
$this->sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
123125
}
124126
}
125127

@@ -163,7 +165,7 @@ public function execute(Connection $conn, array $params, array $types)
163165
);
164166

165167
// Execute UPDATE statements
166-
foreach ($this->_sqlStatements as $key => $statement) {
168+
foreach ($this->sqlStatements as $key => $statement) {
167169
$paramValues = [];
168170
$paramTypes = [];
169171

lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class SingleSelectExecutor extends AbstractSqlExecutor
1818
{
1919
public function __construct(SelectStatement $AST, SqlWalker $sqlWalker)
2020
{
21-
$this->_sqlStatements = $sqlWalker->walkSelectStatement($AST);
21+
parent::__construct();
22+
23+
$this->sqlStatements = $sqlWalker->walkSelectStatement($AST);
2224
}
2325

2426
/**
@@ -28,6 +30,6 @@ public function __construct(SelectStatement $AST, SqlWalker $sqlWalker)
2830
*/
2931
public function execute(Connection $conn, array $params, array $types)
3032
{
31-
return $conn->executeQuery($this->_sqlStatements, $params, $types, $this->queryCacheProfile);
33+
return $conn->executeQuery($this->sqlStatements, $params, $types, $this->queryCacheProfile);
3234
}
3335
}

lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ class SingleTableDeleteUpdateExecutor extends AbstractSqlExecutor
2222
/** @param SqlWalker $sqlWalker */
2323
public function __construct(AST\Node $AST, $sqlWalker)
2424
{
25+
parent::__construct();
26+
2527
if ($AST instanceof AST\UpdateStatement) {
26-
$this->_sqlStatements = $sqlWalker->walkUpdateStatement($AST);
28+
$this->sqlStatements = $sqlWalker->walkUpdateStatement($AST);
2729
} elseif ($AST instanceof AST\DeleteStatement) {
28-
$this->_sqlStatements = $sqlWalker->walkDeleteStatement($AST);
30+
$this->sqlStatements = $sqlWalker->walkDeleteStatement($AST);
2931
}
3032
}
3133

@@ -40,6 +42,6 @@ public function execute(Connection $conn, array $params, array $types)
4042
$conn->ensureConnectedToPrimary();
4143
}
4244

43-
return $conn->executeStatement($this->_sqlStatements, $params, $types);
45+
return $conn->executeStatement($this->sqlStatements, $params, $types);
4446
}
4547
}

psalm-baseline.xml

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,18 @@
18471847
<PossiblyNullPropertyAssignmentValue>
18481848
<code>null</code>
18491849
</PossiblyNullPropertyAssignmentValue>
1850+
<PropertyNotSetInConstructor>
1851+
<code>$_sqlStatements</code>
1852+
<code>$queryCacheProfile</code>
1853+
<code>$sqlStatements</code>
1854+
</PropertyNotSetInConstructor>
1855+
<UninitializedProperty>
1856+
<code><![CDATA[$this->sqlStatements]]></code>
1857+
</UninitializedProperty>
1858+
<UnsupportedPropertyReferenceUsage>
1859+
<code><![CDATA[$this->_sqlStatements = &$this->sqlStatements]]></code>
1860+
<code><![CDATA[$this->_sqlStatements = &$this->sqlStatements]]></code>
1861+
</UnsupportedPropertyReferenceUsage>
18501862
</file>
18511863
<file src="lib/Doctrine/ORM/Query/Exec/MultiTableDeleteExecutor.php">
18521864
<InvalidReturnStatement>
@@ -1856,15 +1868,12 @@
18561868
<code>int</code>
18571869
</InvalidReturnType>
18581870
<PossiblyInvalidIterator>
1859-
<code><![CDATA[$this->_sqlStatements]]></code>
1871+
<code><![CDATA[$this->sqlStatements]]></code>
18601872
</PossiblyInvalidIterator>
18611873
<PropertyNotSetInConstructor>
18621874
<code>MultiTableDeleteExecutor</code>
18631875
<code>MultiTableDeleteExecutor</code>
18641876
</PropertyNotSetInConstructor>
1865-
<UninitializedProperty>
1866-
<code><![CDATA[$this->_sqlStatements]]></code>
1867-
</UninitializedProperty>
18681877
</file>
18691878
<file src="lib/Doctrine/ORM/Query/Exec/MultiTableUpdateExecutor.php">
18701879
<InvalidReturnStatement>
@@ -1874,40 +1883,40 @@
18741883
<code>int</code>
18751884
</InvalidReturnType>
18761885
<PossiblyInvalidIterator>
1877-
<code><![CDATA[$this->_sqlStatements]]></code>
1886+
<code><![CDATA[$this->sqlStatements]]></code>
18781887
</PossiblyInvalidIterator>
18791888
<PropertyNotSetInConstructor>
18801889
<code>MultiTableUpdateExecutor</code>
18811890
<code>MultiTableUpdateExecutor</code>
1891+
<code>MultiTableUpdateExecutor</code>
18821892
</PropertyNotSetInConstructor>
18831893
<PropertyTypeCoercion>
1884-
<code><![CDATA[$this->_sqlStatements]]></code>
1894+
<code><![CDATA[$this->sqlStatements]]></code>
18851895
</PropertyTypeCoercion>
1886-
<UninitializedProperty>
1887-
<code><![CDATA[$this->_sqlStatements]]></code>
1888-
</UninitializedProperty>
18891896
</file>
18901897
<file src="lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php">
18911898
<PossiblyInvalidArgument>
1892-
<code><![CDATA[$this->_sqlStatements]]></code>
1899+
<code><![CDATA[$this->sqlStatements]]></code>
18931900
</PossiblyInvalidArgument>
18941901
<PropertyNotSetInConstructor>
18951902
<code>SingleSelectExecutor</code>
1903+
<code>SingleSelectExecutor</code>
18961904
</PropertyNotSetInConstructor>
18971905
</file>
18981906
<file src="lib/Doctrine/ORM/Query/Exec/SingleTableDeleteUpdateExecutor.php">
18991907
<InvalidReturnStatement>
1900-
<code><![CDATA[$conn->executeStatement($this->_sqlStatements, $params, $types)]]></code>
1908+
<code><![CDATA[$conn->executeStatement($this->sqlStatements, $params, $types)]]></code>
19011909
</InvalidReturnStatement>
19021910
<InvalidReturnType>
19031911
<code>int</code>
19041912
</InvalidReturnType>
19051913
<PossiblyInvalidArgument>
1906-
<code><![CDATA[$this->_sqlStatements]]></code>
1914+
<code><![CDATA[$this->sqlStatements]]></code>
19071915
</PossiblyInvalidArgument>
19081916
<PropertyNotSetInConstructor>
19091917
<code>SingleTableDeleteUpdateExecutor</code>
19101918
<code>SingleTableDeleteUpdateExecutor</code>
1919+
<code>SingleTableDeleteUpdateExecutor</code>
19111920
</PropertyNotSetInConstructor>
19121921
</file>
19131922
<file src="lib/Doctrine/ORM/Query/Expr.php">

tests/Doctrine/Tests/ORM/Functional/ParserResultSerializationTest.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Doctrine\Tests\OrmFunctionalTestCase;
1212
use Generator;
1313
use ReflectionMethod;
14+
use ReflectionProperty;
1415

1516
use function file_get_contents;
1617
use function rtrim;
@@ -41,6 +42,30 @@ public function testSerializeParserResult(): void
4142
$this->assertInstanceOf(SingleSelectExecutor::class, $unserialized->getSqlExecutor());
4243
}
4344

45+
public function testItSerializesParserResultWithAForwardCompatibleFormat(): void
46+
{
47+
$query = $this->_em
48+
->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyEmployee u WHERE u.name = :name');
49+
50+
$parserResult = self::parseQuery($query);
51+
$serialized = serialize($parserResult);
52+
$this->assertStringNotContainsString(
53+
'_sqlStatements',
54+
$serialized,
55+
'ParserResult should not contain any reference to _sqlStatements, which is a legacy property.'
56+
);
57+
$unserialized = unserialize($serialized);
58+
59+
$r = new ReflectionProperty($unserialized->getSqlExecutor(), '_sqlStatements');
60+
$r->setAccessible(true);
61+
62+
$this->assertSame(
63+
$r->getValue($unserialized->getSqlExecutor()),
64+
$unserialized->getSqlExecutor()->getSqlStatements(),
65+
'The legacy property should be populated with the same value as the new one.'
66+
);
67+
}
68+
4469
/**
4570
* @dataProvider provideSerializedSingleSelectResults
4671
*/
@@ -59,6 +84,7 @@ public static function provideSerializedSingleSelectResults(): Generator
5984
{
6085
yield '2.14.3' => [rtrim(file_get_contents(__DIR__ . '/ParserResults/single_select_2_14_3.txt'), "\n")];
6186
yield '2.15.0' => [rtrim(file_get_contents(__DIR__ . '/ParserResults/single_select_2_15_0.txt'), "\n")];
87+
yield '2.17.0' => [rtrim(file_get_contents(__DIR__ . '/ParserResults/single_select_2_17_0.txt'), "\n")];
6288
}
6389

6490
private static function parseQuery(Query $query): ParserResult
Binary file not shown.

0 commit comments

Comments
 (0)