Compare commits

...

46 Commits

Author SHA1 Message Date
Grégoire Paris
c322c71cd4 Merge pull request #12120 from greg0ire/fix-broken-comments
Fix broken comments
2025-08-08 08:55:44 +02:00
Grégoire Paris
3c733a2fee Add missing phpdoc 2025-08-08 08:34:07 +02:00
Grégoire Paris
5984ad586a Fix broken phpdoc comments 2025-08-08 08:33:39 +02:00
Grégoire Paris
ee2c3a506b Merge pull request #12097 from mvorisek/add_fixed_id_insert_count_query_test
Add 2nd level cache test for insert without post-inserted ID
2025-08-08 08:26:23 +02:00
Grégoire Paris
04694a9f7b Merge pull request #11835 from gseidel/fix-pre-persist-call-persist
fix: calling scheduleForInsert twice
2025-08-07 06:25:35 +02:00
Michael Voříšek
5b2060e25f Add 2nd level cache test for insert without post-inserted ID
Inserts without post-inserted ID can be sent to DB grouped together
hence the extra test.
2025-08-06 08:22:48 +02:00
Grégoire Paris
39e35fc06c Merge pull request #12099 from alexislefebvre/2.20.x-update-supported-branches-on-README
doc: update supported branches on README (2.20.x)
2025-08-04 16:59:49 +02:00
Alexis Lefebvre
7f061c3870 doc: update supported branches on README 2025-08-04 16:38:55 +02:00
Grégoire Paris
74495711fb Merge pull request #11934 from mvorisek/fix_joined_subclass_persister_insert_of_multiple_entities
Fix JoinedSubclassPersister when multiple entities are inserted
2025-08-02 08:34:29 +02:00
Michael Voříšek
97a7cb8d2f Unify JoinedSubclassPersister dequeue
Fix JoinedSubclassPersister as BasicEntityPersister was already fixed in GH-10735.

The fix can be verified by modifying UnitOfWork to execute `BasicEntityPersister::executeInserts()` for multiple entities at once for the same entity class/persister instance - https://github.com/doctrine/orm/blob/2.20.3/src/UnitOfWork.php#L1186 - then reproducible on `Doctrine\Tests\ORM\Functional\Ticket\GH10531Test::testInserts` test.

As extending/modifying UnitOfWork in tests in not easily possible, I submit this fix for v2.x without a test.
2025-08-01 15:31:18 +02:00
Grégoire Paris
e0052390e1 Merge pull request #12087 from mvorisek/improve_basic_entity_persister
Improve BasicEntityPersister to be more flexible and cleaner
2025-07-30 09:44:24 +02:00
Michael Voříšek
8c6419e0e0 Prefer strict empty-array comparison over empty() call 2025-07-29 15:15:31 +02:00
Michael Voříšek
6f5ce1aca2 BasicEntityPersister: refactor $values variable into $placeholders
The new variable name is much more clearer.
2025-07-29 15:15:31 +02:00
Michael Voříšek
98e7a53b42 Remove BasicEntityPersister::$insertSql cache property
When the persister is extended to do a multi update, the caching is not
wanted. The impact is minimal as the CPU/time overhead per query is
much bigger and the prepared statement is not cached anyway.
2025-07-29 15:15:31 +02:00
Gerhard Seidel
3aaaf37dfb fix: PrePersistEventTest typos and unnecessary comments 2025-07-29 14:40:20 +02:00
Grégoire Paris
154a4652ee Merge pull request #12086 from mvorisek/add_cache_rw_strict_locking_test
Add functional strict-locking 2nd level cache test
2025-07-29 11:48:25 +02:00
Michael Voříšek
ae7489ff19 Add functional strict-locking 2nd level cache test 2025-07-28 12:14:50 +02:00
Grégoire Paris
1ee01f4473 Merge pull request #12078 from stlgaits/2.20.x
Fix embedded classes display in orm:mapping:command output
2025-07-22 08:48:52 +02:00
stlgaits
8a9ed138a8 Fix embedded classes display in orm:mapping:command output 2025-07-21 15:44:46 +02:00
Alexis Lefebvre
41ea59ac66 chore: use a shorter name for CI on GitHub Actions (#12055) 2025-07-04 00:19:01 +02:00
Alexis Lefebvre
e605e6d569 doc: add links to GitHub Actions on README (#12054) 2025-07-04 00:13:51 +02:00
Grégoire Paris
6307b4fa7d Merge pull request #8012 from sgehrig/bug/#8011-ordering-with-arithmetic-expression
Bug/#8011 ordering with arithmetic expression
2025-06-24 19:50:46 +02:00
Grégoire Paris
528b8837e1 Merge pull request #11929 from doctrine/2.20.x-merge-up-into-2.21.x_KkdqS0u7
Merge release 2.20.3 into 2.21.x
2025-05-02 21:57:23 +02:00
Stefan Gehrig
067ad51b3f fixes sqlite sql inconsistency 2025-03-17 08:48:30 +01:00
Stefan Gehrig
00c77213fb fixes codesniffer violation 2025-03-15 09:42:21 +01:00
Stefan Gehrig
c68b8f90b3 adds a test for postgres that uses a HIDDEN result variable for ordering based on arithmetic expression 2025-03-05 09:28:52 +01:00
Stefan Gehrig
aa4f9ce9e9 CS fix based on PHP_CodeSniffer report 2025-03-05 09:22:57 +01:00
Stefan Gehrig
d96fc23327 skips tests when running on postgres 2025-02-27 10:30:21 +01:00
Gerhard Seidel
4fb044d5f6 fix: cs 2025-02-20 10:01:35 +08:00
Gerhard Seidel
2a953c5e2b fix: PrePersistEventTest and cs 2025-02-17 14:01:08 +08:00
Gerhard Seidel
abc6a40ccb fix: calling scheduleForInsert twice
If scheduleForInsert was called in prePersist hook already, then persistNew need to check this case first, otherwise a ORMInvalidArgumentException will be thrown
2025-02-14 12:45:13 +08:00
Grégoire Paris
73e68f3c7d Merge pull request #11821 from doctrine/2.20.x-merge-up-into-2.21.x_8O8nHxqC
Merge release 2.20.2 into 2.21.x
2025-02-04 20:24:01 +01:00
Alexander M. Turek
73777d0bd4 Merge branch '2.20.x' into 2.21.x
* 2.20.x:
  Introduce testNotListedValueInEnumArray
  Fix documentation for JoinColumn nullable (#11798)
  Ignore deprecations from doctrine/common
  Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared`
2025-01-26 19:56:20 +01:00
Stefan Gehrig
ec6d1b9f72 fixes whitespace
Signed-off-by: Stefan Gehrig <stefan.gehrig.hn@googlemail.com>
2025-01-07 08:51:19 +01:00
Stefan Gehrig
d809fed52a fixes code sniffer complaints
Signed-off-by: Stefan Gehrig <stefan.gehrig.hn@googlemail.com>
2025-01-07 08:48:42 +01:00
Stefan Gehrig
0e4786dfa8 adds testcases for order by items enclosed in ((...)) (double brackets - just one bracket does not work)
just one bracket (...) gives

Exception : [Doctrine\ORM\Query\QueryException] [Syntax Error] line 0, col xx: Error: Expected Doctrine\ORM\Query\Lexer::T_IDENTIFIER, got '('
2025-01-03 10:45:08 +01:00
Stefan Gehrig
c429262f02 adds detection of literals/result variables at the beginning of an order by item with arithmetic expression
Not sure whether this covers the whole problem regarding complex expressions in order by items but it fixes the provided test cases
2025-01-03 10:45:07 +01:00
Stefan Gehrig
f4fdcbcdcb adds more test cases 2025-01-03 10:44:17 +01:00
Stefan Gehrig
b0806469d5 adds test case for GH issue #8011 2025-01-03 10:44:17 +01:00
Grégoire Paris
e89b58a13f Merge pull request #11771 from doctrine/2.20.x-merge-up-into-2.21.x_3Yg2ZYgM
Merge release 2.20.1 into 2.21.x
2024-12-19 08:16:04 +01:00
Grégoire Paris
2b94ec18b9 Merge pull request #11759 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-12-08 14:33:31 +01:00
Grégoire Paris
2a662149f4 Merge pull request #11754 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-12-07 15:39:29 +01:00
Grégoire Paris
37051d57ce Merge pull request #11739 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-11-28 08:23:12 +01:00
Grégoire Paris
4563f2f9a7 Merge pull request #11737 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-11-27 22:10:21 +01:00
Grégoire Paris
91201c094a Merge pull request #11722 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-11-23 19:35:45 +01:00
Grégoire Paris
a4a15ad243 Merge pull request #11687 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2024-10-16 23:37:08 +02:00
10 changed files with 573 additions and 44 deletions

View File

@@ -1,4 +1,4 @@
name: "Continuous Integration"
name: "CI"
on:
pull_request:

View File

@@ -1,7 +1,7 @@
| [4.0.x][4.0] | [3.4.x][3.4] | [3.3.x][3.3] | [2.21.x][2.21] | [2.20.x][2.20] |
| [4.0.x][4.0] | [3.6.x][3.6] | [3.5.x][3.5] | [2.21.x][2.21] | [2.20.x][2.20] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![Build status][4.0 image]][4.0] | [![Build status][3.4 image]][3.4] | [![Build status][3.3 image]][3.3] | [![Build status][2.21 image]][2.21] | [![Build status][2.20 image]][2.20] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.4 coverage image]][3.4 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.6 image]][3.6 workflow] | [![Build status][3.5 image]][3.5 workflow] | [![Build status][2.21 image]][2.21 workflow] | [![Build status][2.20 image]][2.20 workflow] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][3.5 coverage image]][3.5 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
@@ -20,21 +20,26 @@ without requiring unnecessary code duplication.
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
[4.0 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A4.0.x
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
[3.4 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.4.x
[3.4]: https://github.com/doctrine/orm/tree/3.4.x
[3.4 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.4.x/graph/badge.svg
[3.4 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.4.x
[3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x
[3.3]: https://github.com/doctrine/orm/tree/3.3.x
[3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg
[3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x
[3.6 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.6.x
[3.6]: https://github.com/doctrine/orm/tree/3.6.x
[3.6 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.6.x
[3.6 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.6.x/graph/badge.svg
[3.6 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.6.x
[3.5 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.5.x
[3.5]: https://github.com/doctrine/orm/tree/3.5.x
[3.5 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.5.x
[3.5 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.5.x/graph/badge.svg
[3.5 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.5.x
[2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x
[2.21]: https://github.com/doctrine/orm/tree/2.21.x
[2.21 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.21.x
[2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
[2.21 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.21.x
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
[2.20 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.20.x
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x

View File

@@ -168,14 +168,6 @@ class BasicEntityPersister implements EntityPersister
*/
protected $quotedColumns = [];
/**
* The INSERT SQL statement used for entities handled by this persister.
* This SQL is only generated once per request, if at all.
*
* @var string|null
*/
private $insertSql;
/**
* The quote strategy.
*
@@ -310,8 +302,8 @@ class BasicEntityPersister implements EntityPersister
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
}
// 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
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() 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
@@ -1473,22 +1465,17 @@ class BasicEntityPersister implements EntityPersister
*/
public function getInsertSQL()
{
if ($this->insertSql !== null) {
return $this->insertSql;
}
$columns = $this->getInsertColumnList();
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
if (empty($columns)) {
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
if ($columns === []) {
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
return $this->insertSql;
return $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
}
$values = [];
$columns = array_unique($columns);
$placeholders = [];
$columns = array_unique($columns);
foreach ($columns as $column) {
$placeholder = '?';
@@ -1502,15 +1489,13 @@ class BasicEntityPersister implements EntityPersister
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
}
$values[] = $placeholder;
$placeholders[] = $placeholder;
}
$columns = implode(', ', $columns);
$values = implode(', ', $values);
$columns = implode(', ', $columns);
$placeholders = implode(', ', $placeholders);
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
return $this->insertSql;
return sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $placeholders);
}
/**

View File

@@ -149,7 +149,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Execute all inserts. For each entity:
// 1) Insert on root table
// 2) Insert on sub tables
foreach ($this->queuedInserts as $entity) {
foreach ($this->queuedInserts as $key => $entity) {
$insertData = $this->prepareInsertData($entity);
// Execute insert on root table
@@ -194,9 +194,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
if ($this->class->requiresFetchAfterChange) {
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
}
}
$this->queuedInserts = [];
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() 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]);
}
}
/**

View File

@@ -1554,7 +1554,7 @@ class Parser
assert($this->lexer->lookahead !== null);
switch (true) {
case $this->isMathOperator($peek):
case $this->isMathOperator($peek) || $this->isMathOperator($glimpse):
$expr = $this->SimpleArithmeticExpression();
break;

View File

@@ -98,7 +98,7 @@ EOT
$this->formatField('Embedded class?', $metadata->isEmbeddedClass),
$this->formatField('Parent classes', $metadata->parentClasses),
$this->formatField('Sub classes', $metadata->subClasses),
$this->formatField('Embedded classes', $metadata->subClasses),
$this->formatField('Embedded classes', $metadata->embeddedClasses),
$this->formatField('Named queries', $metadata->namedQueries),
$this->formatField('Named native queries', $metadata->namedNativeQueries),
$this->formatField('SQL result set mappings', $metadata->sqlResultSetMappings),

View File

@@ -1060,7 +1060,9 @@ class UnitOfWork implements PropertyChangedListener
$this->entityStates[$oid] = self::STATE_MANAGED;
$this->scheduleForInsert($entity);
if (! isset($this->entityInsertions[$oid])) {
$this->scheduleForInsert($entity);
}
}
/** @param mixed[] $idValue */

View File

@@ -0,0 +1,238 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\Tests\Models\Company\CompanyEmployee;
use Doctrine\Tests\OrmFunctionalTestCase;
use function count;
/**
* Functional tests for ordering with arithmetic expression.
*
* @group GH8011
*/
class GH8011Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
$this->useModelSet('company');
parent::setUp();
$this->generateFixture();
}
private function skipIfPostgres(string $test): void
{
$platform = $this->_em->getConnection()->getDatabasePlatform();
if ($platform instanceof PostgreSQLPlatform) {
self::markTestSkipped(
'The ' . $test . ' test does not work on postgresql (see https://github.com/doctrine/orm/pull/8012).'
);
}
}
public function testOrderWithArithmeticExpressionWithSingleValuedPathExpression(): void
{
$dql = 'SELECT p ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY p.id + p.id ASC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Benjamin E.', $result[0]->getName());
$this->assertEquals('Guilherme B.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithLiteralAndSingleValuedPathExpression(): void
{
$dql = 'SELECT p ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY 1 + p.id ASC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Benjamin E.', $result[0]->getName());
$this->assertEquals('Guilherme B.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithLiteralAndSingleValuedPathExpression2(): void
{
$dql = 'SELECT p ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY ((1 + p.id)) ASC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Benjamin E.', $result[0]->getName());
$this->assertEquals('Guilherme B.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithSingleValuedPathExpressionAndLiteral(): void
{
$dql = 'SELECT p ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY p.id + 1 ASC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Benjamin E.', $result[0]->getName());
$this->assertEquals('Guilherme B.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithResultVariableAndLiteral(): void
{
$this->skipIfPostgres(__FUNCTION__);
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY s + 1 DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithResultVariableAndLiteral2(): void
{
$this->skipIfPostgres(__FUNCTION__);
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY ((s + 1)) DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithLiteralAndResultVariable(): void
{
$this->skipIfPostgres(__FUNCTION__);
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY 1 + s DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithLiteralAndResultVariable2(): void
{
$this->skipIfPostgres(__FUNCTION__);
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY ((1 + s)) DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithResultVariableAndSingleValuedPathExpression(): void
{
$this->skipIfPostgres(__FUNCTION__);
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY s + p.id DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithResultVariableAndSingleValuedPathExpression2(): void
{
$this->skipIfPostgres(__FUNCTION__);
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY ((s + p.id)) DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithSingleValuedPathExpressionAndResultVariable(): void
{
$this->skipIfPostgres(__FUNCTION__);
$dql = 'SELECT p, p.salary AS HIDDEN s ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY p.id + s DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function testOrderWithArithmeticExpressionWithLiteralAndResultVariableUsingHiddenResultVariable(): void
{
$dql = 'SELECT p, 1 + p.salary AS HIDDEN _order ' .
'FROM Doctrine\Tests\Models\Company\CompanyEmployee p ' .
'ORDER BY _order DESC';
/** @var CompanyEmployee[] $result */
$result = $this->_em->createQuery($dql)->getResult();
$this->assertEquals(2, count($result));
$this->assertEquals('Guilherme B.', $result[0]->getName());
$this->assertEquals('Benjamin E.', $result[1]->getName());
}
public function generateFixture(): void
{
$person1 = new CompanyEmployee();
$person1->setName('Benjamin E.');
$person1->setDepartment('IT');
$person1->setSalary(200000);
$person2 = new CompanyEmployee();
$person2->setName('Guilherme B.');
$person2->setDepartment('IT2');
$person2->setSalary(400000);
$this->_em->persist($person1);
$this->_em->persist($person2);
$this->_em->flush();
$this->_em->clear();
}
}

View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\Tests\OrmFunctionalTestCase;
use function uniqid;
class PrePersistEventTest extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
EntityWithUnmappedEntity::class,
EntityWithCascadeAssociation::class
);
}
public function testCallingPersistInPrePersistHook(): void
{
$entityWithUnmapped = new EntityWithUnmappedEntity();
$entityWithCascade = new EntityWithCascadeAssociation();
$entityWithUnmapped->unmapped = $entityWithCascade;
$entityWithCascade->cascaded = $entityWithUnmapped;
$this->_em->getEventManager()->addEventListener(Events::prePersist, new PrePersistUnmappedPersistListener());
$this->_em->persist($entityWithUnmapped);
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithCascade));
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithUnmapped));
}
}
class PrePersistUnmappedPersistListener
{
public function prePersist(PrePersistEventArgs $args): void
{
$object = $args->getObject();
if ($object instanceof EntityWithUnmappedEntity) {
$uow = $args->getObjectManager()->getUnitOfWork();
if ($object->unmapped && ! $uow->isInIdentityMap($object->unmapped) && ! $uow->isScheduledForInsert($object->unmapped)) {
$args->getObjectManager()->persist($object->unmapped);
}
}
}
}
/** @Entity */
#[Entity]
class EntityWithUnmappedEntity
{
/**
* @var string
* @Id
* @Column(type="string", length=255)
* @GeneratedValue(strategy="NONE")
*/
#[Id]
#[Column(type: 'string', length: 255)]
#[GeneratedValue(strategy: 'NONE')]
public $id;
/** @var ?EntityWithCascadeAssociation */
public $unmapped = null;
public function __construct()
{
$this->id = uniqid(self::class, true);
}
}
/** @Entity */
#[Entity]
class EntityWithCascadeAssociation
{
/**
* @var string
* @Id
* @Column(type="string", length=255)
* @GeneratedValue(strategy="NONE")
*/
#[Id]
#[Column(type: 'string', length: 255)]
#[GeneratedValue(strategy: 'NONE')]
public $id;
/**
* @var ?EntityWithUnmappedEntity
* @ManyToOne(targetEntity=EntityWithUnmappedEntity::class, cascade={"persist"})
*/
#[ManyToOne(targetEntity: EntityWithUnmappedEntity::class, cascade: ['persist'])]
public $cascaded = null;
public function __construct()
{
$this->id = uniqid(self::class, true);
}
}

View File

@@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Id\AssignedGenerator;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\Tests\Models\Cache\Country;
use ReflectionProperty;
use function array_diff;
use function array_filter;
use function file_exists;
use function rmdir;
use function scandir;
use function strpos;
use function sys_get_temp_dir;
use const DIRECTORY_SEPARATOR;
/**
* @group DDC-2183
* @phpstan-type SupportedCacheUsage 0|ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE|ClassMetadata::CACHE_USAGE_READ_WRITE
*/
class SecondLevelCacheCountQueriesTest extends SecondLevelCacheFunctionalTestCase
{
/** @var string */
private $tmpDir;
protected function tearDown(): void
{
if ($this->tmpDir !== null && file_exists($this->tmpDir)) {
foreach (array_diff(scandir($this->tmpDir), ['.', '..']) as $f) {
rmdir($this->tmpDir . DIRECTORY_SEPARATOR . $f);
}
rmdir($this->tmpDir);
}
parent::tearDown();
}
/** @param SupportedCacheUsage $cacheUsage */
private function setupCountryModel(int $cacheUsage): void
{
$metadata = $this->_em->getClassMetaData(Country::class);
if ($cacheUsage === 0) {
$metadataCacheReflection = new ReflectionProperty(ClassMetadataInfo::class, 'cache');
$metadataCacheReflection->setAccessible(true);
$metadataCacheReflection->setValue($metadata, null);
return;
}
if ($cacheUsage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
$this->tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::class;
$this->secondLevelCacheFactory->setFileLockRegionDirectory($this->tmpDir);
}
$metadata->enableCache(['usage' => $cacheUsage]);
}
private function loadFixturesCountriesWithoutPostInsertIdentifier(): void
{
$metadata = $this->_em->getClassMetaData(Country::class);
$metadata->setIdGenerator(new AssignedGenerator());
$c1 = new Country('Brazil');
$c1->setId(10);
$c2 = new Country('Germany');
$c2->setId(20);
$this->countries[] = $c1;
$this->countries[] = $c2;
$this->_em->persist($c1);
$this->_em->persist($c2);
$this->_em->flush();
}
/** @param 'INSERT'|'UPDATE'|'DELETE' $type */
private function assertQueryCountByType(string $type, int $expectedCount): void
{
$queries = array_filter($this->getQueryLog()->queries, static function (array $entry) use ($type): bool {
return strpos($entry['sql'], $type) === 0;
});
self::assertCount($expectedCount, $queries);
}
/**
* @param SupportedCacheUsage $cacheUsage
*
* @dataProvider cacheUsageProvider
*/
public function testInsertWithPostInsertIdentifier(int $cacheUsage): void
{
$this->setupCountryModel($cacheUsage);
self::assertQueryCountByType('INSERT', 0);
$this->loadFixturesCountries();
self::assertCount(2, $this->countries);
self::assertQueryCountByType('INSERT', 2);
}
/**
* @param SupportedCacheUsage $cacheUsage
*
* @dataProvider cacheUsageProvider
*/
public function testInsertWithoutPostInsertIdentifier(int $cacheUsage): void
{
$this->setupCountryModel($cacheUsage);
self::assertQueryCountByType('INSERT', 0);
$this->loadFixturesCountriesWithoutPostInsertIdentifier();
self::assertCount(2, $this->countries);
self::assertQueryCountByType('INSERT', 2);
}
/**
/* @param SupportedCacheUsage $cacheUsage
*
* @dataProvider cacheUsageProvider
*/
public function testDelete(int $cacheUsage): void
{
$this->setupCountryModel($cacheUsage);
$this->loadFixturesCountries();
$c1 = $this->_em->find(Country::class, $this->countries[0]->getId());
$c2 = $this->_em->find(Country::class, $this->countries[1]->getId());
$this->_em->remove($c1);
$this->_em->remove($c2);
$this->_em->flush();
self::assertQueryCountByType('DELETE', 2);
}
/**
* @param SupportedCacheUsage $cacheUsage
*
* @dataProvider cacheUsageProvider
*/
public function testUpdate(int $cacheUsage): void
{
$this->setupCountryModel($cacheUsage);
$this->loadFixturesCountries();
$c1 = $this->_em->find(Country::class, $this->countries[0]->getId());
$c2 = $this->_em->find(Country::class, $this->countries[1]->getId());
$c1->setName('Czech Republic');
$c2->setName('Hungary');
$this->_em->persist($c1);
$this->_em->persist($c2);
$this->_em->flush();
self::assertQueryCountByType('UPDATE', 2);
}
/** @return list<array{SupportedCacheUsage}> */
public static function cacheUsageProvider(): array
{
return [
[0],
[ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE],
[ClassMetadata::CACHE_USAGE_READ_WRITE],
];
}
}