mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e67fa5388b | ||
|
|
dddcc507ef | ||
|
|
b41d9da88d | ||
|
|
c04bfb78b7 | ||
|
|
ee919d6231 | ||
|
|
04c390693a | ||
|
|
8d9e2e7d4e | ||
|
|
ef607f26c2 | ||
|
|
ed543a205c | ||
|
|
de1c28bb16 | ||
|
|
b4ca0cd5fb | ||
|
|
a49c1beb93 | ||
|
|
6307b4fa7d | ||
|
|
92e2f6db83 | ||
|
|
aa624f64c1 | ||
|
|
e1675eb371 | ||
|
|
cc2b6385a1 | ||
|
|
a64bed9bbb | ||
|
|
3272e1c0af | ||
|
|
69da22d517 | ||
|
|
06109f360f | ||
|
|
06a9ef1127 | ||
|
|
5d21bb158b | ||
|
|
c74df3fab3 | ||
|
|
f2c902ee03 | ||
|
|
4e5e3c5e50 | ||
|
|
da697f218f | ||
|
|
4f47a80deb | ||
|
|
ab89517093 | ||
|
|
48a51d8470 | ||
|
|
ab11244f08 | ||
|
|
a1c2be140d | ||
|
|
067ad51b3f | ||
|
|
00c77213fb | ||
|
|
c68b8f90b3 | ||
|
|
aa4f9ce9e9 | ||
|
|
d96fc23327 | ||
|
|
ec6d1b9f72 | ||
|
|
d809fed52a | ||
|
|
0e4786dfa8 | ||
|
|
c429262f02 | ||
|
|
f4fdcbcdcb | ||
|
|
b0806469d5 |
@@ -11,17 +11,23 @@
|
||||
"slug": "latest",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.5",
|
||||
"branchName": "3.5.x",
|
||||
"slug": "3.5",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.4",
|
||||
"branchName": "3.4.x",
|
||||
"slug": "3.4",
|
||||
"upcoming": true
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "3.3",
|
||||
"branchName": "3.3.x",
|
||||
"slug": "3.3",
|
||||
"current": true
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.2",
|
||||
|
||||
16
.github/workflows/continuous-integration.yml
vendored
16
.github/workflows/continuous-integration.yml
vendored
@@ -43,27 +43,27 @@ jobs:
|
||||
- "pdo_sqlite"
|
||||
deps:
|
||||
- "highest"
|
||||
lazy_proxy:
|
||||
native_lazy:
|
||||
- "0"
|
||||
include:
|
||||
- php-version: "8.2"
|
||||
dbal-version: "4@dev"
|
||||
extension: "pdo_sqlite"
|
||||
lazy_proxy: "0"
|
||||
native_lazy: "0"
|
||||
- php-version: "8.2"
|
||||
dbal-version: "4@dev"
|
||||
extension: "sqlite3"
|
||||
lazy_proxy: "0"
|
||||
native_lazy: "0"
|
||||
- php-version: "8.1"
|
||||
dbal-version: "default"
|
||||
deps: "lowest"
|
||||
extension: "pdo_sqlite"
|
||||
lazy_proxy: "0"
|
||||
native_lazy: "0"
|
||||
- php-version: "8.4"
|
||||
dbal-version: "default"
|
||||
deps: "highest"
|
||||
extension: "pdo_sqlite"
|
||||
lazy_proxy: "1"
|
||||
native_lazy: "1"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
@@ -93,18 +93,18 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
ENABLE_LAZY_PROXY: ${{ matrix.lazy_proxy }}
|
||||
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
ENABLE_LAZY_PROXY: ${{ matrix.lazy_proxy }}
|
||||
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.lazy_proxy }}-coverage"
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.native_lazy }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -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.5.x][3.5] | [3.4.x][3.4] | [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] | [![Build status][3.5 image]][3.5] | [![Build status][3.4 image]][3.4] | [![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.5 coverage image]][3.5 coverage] | [![Coverage Status][3.4 coverage image]][3.4 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
|
||||
|
||||
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
@@ -20,14 +20,14 @@ without requiring unnecessary code duplication.
|
||||
[4.0]: https://github.com/doctrine/orm/tree/4.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.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 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
|
||||
[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
|
||||
[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 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
|
||||
|
||||
11
UPGRADE.md
11
UPGRADE.md
@@ -1,3 +1,10 @@
|
||||
# Upgrade to 3.4.1
|
||||
|
||||
## BC BREAK: You can no longer use the `.*` notation to get all fields of an entity in a DTO
|
||||
|
||||
This feature was introduced in 3.4.0, and introduces several issues, so we
|
||||
decide to remove it before it is used too widely.
|
||||
|
||||
# Upgrade to 3.4
|
||||
|
||||
## Discriminator Map class duplicates
|
||||
@@ -26,7 +33,7 @@ The class `Doctrine\ORM\Mapping\Driver\DatabaseDriver` is deprecated without rep
|
||||
|
||||
Output walkers should implement the new `\Doctrine\ORM\Query\OutputWalker` interface and create
|
||||
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances instead of `Doctrine\ORM\Query\Exec\AbstractSqlExecutor`s.
|
||||
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
|
||||
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
|
||||
`SqlFinalizer` can be kept in the query cache and used regardless of the actual `firstResult`/`maxResult` values.
|
||||
Any operation dependent on `firstResult`/`maxResult` should take place within the `SqlFinalizer::createExecutor()`
|
||||
method. Details can be found at https://github.com/doctrine/orm/pull/11188.
|
||||
@@ -137,7 +144,7 @@ WARNING: This was relaxed in ORM 3.2 when partial was re-allowed for array-hydra
|
||||
`Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD` are removed.
|
||||
- `Doctrine\ORM\EntityManager*::getPartialReference()` is removed.
|
||||
|
||||
## BC BREAK: Enforce ArrayCollection Type on `\Doctrine\ORM\QueryBuilder::setParameters(ArrayCollection $parameters)`
|
||||
## BC BREAK: Enforce ArrayCollection Type on `\Doctrine\ORM\QueryBuilder::setParameters(ArrayCollection $parameters)`
|
||||
|
||||
The argument $parameters can no longer be a key=>value array. Only ArrayCollection types are allowed.
|
||||
|
||||
|
||||
@@ -588,7 +588,7 @@ And then use the ``NEW`` DQL keyword :
|
||||
$query = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c');
|
||||
$users = $query->getResult(); // array of CustomerDTO
|
||||
|
||||
You can also nest several DTO :
|
||||
You can also nest several DTO :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -684,17 +684,6 @@ You can hydrate an entity nested in a DTO :
|
||||
|
||||
// CustomerDTO => {name : 'DOE', email: null, address : {city: 'New York', zip: '10011', address: 'Abbey Road'}
|
||||
|
||||
In a Dto, if you want add all fields of an entity, you can use ``.*`` :
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT NEW NAMED CustomerDTO(c.name, NEW NAMED AddressDTO(a.*) AS address) FROM Customer c JOIN c.address a');
|
||||
$users = $query->getResult(); // array of CustomerDTO
|
||||
|
||||
// CustomerDTO => {name : 'DOE', email: null, city: null, address: {id: 18, city: 'New York', zip: '10011'}}
|
||||
|
||||
It's recommended to use named arguments DTOs with the ``.*`` notation because argument order is not guaranteed.
|
||||
|
||||
Using INDEX BY
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1718,14 +1707,13 @@ Select Expressions
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
|
||||
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
|
||||
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
|
||||
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
|
||||
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
|
||||
NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression | EntityAsDtoArgumentExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
|
||||
NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression | EntityAsDtoArgumentExpression) ["AS" AliasResultVariable]
|
||||
EntityAsDtoArgumentExpression ::= IdentificationVariable
|
||||
AllFieldsExpression ::= IdentificationVariable ".*"
|
||||
|
||||
Conditional Expressions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -2811,7 +2811,7 @@ parameters:
|
||||
-
|
||||
message: '#^Cannot assign new offset to list\<string\>\|string\.$#'
|
||||
identifier: offsetAssign.dimType
|
||||
count: 3
|
||||
count: 2
|
||||
path: src/Query/SqlWalker.php
|
||||
|
||||
-
|
||||
@@ -3450,12 +3450,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @phpstan\-assert\-if\-true for \$obj contains generic interface Doctrine\\ORM\\Proxy\\InternalProxy but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$assoc of method Doctrine\\ORM\\PersistentCollection\<\(int\|string\),mixed\>\:\:setOwner\(\) expects Doctrine\\ORM\\Mapping\\AssociationMapping&Doctrine\\ORM\\Mapping\\ToManyAssociationMapping, Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
|
||||
identifier: argument.type
|
||||
@@ -3498,6 +3492,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Unable to resolve the template type T in call to method static method Symfony\\Component\\VarExporter\\Hydrator\:\:hydrate\(\)$#'
|
||||
identifier: argument.templateType
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
|
||||
identifier: property.notFound
|
||||
|
||||
@@ -45,7 +45,7 @@ parameters:
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '~^Parameter #1 \$command of method Symfony\\Component\\Console\\Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
|
||||
message: '~^Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner::addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
|
||||
path: src/Tools/Console/ConsoleRunner.php
|
||||
|
||||
-
|
||||
|
||||
@@ -602,7 +602,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
public function enableNativeLazyObjects(bool $nativeLazyObjects): void
|
||||
{
|
||||
if (PHP_VERSION_ID < 80400) {
|
||||
if (PHP_VERSION_ID < 80400 && $nativeLazyObjects) {
|
||||
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
|
||||
public function setEntityManager(EntityManagerInterface $em): void
|
||||
{
|
||||
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
|
||||
if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
|
||||
}
|
||||
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
@@ -132,6 +132,9 @@ EOPHP;
|
||||
/** @var array<class-string, Closure> */
|
||||
private array $proxyFactories = [];
|
||||
|
||||
private readonly string $proxyDir;
|
||||
private readonly string $proxyNs;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
|
||||
* connected to the given <tt>EntityManager</tt>.
|
||||
@@ -143,8 +146,8 @@ EOPHP;
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly string $proxyDir,
|
||||
private readonly string $proxyNs,
|
||||
string|null $proxyDir = null,
|
||||
string|null $proxyNs = null,
|
||||
bool|int $autoGenerate = self::AUTOGENERATE_NEVER,
|
||||
) {
|
||||
if (! $proxyDir && ! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
@@ -159,6 +162,17 @@ EOPHP;
|
||||
throw ORMInvalidArgumentException::invalidAutoGenerateMode($autoGenerate);
|
||||
}
|
||||
|
||||
if ($proxyDir === null && $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
$proxyDir = '';
|
||||
}
|
||||
|
||||
if ($proxyNs === null && $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
$proxyNs = '';
|
||||
}
|
||||
|
||||
$this->proxyDir = $proxyDir;
|
||||
$this->proxyNs = $proxyNs;
|
||||
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->autoGenerate = (int) $autoGenerate;
|
||||
$this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory());
|
||||
@@ -171,11 +185,23 @@ EOPHP;
|
||||
public function getProxy(string $className, array $identifier): object
|
||||
{
|
||||
if ($this->em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
$classMetadata = $this->em->getClassMetadata($className);
|
||||
$entityPersister = $this->uow->getEntityPersister($className);
|
||||
$classMetadata = $this->em->getClassMetadata($className);
|
||||
$entityPersister = $this->uow->getEntityPersister($className);
|
||||
$identifierFlattener = $this->identifierFlattener;
|
||||
|
||||
$proxy = $classMetadata->reflClass->newLazyGhost(static function (object $object) use ($identifier, $entityPersister): void {
|
||||
$entityPersister->loadById($identifier, $object);
|
||||
$proxy = $classMetadata->reflClass->newLazyGhost(static function (object $object) use (
|
||||
$identifier,
|
||||
$entityPersister,
|
||||
$identifierFlattener,
|
||||
$classMetadata,
|
||||
): void {
|
||||
$original = $entityPersister->loadById($identifier, $object);
|
||||
if ($original === null) {
|
||||
throw EntityNotFoundException::fromClassNameAndIdentifier(
|
||||
$classMetadata->getName(),
|
||||
$identifierFlattener->flattenIdentifier($classMetadata, $identifier),
|
||||
);
|
||||
}
|
||||
}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE);
|
||||
|
||||
foreach ($identifier as $idField => $value) {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* AllFieldsExpression ::= u.*
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
*/
|
||||
class AllFieldsExpression extends Node
|
||||
{
|
||||
public string $field = '';
|
||||
|
||||
public function __construct(
|
||||
public string|null $identificationVariable,
|
||||
) {
|
||||
$this->field = $this->identificationVariable . '.*';
|
||||
}
|
||||
|
||||
public function dispatch(SqlWalker $walker, int|string $parent = '', int|string $argIndex = '', int|null &$aliasGap = null): string
|
||||
{
|
||||
return $walker->walkAllEntityFieldsExpression($this, $parent, $argIndex, $aliasGap);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class NewObjectExpression extends Node
|
||||
* @param class-string $className
|
||||
* @param mixed[] $args
|
||||
*/
|
||||
public function __construct(public string $className, public array $args, public bool $hasNamedArgs = false)
|
||||
public function __construct(public string $className, public array $args)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -1036,7 +1036,6 @@ final class Parser
|
||||
assert($this->lexer->token !== null);
|
||||
if ($this->lexer->isNextToken(TokenType::T_DOT)) {
|
||||
$this->match(TokenType::T_DOT);
|
||||
|
||||
$this->match(TokenType::T_IDENTIFIER);
|
||||
|
||||
$field = $this->lexer->token->value;
|
||||
@@ -1151,20 +1150,6 @@ final class Parser
|
||||
return new AST\EntityAsDtoArgumentExpression($expression, $identVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
* AllFieldsExpression ::= IdentificationVariable
|
||||
*/
|
||||
public function AllFieldsExpression(): AST\AllFieldsExpression
|
||||
{
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
assert($this->lexer->token !== null);
|
||||
|
||||
$this->match(TokenType::T_DOT);
|
||||
$this->match(TokenType::T_MULTIPLY);
|
||||
|
||||
return new AST\AllFieldsExpression($identVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
* SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
|
||||
*/
|
||||
@@ -1479,7 +1464,7 @@ final class Parser
|
||||
|
||||
assert($this->lexer->lookahead !== null);
|
||||
$expr = match (true) {
|
||||
$this->isMathOperator($peek) => $this->SimpleArithmeticExpression(),
|
||||
$this->isMathOperator($peek) || $this->isMathOperator($glimpse) => $this->SimpleArithmeticExpression(),
|
||||
$glimpse !== null && $glimpse->type === TokenType::T_DOT => $this->SingleValuedPathExpression(),
|
||||
$this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->ScalarExpression(),
|
||||
$this->lexer->lookahead->type === TokenType::T_CASE => $this->CaseExpression(),
|
||||
@@ -1841,7 +1826,7 @@ final class Parser
|
||||
|
||||
$this->match(TokenType::T_CLOSE_PARENTHESIS);
|
||||
|
||||
$expression = new AST\NewObjectExpression($className, $args, $useNamedArguments);
|
||||
$expression = new AST\NewObjectExpression($className, $args);
|
||||
|
||||
// Defer NewObjectExpression validation
|
||||
$this->deferredNewObjectExpressions[] = [
|
||||
@@ -1888,7 +1873,7 @@ final class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
|
||||
* NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]
|
||||
*/
|
||||
public function NewObjectArg(string|null &$fieldAlias = null): mixed
|
||||
{
|
||||
@@ -2000,14 +1985,10 @@ final class Parser
|
||||
// it is no function, so it must be a field path
|
||||
case $lookahead === TokenType::T_IDENTIFIER:
|
||||
$this->lexer->peek(); // lookahead => '.'
|
||||
$token = $this->lexer->peek(); // lookahead => token after '.'
|
||||
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
|
||||
$this->lexer->peek(); // lookahead => token after '.'
|
||||
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
|
||||
$this->lexer->resetPeek();
|
||||
|
||||
if ($token->value === '*') {
|
||||
return $this->AllFieldsExpression();
|
||||
}
|
||||
|
||||
if ($this->isMathOperator($peek)) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
}
|
||||
|
||||
@@ -1518,17 +1518,11 @@ class SqlWalker
|
||||
{
|
||||
$sqlSelectExpressions = [];
|
||||
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
|
||||
$aliasGap = $newObjectExpression->hasNamedArgs ? null : 0;
|
||||
|
||||
foreach ($newObjectExpression->args as $argIndex => $e) {
|
||||
if (! $newObjectExpression->hasNamedArgs) {
|
||||
$argIndex += $aliasGap;
|
||||
}
|
||||
|
||||
$resultAlias = $this->scalarResultCounter++;
|
||||
$columnAlias = $this->getSQLColumnAlias('sclr');
|
||||
$fieldType = 'string';
|
||||
$isScalarResult = true;
|
||||
$resultAlias = $this->scalarResultCounter++;
|
||||
$columnAlias = $this->getSQLColumnAlias('sclr');
|
||||
$fieldType = 'string';
|
||||
|
||||
switch (true) {
|
||||
case $e instanceof AST\NewObjectExpression:
|
||||
@@ -1582,26 +1576,18 @@ class SqlWalker
|
||||
$sqlSelectExpressions[] = trim($e->dispatch($this));
|
||||
break;
|
||||
|
||||
case $e instanceof AST\AllFieldsExpression:
|
||||
$isScalarResult = false;
|
||||
$sqlSelectExpressions[] = $e->dispatch($this, $objIndex, $argIndex, $aliasGap);
|
||||
break;
|
||||
|
||||
default:
|
||||
$sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($isScalarResult) {
|
||||
$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
|
||||
$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
|
||||
|
||||
$this->rsm->newObjectMappings[$columnAlias] = [
|
||||
'className' => $newObjectExpression->className,
|
||||
'objIndex' => $objIndex,
|
||||
'argIndex' => $argIndex,
|
||||
];
|
||||
}
|
||||
$this->rsm->newObjectMappings[$columnAlias] = [
|
||||
'objIndex' => $objIndex,
|
||||
'argIndex' => $argIndex,
|
||||
];
|
||||
}
|
||||
|
||||
$this->rsm->newObject[$objIndex] = $newObjectExpression->className;
|
||||
@@ -2306,42 +2292,6 @@ class SqlWalker
|
||||
return $resultAlias;
|
||||
}
|
||||
|
||||
public function walkAllEntityFieldsExpression(AST\AllFieldsExpression $expression, int|string $objIndex, int|string $argIndex, int|null &$aliasGap): string
|
||||
{
|
||||
$dqlAlias = $expression->identificationVariable;
|
||||
$class = $this->getMetadataForDqlAlias($expression->identificationVariable);
|
||||
|
||||
$sqlParts = [];
|
||||
// Select all fields from the queried class
|
||||
foreach ($class->fieldMappings as $fieldName => $mapping) {
|
||||
$tableName = isset($mapping->inherited)
|
||||
? $this->em->getClassMetadata($mapping->inherited)->getTableName()
|
||||
: $class->getTableName();
|
||||
|
||||
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
|
||||
$columnAlias = $this->getSQLColumnAlias($mapping->columnName);
|
||||
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
|
||||
|
||||
$col = $sqlTableAlias . '.' . $quotedColumnName;
|
||||
|
||||
$type = Type::getType($mapping->type);
|
||||
$col = $type->convertToPHPValueSQL($col, $this->platform);
|
||||
|
||||
$sqlParts[] = $col . ' AS ' . $columnAlias;
|
||||
|
||||
$this->scalarResultAliasMap[$objIndex][] = $columnAlias;
|
||||
|
||||
$this->rsm->addScalarResult($columnAlias, $objIndex, $mapping->type);
|
||||
|
||||
$this->rsm->newObjectMappings[$columnAlias] = [
|
||||
'objIndex' => $objIndex,
|
||||
'argIndex' => $aliasGap === null ? $fieldName : (int) $argIndex + $aliasGap++,
|
||||
];
|
||||
}
|
||||
|
||||
return implode(', ', $sqlParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The list in parentheses of valid child discriminators from the given class
|
||||
*
|
||||
|
||||
28
src/Tools/Console/ApplicationCompatibility.php
Normal file
28
src/Tools/Console/ApplicationCompatibility.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function method_exists;
|
||||
|
||||
/**
|
||||
* Forward compatibility with Symfony Console 7.4
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait ApplicationCompatibility
|
||||
{
|
||||
private static function addCommandToApplication(Application $application, Command $command): Command|null
|
||||
{
|
||||
if (method_exists(Application::class, 'addCommand')) {
|
||||
// @phpstan-ignore method.notFound (This method will be added in Symfony 7.4)
|
||||
return $application->addCommand($command);
|
||||
}
|
||||
|
||||
return $application->add($command);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ use function class_exists;
|
||||
*/
|
||||
final class ConsoleRunner
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
/**
|
||||
* Runs console with the given helper set.
|
||||
*
|
||||
@@ -59,7 +61,10 @@ final class ConsoleRunner
|
||||
$connectionProvider = new ConnectionFromManagerProvider($entityManagerProvider);
|
||||
|
||||
if (class_exists(DBALConsole\Command\ReservedWordsCommand::class)) {
|
||||
$cli->add(new DBALConsole\Command\ReservedWordsCommand($connectionProvider));
|
||||
self::addCommandToApplication(
|
||||
$cli,
|
||||
new DBALConsole\Command\ReservedWordsCommand($connectionProvider),
|
||||
);
|
||||
}
|
||||
|
||||
$cli->addCommands(
|
||||
|
||||
@@ -2383,9 +2383,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$class->reflClass->markLazyObjectAsInitialized($entity);
|
||||
} else {
|
||||
$entity->__setInitialized(true);
|
||||
}
|
||||
|
||||
Hydrator::hydrate($entity, (array) $class->reflClass->newInstanceWithoutConstructor());
|
||||
Hydrator::hydrate($entity, (array) $class->reflClass->newInstanceWithoutConstructor());
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
! isset($hints[Query::HINT_REFRESH])
|
||||
@@ -3050,14 +3050,10 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a value is an uninitialized entity.
|
||||
*
|
||||
* @phpstan-assert-if-true InternalProxy $obj
|
||||
*/
|
||||
/** Tests if a value is an uninitialized entity. */
|
||||
public function isUninitializedObject(mixed $obj): bool
|
||||
{
|
||||
if ($this->em->getConfiguration()->isNativeLazyObjectsEnabled() && ! ($obj instanceof Collection)) {
|
||||
if ($this->em->getConfiguration()->isNativeLazyObjectsEnabled() && ! ($obj instanceof Collection) && is_object($obj)) {
|
||||
return $this->em->getClassMetadata($obj::class)->reflClass->isUninitializedLazyObject($obj);
|
||||
}
|
||||
|
||||
|
||||
238
tests/Doctrine/Tests/ORM/Functional/GH8011Test.php
Normal file
238
tests/Doctrine/Tests/ORM/Functional/GH8011Test.php
Normal 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 PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Performance\LazyLoading;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Proxy\InternalProxy as Proxy;
|
||||
use Doctrine\Performance\EntityManagerFactory;
|
||||
use Doctrine\Performance\Mock\NonProxyLoadingEntityManager;
|
||||
@@ -25,9 +26,11 @@ final class ProxyInitializationTimeBench
|
||||
/** @var Proxy[] */
|
||||
private array|null $initializedEmployees = null;
|
||||
|
||||
private EntityManager $em;
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$proxyFactory = (new NonProxyLoadingEntityManager(EntityManagerFactory::getEntityManager([])))
|
||||
$proxyFactory = (new NonProxyLoadingEntityManager($this->em = EntityManagerFactory::getEntityManager([])))
|
||||
->getProxyFactory();
|
||||
|
||||
for ($i = 0; $i < 10000; ++$i) {
|
||||
@@ -36,36 +39,36 @@ final class ProxyInitializationTimeBench
|
||||
$this->initializedUsers[$i] = $proxyFactory->getProxy(CmsUser::class, ['id' => $i]);
|
||||
$this->initializedEmployees[$i] = $proxyFactory->getProxy(CmsEmployee::class, ['id' => $i]);
|
||||
|
||||
$this->initializedUsers[$i]->__load();
|
||||
$this->initializedEmployees[$i]->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($this->initializedUsers[$i]);
|
||||
$this->em->getUnitOfWork()->initializeObject($this->initializedEmployees[$i]);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchCmsUserInitialization(): void
|
||||
{
|
||||
foreach ($this->cmsUsers as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchCmsEmployeeInitialization(): void
|
||||
{
|
||||
foreach ($this->cmsEmployees as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchInitializationOfAlreadyInitializedCmsUsers(): void
|
||||
{
|
||||
foreach ($this->initializedUsers as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
|
||||
public function benchInitializationOfAlreadyInitializedCmsEmployees(): void
|
||||
{
|
||||
foreach ($this->initializedEmployees as $proxy) {
|
||||
$proxy->__load();
|
||||
$this->em->getUnitOfWork()->initializeObject($proxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\CMS;
|
||||
|
||||
class CmsDumbVariadicDTO
|
||||
{
|
||||
private array $values = [];
|
||||
|
||||
public function __construct(...$args)
|
||||
{
|
||||
foreach ($args as $key => $val) {
|
||||
$this->values[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
public function __get(string $key): mixed
|
||||
{
|
||||
return $this->values[$key] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,10 @@ use Generator;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhp;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use stdClass;
|
||||
use Symfony\Component\VarExporter\LazyGhostTrait;
|
||||
use TypeError;
|
||||
|
||||
class EntityManagerTest extends OrmTestCase
|
||||
@@ -180,17 +181,12 @@ class EntityManagerTest extends OrmTestCase
|
||||
}
|
||||
|
||||
/** Resetting the EntityManager relies on lazy objects until https://github.com/doctrine/orm/issues/5933 is resolved */
|
||||
#[RequiresPhp('8.4')]
|
||||
public function testLazyGhostEntityManager(): void
|
||||
{
|
||||
$em = new class () extends EntityManager {
|
||||
use LazyGhostTrait;
|
||||
$reflector = new ReflectionClass(EntityManager::class);
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$em = $em::createLazyGhost(static function ($em): void {
|
||||
$em = $reflector->newLazyGhost($initializer = static function (EntityManager $em): void {
|
||||
$r = new ReflectionProperty(EntityManager::class, 'unitOfWork');
|
||||
$r->setValue($em, new class () extends UnitOfWork {
|
||||
public function __construct()
|
||||
@@ -207,7 +203,7 @@ class EntityManagerTest extends OrmTestCase
|
||||
$em->close();
|
||||
$this->assertFalse($em->isOpen());
|
||||
|
||||
$em->resetLazyObject();
|
||||
$reflector->resetAsLazyGhost($em, $initializer);
|
||||
$this->assertTrue($em->isOpen());
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ use Doctrine\Tests\Models\CMS\CmsAddress;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddressDTO;
|
||||
use Doctrine\Tests\Models\CMS\CmsAddressDTONamedArgs;
|
||||
use Doctrine\Tests\Models\CMS\CmsDumbDTO;
|
||||
use Doctrine\Tests\Models\CMS\CmsDumbVariadicDTO;
|
||||
use Doctrine\Tests\Models\CMS\CmsEmail;
|
||||
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
|
||||
use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
@@ -1572,290 +1571,6 @@ class NewOperatorTest extends OrmFunctionalTestCase
|
||||
self::assertSame($this->fixtures[2]->username, $result[2]['cmsUserUsername']);
|
||||
}
|
||||
|
||||
public function testShouldSupportNestedNewOperatorsWithAllFieldsForDto(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new CmsDumbDTO(
|
||||
u.*
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
JOIN
|
||||
u.email e
|
||||
JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[0]);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[1]);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[2]);
|
||||
|
||||
self::assertSame($this->fixtures[0]->status, $result[0]->val2);
|
||||
self::assertSame($this->fixtures[1]->status, $result[1]->val2);
|
||||
self::assertSame($this->fixtures[2]->status, $result[2]->val2);
|
||||
|
||||
self::assertSame($this->fixtures[0]->username, $result[0]->val3);
|
||||
self::assertSame($this->fixtures[1]->username, $result[1]->val3);
|
||||
self::assertSame($this->fixtures[2]->username, $result[2]->val3);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->val4);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->val4);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->val4);
|
||||
}
|
||||
|
||||
public function testShouldSupportNestedNewOperatorsWithAllFieldsForNamedDto(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new NAMED CmsDumbVariadicDTO(
|
||||
u.*
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
JOIN
|
||||
u.email e
|
||||
JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[0]);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[1]);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[2]);
|
||||
|
||||
self::assertSame($this->fixtures[0]->status, $result[0]->status);
|
||||
self::assertSame($this->fixtures[1]->status, $result[1]->status);
|
||||
self::assertSame($this->fixtures[2]->status, $result[2]->status);
|
||||
|
||||
self::assertSame($this->fixtures[0]->username, $result[0]->username);
|
||||
self::assertSame($this->fixtures[1]->username, $result[1]->username);
|
||||
self::assertSame($this->fixtures[2]->username, $result[2]->username);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->name);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->name);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->name);
|
||||
}
|
||||
|
||||
public function testShouldSupportNestedNewOperatorsWithMultipleAllFieldsForNamedDto(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new NAMED CmsDumbVariadicDTO(
|
||||
u.*, a.*
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
JOIN
|
||||
u.email e
|
||||
JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[0]);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[1]);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[2]);
|
||||
|
||||
self::assertSame($this->fixtures[0]->status, $result[0]->status);
|
||||
self::assertSame($this->fixtures[1]->status, $result[1]->status);
|
||||
self::assertSame($this->fixtures[2]->status, $result[2]->status);
|
||||
|
||||
self::assertSame($this->fixtures[0]->username, $result[0]->username);
|
||||
self::assertSame($this->fixtures[1]->username, $result[1]->username);
|
||||
self::assertSame($this->fixtures[2]->username, $result[2]->username);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->name);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->name);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->name);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->city, $result[0]->city);
|
||||
self::assertSame($this->fixtures[1]->address->city, $result[1]->city);
|
||||
self::assertSame($this->fixtures[2]->address->city, $result[2]->city);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->zip, $result[0]->zip);
|
||||
self::assertSame($this->fixtures[1]->address->zip, $result[1]->zip);
|
||||
self::assertSame($this->fixtures[2]->address->zip, $result[2]->zip);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->country, $result[0]->country);
|
||||
self::assertSame($this->fixtures[1]->address->country, $result[1]->country);
|
||||
self::assertSame($this->fixtures[2]->address->country, $result[2]->country);
|
||||
}
|
||||
|
||||
public function testShouldSupportNestedNewOperatorsWithAllFieldsForNamedDtoWithOtherValues(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new NAMED CmsDumbVariadicDTO(
|
||||
u.*, e.email, a.zip, a.country
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
JOIN
|
||||
u.email e
|
||||
JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[0]);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[1]);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[2]);
|
||||
|
||||
self::assertSame($this->fixtures[0]->status, $result[0]->status);
|
||||
self::assertSame($this->fixtures[1]->status, $result[1]->status);
|
||||
self::assertSame($this->fixtures[2]->status, $result[2]->status);
|
||||
|
||||
self::assertSame($this->fixtures[0]->username, $result[0]->username);
|
||||
self::assertSame($this->fixtures[1]->username, $result[1]->username);
|
||||
self::assertSame($this->fixtures[2]->username, $result[2]->username);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->name);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->name);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->name);
|
||||
|
||||
self::assertSame($this->fixtures[0]->email->email, $result[0]->email);
|
||||
self::assertSame($this->fixtures[1]->email->email, $result[1]->email);
|
||||
self::assertSame($this->fixtures[2]->email->email, $result[2]->email);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->zip, $result[0]->zip);
|
||||
self::assertSame($this->fixtures[1]->address->zip, $result[1]->zip);
|
||||
self::assertSame($this->fixtures[2]->address->zip, $result[2]->zip);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->country, $result[0]->country);
|
||||
self::assertSame($this->fixtures[1]->address->country, $result[1]->country);
|
||||
self::assertSame($this->fixtures[2]->address->country, $result[2]->country);
|
||||
}
|
||||
|
||||
public function testShouldSupportNestedNewOperatorsWithAllFieldsForNestedDto(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new CmsDumbDTO(
|
||||
u.name,
|
||||
e.email,
|
||||
new CmsDumbDTO(
|
||||
a.*
|
||||
) as address
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
JOIN
|
||||
u.email e
|
||||
JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[0]);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[1]);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[2]);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->val1);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->val1);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->val1);
|
||||
|
||||
self::assertSame($this->fixtures[0]->email->email, $result[0]->val2);
|
||||
self::assertSame($this->fixtures[1]->email->email, $result[1]->val2);
|
||||
self::assertSame($this->fixtures[2]->email->email, $result[2]->val2);
|
||||
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[0]->val3);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[1]->val3);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[2]->val3);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->country, $result[0]->val3->val2);
|
||||
self::assertSame($this->fixtures[1]->address->country, $result[1]->val3->val2);
|
||||
self::assertSame($this->fixtures[2]->address->country, $result[2]->val3->val2);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->zip, $result[0]->val3->val3);
|
||||
self::assertSame($this->fixtures[1]->address->zip, $result[1]->val3->val3);
|
||||
self::assertSame($this->fixtures[2]->address->zip, $result[2]->val3->val3);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->city, $result[0]->val3->val4);
|
||||
self::assertSame($this->fixtures[1]->address->city, $result[1]->val3->val4);
|
||||
self::assertSame($this->fixtures[2]->address->city, $result[2]->val3->val4);
|
||||
}
|
||||
|
||||
public function testShouldSupportNestedNewOperatorsWithAllFieldsForNestedNamedDto(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new CmsDumbDTO(
|
||||
u.name,
|
||||
e.email,
|
||||
new NAMED CmsDumbVariadicDTO(
|
||||
a.*
|
||||
) as address
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
JOIN
|
||||
u.email e
|
||||
JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[0]);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[1]);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[2]);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->val1);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->val1);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->val1);
|
||||
|
||||
self::assertSame($this->fixtures[0]->email->email, $result[0]->val2);
|
||||
self::assertSame($this->fixtures[1]->email->email, $result[1]->val2);
|
||||
self::assertSame($this->fixtures[2]->email->email, $result[2]->val2);
|
||||
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[0]->val3);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[1]->val3);
|
||||
self::assertInstanceOf(CmsDumbVariadicDTO::class, $result[2]->val3);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->country, $result[0]->val3->country);
|
||||
self::assertSame($this->fixtures[1]->address->country, $result[1]->val3->country);
|
||||
self::assertSame($this->fixtures[2]->address->country, $result[2]->val3->country);
|
||||
|
||||
self::assertSame($this->fixtures[2]->address->city, $result[2]->val3->city);
|
||||
self::assertSame($this->fixtures[0]->address->city, $result[0]->val3->city);
|
||||
self::assertSame($this->fixtures[1]->address->city, $result[1]->val3->city);
|
||||
|
||||
self::assertSame($this->fixtures[2]->address->zip, $result[2]->val3->zip);
|
||||
self::assertSame($this->fixtures[0]->address->zip, $result[0]->val3->zip);
|
||||
self::assertSame($this->fixtures[1]->address->zip, $result[1]->val3->zip);
|
||||
}
|
||||
|
||||
public function testVariadicArgument(): void
|
||||
{
|
||||
$dql = <<<'SQL'
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Tests\Models\PropertyHooks\MappingVirtualProperty;
|
||||
use Doctrine\Tests\Models\PropertyHooks\User;
|
||||
@@ -17,6 +18,10 @@ class PropertyHooksTest extends OrmFunctionalTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if ($this->_em->getConnection()->getDatabasePlatform() instanceof AbstractMySQLPlatform) {
|
||||
self::markTestSkipped('MySQL/MariaDB is case-insensitive by default, and the logic of this test relies on case sensitivity.');
|
||||
}
|
||||
|
||||
if (! $this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
$this->markTestSkipped('Property hooks require native lazy objects to be enabled.');
|
||||
}
|
||||
|
||||
@@ -14,8 +14,6 @@ use Doctrine\Tests\Models\CMS\CmsUser;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Doctrine\Tests\Proxies\__CG__\Doctrine\Tests\Models\CMS\CmsUser as CmsUserProxy;
|
||||
|
||||
use function assert;
|
||||
|
||||
/**
|
||||
* Test that Doctrine ORM correctly works with proxy instances exactly like with ordinary Entities
|
||||
*
|
||||
@@ -34,10 +32,6 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if ($this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
self::markTestSkipped('This test is not applicable when lazy proxy is enabled.');
|
||||
}
|
||||
|
||||
$this->createSchemaForModels(
|
||||
CmsUser::class,
|
||||
CmsTag::class,
|
||||
@@ -83,8 +77,7 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
{
|
||||
$userId = $this->user->getId();
|
||||
$uninitializedProxy = $this->_em->getReference(CmsUser::class, $userId);
|
||||
assert($uninitializedProxy instanceof CmsUserProxy);
|
||||
self::assertInstanceOf(CmsUserProxy::class, $uninitializedProxy);
|
||||
$this->assertTrue($this->isUninitializedObject($uninitializedProxy));
|
||||
|
||||
$this->_em->persist($uninitializedProxy);
|
||||
$this->_em->flush();
|
||||
@@ -116,6 +109,10 @@ class ProxiesLikeEntitiesTest extends OrmFunctionalTestCase
|
||||
*/
|
||||
public function testFindWithProxyName(): void
|
||||
{
|
||||
if ($this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
self::markTestSkipped('There is no such thing as a proxy class name when native lazy objects are enabled.');
|
||||
}
|
||||
|
||||
$result = $this->_em->find(CmsUserProxy::class, $this->user->getId());
|
||||
self::assertSame($this->user->getId(), $result->getId());
|
||||
$this->_em->clear();
|
||||
|
||||
@@ -14,8 +14,7 @@ use Doctrine\ORM\Mapping\Table;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function get_class;
|
||||
use ReflectionClass;
|
||||
|
||||
#[Group('GH10808')]
|
||||
class GH10808Test extends OrmFunctionalTestCase
|
||||
@@ -32,10 +31,6 @@ class GH10808Test extends OrmFunctionalTestCase
|
||||
|
||||
public function testDQLDeferredEagerLoad(): void
|
||||
{
|
||||
if ($this->_em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
self::markTestSkipped('Test requires lazy loading to be disabled');
|
||||
}
|
||||
|
||||
$appointment = new GH10808Appointment();
|
||||
|
||||
$this->_em->persist($appointment);
|
||||
@@ -55,14 +50,13 @@ class GH10808Test extends OrmFunctionalTestCase
|
||||
|
||||
$eagerLoadResult = $query->setHint(UnitOfWork::HINT_DEFEREAGERLOAD, false)->getSingleResult();
|
||||
|
||||
self::assertNotEquals(
|
||||
GH10808AppointmentChild::class,
|
||||
get_class($deferredLoadResult->child),
|
||||
'$deferredLoadResult->child should be a proxy',
|
||||
$reflector = new ReflectionClass(GH10808AppointmentChild::class);
|
||||
self::assertFalse(
|
||||
$this->isUninitializedObject($deferredLoadResult->child),
|
||||
'$deferredLoadResult->child should be a native lazy object',
|
||||
);
|
||||
self::assertEquals(
|
||||
GH10808AppointmentChild::class,
|
||||
get_class($eagerLoadResult->child),
|
||||
self::assertFalse(
|
||||
$this->isUninitializedObject($deferredLoadResult->child),
|
||||
'$eagerLoadResult->child should not be a proxy',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use Doctrine\ORM\Internal\Hydration\HydrationException;
|
||||
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Proxy\InternalProxy;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\Mocks\ArrayResultFactory;
|
||||
@@ -1030,7 +1029,7 @@ class ObjectHydratorTest extends HydrationTestCase
|
||||
'Proxies',
|
||||
ProxyFactory::AUTOGENERATE_ALWAYS,
|
||||
) extends ProxyFactory {
|
||||
public function getProxy(string $className, array $identifier): InternalProxy
|
||||
public function getProxy(string $className, array $identifier): object
|
||||
{
|
||||
TestCase::assertSame(ECommerceShipping::class, $className);
|
||||
TestCase::assertSame(['id' => 42], $identifier);
|
||||
@@ -1084,7 +1083,7 @@ class ObjectHydratorTest extends HydrationTestCase
|
||||
'Proxies',
|
||||
ProxyFactory::AUTOGENERATE_ALWAYS,
|
||||
) extends ProxyFactory {
|
||||
public function getProxy(string $className, array $identifier): InternalProxy
|
||||
public function getProxy(string $className, array $identifier): object
|
||||
{
|
||||
TestCase::assertSame(ECommerceShipping::class, $className);
|
||||
TestCase::assertSame(['id' => 42], $identifier);
|
||||
|
||||
@@ -20,6 +20,8 @@ use Doctrine\Tests\Models\Company\CompanyPerson;
|
||||
use Doctrine\Tests\Models\ECommerce\ECommerceFeature;
|
||||
use Doctrine\Tests\OrmTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhp;
|
||||
use ReflectionClass;
|
||||
use ReflectionProperty;
|
||||
use stdClass;
|
||||
|
||||
@@ -127,7 +129,6 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
$this->uowMock->setEntityPersister(ECommerceFeature::class, $persister);
|
||||
|
||||
$proxy = $this->proxyFactory->getProxy(ECommerceFeature::class, ['id' => 42]);
|
||||
assert($proxy instanceof Proxy);
|
||||
|
||||
$persister
|
||||
->expects(self::atLeastOnce())
|
||||
@@ -140,7 +141,19 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
} catch (EntityNotFoundException) {
|
||||
}
|
||||
|
||||
self::assertFalse($proxy->__isInitialized());
|
||||
self::assertUninitializedLazyObject($proxy);
|
||||
}
|
||||
|
||||
private static function assertUninitializedLazyObject(object $proxy): void
|
||||
{
|
||||
if ($proxy instanceof Proxy) {
|
||||
self::assertFalse($proxy->__isInitialized());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$reflectionClass = new ReflectionClass($proxy);
|
||||
self::assertTrue($reflectionClass->isUninitializedLazyObject($proxy));
|
||||
}
|
||||
|
||||
#[Group('DDC-2432')]
|
||||
@@ -153,7 +166,6 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
$this->uowMock->setEntityPersister(ECommerceFeature::class, $persister);
|
||||
|
||||
$proxy = $this->proxyFactory->getProxy(ECommerceFeature::class, ['id' => 42]);
|
||||
assert($proxy instanceof Proxy);
|
||||
|
||||
$persister
|
||||
->expects(self::atLeastOnce())
|
||||
@@ -167,11 +179,15 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
} catch (EntityNotFoundException) {
|
||||
}
|
||||
|
||||
self::assertFalse($proxy->__isInitialized());
|
||||
self::assertUninitializedLazyObject($proxy);
|
||||
}
|
||||
|
||||
public function testProxyClonesParentFields(): void
|
||||
{
|
||||
if ($this->emMock->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
self::markTestSkipped('This test is not relevant when native lazy objects are enabled');
|
||||
}
|
||||
|
||||
$companyEmployee = new CompanyEmployee();
|
||||
$companyEmployee->setSalary(1000); // A property on the CompanyEmployee
|
||||
$companyEmployee->setName('Bob'); // A property on the parent class, CompanyPerson
|
||||
@@ -209,6 +225,24 @@ class ProxyFactoryTest extends OrmTestCase
|
||||
self::assertSame(1000, $cloned->getSalary(), 'Expect properties on the CompanyEmployee class to be cloned');
|
||||
self::assertSame('Bob', $cloned->getName(), 'Expect properties on the CompanyPerson class to be cloned');
|
||||
}
|
||||
|
||||
#[RequiresPhp('8.4')]
|
||||
public function testProxyFactoryAcceptsNullProxyArgsWhenNativeLazyObjectsAreEnabled(): void
|
||||
{
|
||||
$this->emMock->getConfiguration()->enableNativeLazyObjects(true);
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this->emMock,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
$proxy = $this->proxyFactory->getProxy(
|
||||
ECommerceFeature::class,
|
||||
['id' => 42],
|
||||
);
|
||||
$reflection = new ReflectionClass($proxy);
|
||||
|
||||
self::assertTrue($reflection->isUninitializedLazyObject($proxy));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractClass
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\ORM\Tools\Console\ApplicationCompatibility;
|
||||
use Doctrine\ORM\Tools\Console\Command\ClearCache\CollectionRegionCommand;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
use Doctrine\Tests\Models\Cache\State;
|
||||
@@ -15,6 +16,8 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
#[Group('DDC-2183')]
|
||||
class ClearCacheCollectionRegionCommandTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
private Application $application;
|
||||
|
||||
private CollectionRegionCommand $command;
|
||||
@@ -28,7 +31,7 @@ class ClearCacheCollectionRegionCommandTest extends OrmFunctionalTestCase
|
||||
$this->command = new CollectionRegionCommand(new SingleManagerProvider($this->_em));
|
||||
|
||||
$this->application = new Application();
|
||||
$this->application->add($this->command);
|
||||
self::addCommandToApplication($this->application, $this->command);
|
||||
}
|
||||
|
||||
public function testClearAllRegion(): void
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\ORM\Tools\Console\ApplicationCompatibility;
|
||||
use Doctrine\ORM\Tools\Console\Command\ClearCache\EntityRegionCommand;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
@@ -18,6 +19,8 @@ use function trim;
|
||||
#[Group('DDC-2183')]
|
||||
class ClearCacheEntityRegionCommandTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
private Application $application;
|
||||
|
||||
private EntityRegionCommand $command;
|
||||
@@ -31,7 +34,7 @@ class ClearCacheEntityRegionCommandTest extends OrmFunctionalTestCase
|
||||
$this->command = new EntityRegionCommand(new SingleManagerProvider($this->_em));
|
||||
|
||||
$this->application = new Application();
|
||||
$this->application->add($this->command);
|
||||
self::addCommandToApplication($this->application, $this->command);
|
||||
}
|
||||
|
||||
public function testClearAllRegion(): void
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\ORM\Tools\Console\ApplicationCompatibility;
|
||||
use Doctrine\ORM\Tools\Console\Command\ClearCache\QueryRegionCommand;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
@@ -14,6 +15,8 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
#[Group('DDC-2183')]
|
||||
class ClearCacheQueryRegionCommandTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
private Application $application;
|
||||
|
||||
private QueryRegionCommand $command;
|
||||
@@ -27,7 +30,7 @@ class ClearCacheQueryRegionCommandTest extends OrmFunctionalTestCase
|
||||
$this->command = new QueryRegionCommand(new SingleManagerProvider($this->_em));
|
||||
|
||||
$this->application = new Application();
|
||||
$this->application->add($this->command);
|
||||
self::addCommandToApplication($this->application, $this->command);
|
||||
}
|
||||
|
||||
public function testClearAllRegion(): void
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\Tests\ORM\Tools\Console\Command;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Tools\Console\ApplicationCompatibility;
|
||||
use Doctrine\ORM\Tools\Console\Command\InfoCommand;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
@@ -18,6 +19,8 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class InfoCommandTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
private Application $application;
|
||||
private InfoCommand $command;
|
||||
private CommandTester $tester;
|
||||
@@ -28,7 +31,7 @@ class InfoCommandTest extends OrmFunctionalTestCase
|
||||
|
||||
$this->application = new Application();
|
||||
|
||||
$this->application->add(new InfoCommand(new SingleManagerProvider($this->_em)));
|
||||
self::addCommandToApplication($this->application, new InfoCommand(new SingleManagerProvider($this->_em)));
|
||||
|
||||
$this->command = $this->application->find('orm:info');
|
||||
$this->tester = new CommandTester($this->command);
|
||||
@@ -58,7 +61,7 @@ class InfoCommandTest extends OrmFunctionalTestCase
|
||||
->willReturn($configuration);
|
||||
|
||||
$application = new Application();
|
||||
$application->add(new InfoCommand(new SingleManagerProvider($em)));
|
||||
self::addCommandToApplication($application, new InfoCommand(new SingleManagerProvider($em)));
|
||||
|
||||
$command = $application->find('orm:info');
|
||||
$tester = new CommandTester($command);
|
||||
@@ -96,7 +99,7 @@ class InfoCommandTest extends OrmFunctionalTestCase
|
||||
->willThrowException(new MappingException('exception message'));
|
||||
|
||||
$application = new Application();
|
||||
$application->add(new InfoCommand(new SingleManagerProvider($em)));
|
||||
self::addCommandToApplication($application, new InfoCommand(new SingleManagerProvider($em)));
|
||||
|
||||
$command = $application->find('orm:info');
|
||||
$tester = new CommandTester($command);
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\ORM\Tools\Console\ApplicationCompatibility;
|
||||
use Doctrine\ORM\Tools\Console\Command\MappingDescribeCommand;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
use Doctrine\Tests\Models\Cache\AttractionInfo;
|
||||
@@ -19,6 +20,8 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
#[CoversClass(MappingDescribeCommand::class)]
|
||||
class MappingDescribeCommandTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
private Application $application;
|
||||
|
||||
private MappingDescribeCommand $command;
|
||||
@@ -30,7 +33,7 @@ class MappingDescribeCommandTest extends OrmFunctionalTestCase
|
||||
parent::setUp();
|
||||
|
||||
$this->application = new Application();
|
||||
$this->application->add(new MappingDescribeCommand(new SingleManagerProvider($this->_em)));
|
||||
self::addCommandToApplication($this->application, new MappingDescribeCommand(new SingleManagerProvider($this->_em)));
|
||||
|
||||
$this->command = $this->application->find('orm:mapping:describe');
|
||||
$this->tester = new CommandTester($this->command);
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\ORM\Tools\Console\ApplicationCompatibility;
|
||||
use Doctrine\ORM\Tools\Console\Command\RunDqlCommand;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
use Doctrine\Tests\Models\Generic\DateTimeModel;
|
||||
@@ -20,6 +21,8 @@ use function trim;
|
||||
#[CoversClass(RunDqlCommand::class)]
|
||||
class RunDqlCommandTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
private Application $application;
|
||||
|
||||
private RunDqlCommand $command;
|
||||
@@ -35,7 +38,7 @@ class RunDqlCommandTest extends OrmFunctionalTestCase
|
||||
$this->command = new RunDqlCommand(new SingleManagerProvider($this->_em));
|
||||
|
||||
$this->application = new Application();
|
||||
$this->application->add($this->command);
|
||||
self::addCommandToApplication($this->application, $this->command);
|
||||
|
||||
$this->tester = new CommandTester($this->command);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\DBAL\Platforms\SQLitePlatform;
|
||||
use Doctrine\DBAL\Schema\SchemaDiff;
|
||||
use Doctrine\ORM\Tools\Console\ApplicationCompatibility;
|
||||
use Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
@@ -22,6 +23,8 @@ use function method_exists;
|
||||
#[CoversClass(ValidateSchemaCommand::class)]
|
||||
class ValidateSchemaCommandTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use ApplicationCompatibility;
|
||||
|
||||
private ValidateSchemaCommand $command;
|
||||
|
||||
private CommandTester $tester;
|
||||
@@ -39,7 +42,7 @@ class ValidateSchemaCommandTest extends OrmFunctionalTestCase
|
||||
}
|
||||
|
||||
$application = new Application();
|
||||
$application->add(new ValidateSchemaCommand(new SingleManagerProvider($this->_em)));
|
||||
self::addCommandToApplication($application, new ValidateSchemaCommand(new SingleManagerProvider($this->_em)));
|
||||
|
||||
$this->command = $application->find('orm:validate-schema');
|
||||
$this->tester = new CommandTester($this->command);
|
||||
|
||||
@@ -20,6 +20,7 @@ use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\ORM\Mapping\Version;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
@@ -40,6 +41,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use stdClass;
|
||||
|
||||
use function enum_exists;
|
||||
use function is_object;
|
||||
use function random_int;
|
||||
use function uniqid;
|
||||
|
||||
@@ -278,7 +280,19 @@ class UnitOfWorkTest extends OrmTestCase
|
||||
$user->username = 'John';
|
||||
$user->avatar = $invalidValue;
|
||||
|
||||
$this->expectException(ORMInvalidArgumentException::class);
|
||||
if (
|
||||
is_object($invalidValue) &&
|
||||
! $invalidValue instanceof ArrayCollection &&
|
||||
$this->_emMock->getConfiguration()->isNativeLazyObjectsEnabled()
|
||||
) {
|
||||
// in the case of stdClass, the changeset is rejected because
|
||||
// stdClass is not a valid entity
|
||||
// when using native lazy objects, this happens because UnitOfWork::isUninitializedObject()
|
||||
// needs to load the class metadata to do its job
|
||||
$this->expectException(MappingException::class);
|
||||
} else {
|
||||
$this->expectException(ORMInvalidArgumentException::class);
|
||||
}
|
||||
|
||||
$this->_unitOfWork->computeChangeSet($metadata, $user);
|
||||
}
|
||||
|
||||
@@ -941,6 +941,13 @@ abstract class OrmFunctionalTestCase extends OrmTestCase
|
||||
|
||||
$enableNativeLazyObjects = getenv('ENABLE_NATIVE_LAZY_OBJECTS');
|
||||
|
||||
if ($enableNativeLazyObjects === false) {
|
||||
// If the environment variable is not set, we default to true.
|
||||
// This is OK because environment variables are always strings, and
|
||||
// we are comparing it to a boolean.
|
||||
$enableNativeLazyObjects = true;
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID >= 80400 && $enableNativeLazyObjects) {
|
||||
$config->enableNativeLazyObjects(true);
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ use function class_exists;
|
||||
use function explode;
|
||||
use function fwrite;
|
||||
use function get_debug_type;
|
||||
use function getenv;
|
||||
use function in_array;
|
||||
use function sprintf;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
use const STDERR;
|
||||
|
||||
/**
|
||||
@@ -89,8 +91,23 @@ class TestUtil
|
||||
|
||||
public static function configureProxies(Configuration $configuration): void
|
||||
{
|
||||
$enableNativeLazyObjects = getenv('ENABLE_NATIVE_LAZY_OBJECTS');
|
||||
|
||||
if ($enableNativeLazyObjects === false) {
|
||||
// If the environment variable is not set, we default to true.
|
||||
// This is OK because environment variables are always strings, and
|
||||
// we are comparing it to a boolean.
|
||||
$enableNativeLazyObjects = true;
|
||||
}
|
||||
|
||||
$configuration->setProxyDir(__DIR__ . '/Proxies');
|
||||
$configuration->setProxyNamespace('Doctrine\Tests\Proxies');
|
||||
|
||||
if (PHP_VERSION_ID >= 80400 && $enableNativeLazyObjects) {
|
||||
$configuration->enableNativeLazyObjects(true);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static function initializeDatabase(): void
|
||||
|
||||
Reference in New Issue
Block a user