Compare commits

...

78 Commits
3.4.0 ... 3.5.0

Author SHA1 Message Date
Grégoire Paris
6deec3655b Merge pull request #12046 from greg0ire/3.5.x
Merge 3.4.x up into 3.5.x
2025-07-01 19:40:53 +02:00
Grégoire Paris
7f40422d21 Merge remote-tracking branch 'origin/3.4.x' into 3.5.x 2025-07-01 19:13:12 +02:00
Grégoire Paris
e67fa5388b Merge pull request #12043 from beberlei/Bugfix-DisableNativeLazyLogicException
Only throw PHP 8.4 requirement exception when enabling native lazy objects.
2025-06-30 23:43:15 +02:00
Grégoire Paris
80053336c9 Merge pull request #12044 from doctrine/3.4.x
Merge branch 3.4.x into 3.5.x
2025-06-30 21:53:04 +02:00
Grégoire Paris
dddcc507ef Merge pull request #12039 from xabbuh/pr-12036
do not register the legacy proxy class name resolver with enabled native lazy ghost
2025-06-30 20:38:34 +02:00
Christian Flothmann
b41d9da88d do not register the legacy proxy class name resolver with enabled native lazy ghost 2025-06-30 19:14:11 +02:00
Benjamin Eberlei
c04bfb78b7 Only throw PHP 8.4 requirement exception when enabling native lazy objects. 2025-06-30 19:01:49 +02:00
Grégoire Paris
8a5dfc86d4 Merge pull request #12037 from stlgaits/mapping-describe-completion
Add console completion for entityName param of orm:mapping:describe c…
2025-06-29 18:26:55 +02:00
Grégoire Paris
79e103c07e Merge pull request #11978 from Ocramius/feature/#11977-batch-handling-of-inserts-with-given-ids
#11977 implemented batching of `INSERT` operations in `UnitOfWork#executeInserts()` so that `EntityPersister#executeInserts()` calls are reduced
2025-06-28 22:19:54 +02:00
stlgaits
5afadf163a Add console completion for entityName param of orm:mapping:describe command 2025-06-28 11:27:52 +02:00
Grégoire Paris
edfaa37228 Merge pull request #12036 from greg0ire/depr-proxy-autoload
Deprecate proxy autoloader and class name resolver
2025-06-28 11:09:37 +02:00
Grégoire Paris
ea056e98ba Deprecate proxy autoloader and class name resolver
These are only needed when not using native lazy objects.
2025-06-27 19:24:23 +02:00
Grégoire Paris
bab5771e98 Merge pull request #12034 from doctrine/3.4.x
Merge 3.4.x up into 3.5.x
2025-06-27 18:32:26 +02:00
Grégoire Paris
ee919d6231 Merge pull request #12030 from greg0ire/test-w-lazy-o
Rework tests and benchmarks to work with lazy objects
2025-06-27 18:13:24 +02:00
Grégoire Paris
04c390693a Merge pull request #12033 from greg0ire/remove-assert
Remove wrong assertion
2025-06-27 15:55:40 +02:00
Grégoire Paris
49293c4d48 Merge pull request #12032 from doctrine/3.4.x-merge-up-into-3.5.x_dG7qI4BR
Merge release 3.4.3 into 3.5.x
2025-06-27 14:44:44 +02:00
Gregoire PARIS
8d9e2e7d4e Remove wrong assertion
When using native lazy objects, it is plain wrong.
2025-06-27 14:42:37 +02:00
Grégoire Paris
ef607f26c2 Merge pull request #12031 from doctrine/stof-patch-1
Clean the handling of proxy initialization in the UnitOfWork
2025-06-27 14:14:15 +02:00
Gregoire PARIS
ed543a205c Rework tests and benchmarks to work with lazy objects
These tests and benchmarks are still relevant with lazy objects.
I am not setting up an extra job to test phpbench without native lazy
objects. Instead, I'm bumping the PHP version to 8.4 so that native lazy
objects are in use.
2025-06-27 14:12:58 +02:00
Christophe Coevoet
de1c28bb16 Clean the handling of proxy initialization in the UnitOfWork
Using the VarExporter Hydrator to assign default values of properties when marking an entity as initialized is needed only when using var-exporter proxies.
For lazy objects, this behavior is already provided by `ReflectionClass::markLazyObjectAsInitialized`
2025-06-27 13:58:03 +02:00
Grégoire Paris
60ff966d54 Merge pull request #12022 from greg0ire/depr-proxy-dir
Provide upgrade path to new ORMSetup::create* signature
2025-06-27 08:12:42 +02:00
Grégoire Paris
33684253c3 Merge pull request #12026 from doctrine/3.4.x-merge-up-into-3.5.x_yt3lc4tn
Merge release 3.4.2 into 3.5.x
2025-06-26 21:07:39 +02:00
Grégoire Paris
b4ca0cd5fb Merge pull request #12024 from greg0ire/3.4.x
Merge 2.20.x up into 3.4.x
2025-06-26 20:51:01 +02:00
Grégoire Paris
a49c1beb93 Merge remote-tracking branch 'origin/2.20.x' into 3.4.x 2025-06-26 20:38:31 +02:00
Grégoire Paris
76852cfef3 Provide upgrade path to new ORMSetup::create* signature
Currently we have ORMSetup::create*Configuration methods with a
$proxyDir argument that is used to configure the proxy directory, but
also as a seed for generating a namespace for cache systems.

Since these methods could be used with named arguments, renaming the
argument is not really an option and we need separate methods.
2025-06-26 00:14:59 +02:00
Grégoire Paris
3bd89caf36 use lowercase for word in upgrade guide 2025-06-25 23:27:18 +02:00
Grégoire Paris
eb2e7d959c Merge pull request #12020 from greg0ire/depr-legacy-proxy
Deprecate more proxies-related methods or calls
2025-06-25 23:17:56 +02:00
Grégoire Paris
a4b20356f4 Merge pull request #11988 from jannes-io/3.4.x
Add index mapping to column
2025-06-25 11:17:22 +02:00
Grégoire Paris
2550b2d1de Deprecate more proxies-related methods or calls 2025-06-25 00:04:27 +02:00
jannes
e94e1ab126 Add index mapping to Column
Adds a new option to Column mapping to add indexes to class fields
directly instead of having to use the Index() class attribute.
This allows users to define indexes in traits
where access to the class isn't available.
Fixes #11982
2025-06-24 19:50:47 +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
19e1a64a91 Merge pull request #12014 from greg0ire/3.5.x
Merge 3.4.1 up into 3.5.x
2025-06-21 13:57:03 +02:00
Grégoire Paris
082e776e91 Merge remote-tracking branch 'origin/3.4.x' into 3.5.x 2025-06-21 13:14:14 +02:00
Grégoire Paris
92e2f6db83 Merge pull request #12012 from greg0ire/revert-allfields-dto
Revert "add capability to use allfields sql notation"
2025-06-21 12:44:26 +02:00
Grégoire Paris
aa624f64c1 Remove trailing whitespace 2025-06-21 11:58:51 +02:00
Grégoire Paris
e1675eb371 Revert "add capability to use allfields sql notation"
This reverts commit 12c721f528.

This feature introduces several issues:

- It adds alias.*, which is a first, for instance you cannot do
SELECT u.* FROM User u
- If introduces coupling between property order in mapping fields and
  the result.
2025-06-21 11:58:42 +02:00
Grégoire Paris
cc2b6385a1 Merge pull request #12011 from greg0ire/3.4.x
Merge 2.20.x up into 3.4.x
2025-06-21 11:48:10 +02:00
Grégoire Paris
a64bed9bbb Merge remote-tracking branch 'origin/2.20.x' into 3.4.x 2025-06-21 11:11:52 +02:00
Grégoire Paris
3272e1c0af Merge pull request #12008 from greg0ire/add-test-to-todo-list
Ensure proxies implementations behave the same on entity not found
2025-06-20 00:24:56 +02:00
Grégoire Paris
69da22d517 Ensure proxies implementations behave the same on entity not found
Both implementations are supposed to throw EntityNotFoundException
2025-06-19 10:07:06 +02:00
Grégoire Paris
06109f360f Merge pull request #12002 from greg0ire/relax-type-declarations
Make proxyDir and proxyNs nullable and optional
2025-06-19 08:00:18 +02:00
Grégoire Paris
06a9ef1127 Make proxyDir and proxyNs nullable and optional
When using native lazy objects, it should be possible to omit these
arguments, hence the default value.
Also, when using native lazy objects, one should not have to configure
the corresponding Configuration attributes, which means
EntityManager__construct() should be able to pass null to this class,
hence the nullability.

Fixes #11997
2025-06-18 23:23:30 +02:00
Alexander M. Turek
5d21bb158b Fix calls to Application::add() (#12006) 2025-06-18 08:58:26 +02:00
Grégoire Paris
bbde41f712 Merge pull request #12005 from greg0ire/depr-no-lazy-objects
Deprecate not using native lazy objects on PHP 8.4+
2025-06-18 07:24:56 +02:00
Alexander M. Turek
8c0994f35f Detect DBAL's number type (#11781) 2025-06-18 02:43:47 +02:00
Grégoire Paris
3d390bc053 Deprecate not using native lazy objects on PHP 8.4+ 2025-06-18 00:14:15 +02:00
Grégoire Paris
16f1be7f10 Merge pull request #12004 from doctrine/3.4.x
Merge 3.4.x up into 3.5.x
2025-06-18 00:12:23 +02:00
Grégoire Paris
c74df3fab3 Merge pull request #12001 from greg0ire/lazy-objects-by-default
Enable native lazy objects by default
2025-06-17 23:50:46 +02:00
Grégoire Paris
f2c902ee03 Rewrite test with native lazy ghost
I do not think this needs to be tested on all versions of PHP, using
native lazy objects allows us to remove a deprecation.
2025-06-17 23:35:47 +02:00
Grégoire Paris
4e5e3c5e50 Enable native lazy objects by default
This should make the test suite look less like a christmas tree.
2025-06-17 23:09:48 +02:00
Grégoire Paris
da697f218f Merge pull request #12000 from greg0ire/fix-var-name
Use the correct environment variable name for lazy objects and enable them by default
2025-06-17 21:15:38 +02:00
Grégoire Paris
4f47a80deb Use the correct environment variable name for lazy objects
The test suite checks for ENABLE_NATIVE_LAZY_OBJECTS
I have also renamed the matrix variable for the sake of consistency.
2025-06-17 08:35:16 +02:00
Grégoire Paris
1334162a56 Merge pull request #11989 from greg0ire/late-depr
Deprecate methods for configuring no longer configurable features
2025-06-16 08:37:59 +02:00
Grégoire Paris
ab89517093 Merge pull request #11987 from greg0ire/update-branch-metadata
Update branch metadata
2025-06-16 08:37:20 +02:00
Grégoire Paris
48a51d8470 Merge pull request #11992 from eltharin/error_doc_codeblock
repair code block bad showing
2025-06-16 08:36:13 +02:00
eltharin
ab11244f08 repair code block bad showing 2025-06-16 08:03:47 +02:00
Grégoire Paris
68ec3ebaa3 Remove trailing whitespace 2025-06-14 18:14:58 +02:00
Grégoire Paris
4f4ed2f242 Deprecate methods for configuring no longer configurable features
In 3.0.0, it is no longer possible to disable lazy ghost objects, and
likewise, it is no longer possible to disable rejecting id collisions in
the identity map, so let us deprecate the related methods.
I was supposed to do this in 3.1.0.
2025-06-14 18:14:54 +02:00
Grégoire Paris
a1c2be140d Update branch metadata
- 3.5.x has been created
- 3.4.0 has been released
- 3.3.x is no longer maintained
2025-06-14 13:50:55 +02:00
Marco Pivetta
79cc70a62f #11977 expanded test coverage to check interleaved assigned-id vs generated-id entities
As noted by @bendavies

Ref: https://github.com/doctrine/orm/pull/11978#discussion_r2141143273
2025-06-11 23:44:24 +02:00
Marco Pivetta
4e6b5a1b0b #11977 provided method documentation / example, as per @greg0ire's feedback
Ref: https://github.com/doctrine/orm/pull/11978#discussion_r2140881217
2025-06-11 22:53:22 +02:00
Marco Pivetta
21b144fff9 #11977 removed unused type-hint, which can be completely inferred by the parameters 2025-06-11 22:50:15 +02:00
Marco Pivetta
658940de38 #11977 only perform batching if/when the AssignedGenerator is in use
The `SequenceGenerator` is potentially used for PostgreSQL table auto-generated fields, but
the `SequenceGenerator` is not a **POST**-insert generator.

Because the `SequenceGenerator` is used in the middle of `INSERT` operations performed
by persisters, we cannot rely on it in batching operations: disabling it, so we get a green
test suite on PostgreSQL.

This change makes `GH10531Test` pass on PostgreSQL: see #10531
2025-06-11 18:26:20 +02:00
Marco Pivetta
ad487370f5 #11977 hardened InsertBatchTest to check entity types of sequential batches 2025-06-11 18:16:45 +02:00
Marco Pivetta
259f83b549 #11977 added test coverage verifying that persisters are being used to batch INSERTs 2025-06-11 17:36:24 +02:00
Marco Pivetta
4a24860dcf #11977 isolated INSERT batch generation to own @internal performance-sensitive class 2025-06-11 17:18:23 +02:00
Marco Pivetta
116cdf8661 #11977 implemented simplistic (and ugly) batch handing of INSERT operations in UnitOfWork#executeInserts()
This logic also brings a minor benefit in reducing the number of times `ListenersInvoker#getSubscribedSystems`
is queried.

TODOs:

* [ ] integration test this - it is expected to reduce the number of `EntityPersister#executeInserts()` calls
* [ ] refactor this by creating a new `@internal` class for the batch, and perhaps batch via a generator
* [ ] reduce amount of repeated `getClassMetadata()` calls
* [ ] reduce overall size of `UnitOfWork` code, instead of increasing it
2025-06-11 15:43:04 +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
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
71 changed files with 1384 additions and 594 deletions

View File

@@ -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",

View File

@@ -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"

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.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

View File

@@ -1,3 +1,56 @@
# Upgrade to 3.5
## Deprecate not using native lazy objects on PHP 8.4+
Having native lazy objects disabled on PHP 8.4+ is deprecated and will not be
possible in 4.0.
You can enable them through configuration:
```php
$config->enableNativeLazyObjects(true);
```
As a consequence, methods, parameters and commands related to userland lazy
objects have been deprecated on PHP 8.4+:
- `Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand`
- `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()`
- `Doctrine\ORM\Configuration::getProxyDir()`
- `Doctrine\ORM\Configuration::getProxyNamespace()`
- `Doctrine\ORM\Configuration::setAutoGenerateProxyClasses()`
- `Doctrine\ORM\Configuration::setProxyDir()`
- `Doctrine\ORM\Configuration::setProxyNamespace()`
- Passing more than one argument to `Doctrine\ORM\Proxy\ProxyFactory::__construct()`
Additionally, some methods of ORMSetup have been deprecated in favor of a new
counterpart.
- `Doctrine\ORM\ORMSetup::createAttributeMetadataConfiguration()` is deprecated in favor of
`Doctrine\ORM\ORMSetup::createAttributeMetadataConfig()`
- `Doctrine\ORM\ORMSetup::createXMLMetadataConfiguration()` is deprecated in favor of
`Doctrine\ORM\ORMSetup::createXMLMetadataConfig()`
- `Doctrine\ORM\ORMSetup::createConfiguration()` is deprecated in favor of
`Doctrine\ORM\ORMSetup::createConfig()`
## Deprecate methods for configuring no longer configurable features
Since 3.0, lazy ghosts are enabled unconditionally, and so is rejecting ID
collisions in the identity map.
As a consequence, the following methods are deprecated and will be removed in 4.0:
* `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()`
* `Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()`
# 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 +79,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 +190,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.

View File

@@ -175,6 +175,10 @@ Optional parameters:
- **unique**: Boolean value to determine if the value of the column
should be unique across all rows of the underlying entities table.
- **index**: Boolean value to generate an index for this column.
For more advanced usages, take a look at :ref:`#[Index] <attrref_index>`.
If not specified, default value is ``false``.
- **nullable**: Determines if NULL values allowed for this column.
If not specified, default value is ``false``.
@@ -245,6 +249,9 @@ Examples:
#[Column(type: "string", length: 32, unique: true, nullable: false)]
protected $username;
#[Column(type: "string", index: true)]
protected $firstName;
#[Column(type: "string", columnDefinition: "CHAR(2) NOT NULL")]
protected $country;

View File

@@ -56,7 +56,8 @@ access point to ORM functionality provided by Doctrine.
'dbname' => 'foo',
];
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
$config = ORMSetup::createAttributeMetadataConfig($paths, $isDevMode);
// on PHP < 8.4, use ORMSetup::createAttributeMetadataConfiguration() instead
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
@@ -66,7 +67,8 @@ Or if you prefer XML:
<?php
$paths = ['/path/to/xml-mappings'];
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
$config = ORMSetup::createXMLMetadataConfig($paths, $isDevMode);
// on PHP < 8.4, use ORMSetup::createXMLMetadataConfiguration() instead
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);

View File

@@ -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
~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -209,6 +209,7 @@ Field & Association Getters
- ``isUniqueField($fieldName)``
- ``isNullable($fieldName)``
- ``isIndexed($fieldName)``
- ``getColumnName($fieldName)``
- ``getFieldMapping($fieldName)``
- ``getAssociationMapping($fieldName)``

View File

@@ -84,7 +84,7 @@ The following Commands are currently available:
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes.
classes. Deprecated in favor of using native lazy objects.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either

View File

@@ -112,7 +112,6 @@ of several common elements:
<indexes>
<index name="name_idx" columns="name"/>
<index columns="user_email"/>
</indexes>
<unique-constraints>
@@ -131,7 +130,7 @@ of several common elements:
</id>
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
<field name="email" column="user_email" type="string" index="true" column-definition="CHAR(32) NOT NULL" />
<one-to-one field="address" target-entity="Address" inversed-by="user">
<cascade><cascade-remove /></cascade>
@@ -255,6 +254,8 @@ Optional attributes:
only.
- unique - Should this field contain a unique value across the
table? Defaults to false.
- index - Should an index be created for this column? Defaults to
false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- insertable - Should this field be inserted? Defaults to true.

View File

@@ -138,12 +138,12 @@ step:
require_once "vendor/autoload.php";
// Create a simple "default" Doctrine ORM configuration for Attributes
$config = ORMSetup::createAttributeMetadataConfiguration(
$config = ORMSetup::createAttributeMetadataConfig( // on PHP < 8.4, use ORMSetup::createAttributeMetadataConfiguration()
paths: [__DIR__ . '/src'],
isDevMode: true,
);
// or if you prefer XML
// $config = ORMSetup::createXMLMetadataConfiguration(
// $config = ORMSetup::createXMLMetadataConfig( // on PHP < 8.4, use ORMSetup::createXMLMetadataConfiguration()
// paths: [__DIR__ . '/config/xml'],
// isDevMode: true,
//);

View File

@@ -243,6 +243,7 @@
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="false" />
<xs:attribute name="index" type="xs:boolean" default="false" />
<xs:attribute name="insertable" type="xs:boolean" default="true" />
<xs:attribute name="updatable" type="xs:boolean" default="true" />
<xs:attribute name="generated" type="orm:generated-type" default="NEVER" />

View File

@@ -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

View File

@@ -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
-

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Exception\InvalidEntityRepository;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
@@ -63,6 +64,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setProxyDir(string $dir): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['proxyDir'] = $dir;
}
@@ -71,6 +81,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getProxyDir(): string|null
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['proxyDir'] ?? null;
}
@@ -81,6 +100,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getAutoGenerateProxyClasses(): int
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
}
@@ -91,6 +119,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
}
@@ -99,6 +136,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getProxyNamespace(): string|null
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['proxyNamespace'] ?? null;
}
@@ -107,6 +153,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setProxyNamespace(string $ns): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['proxyNamespace'] = $ns;
}
@@ -597,12 +652,30 @@ class Configuration extends \Doctrine\DBAL\Configuration
public function isNativeLazyObjectsEnabled(): bool
{
return $this->attributes['nativeLazyObjects'] ?? false;
$nativeLazyObjects = $this->attributes['nativeLazyObjects'] ?? false;
if (! $nativeLazyObjects && PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Not enabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
);
}
return $nativeLazyObjects;
}
public function enableNativeLazyObjects(bool $nativeLazyObjects): void
{
if (PHP_VERSION_ID < 80400) {
if (PHP_VERSION_ID >= 80400 && ! $nativeLazyObjects) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Disabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
);
}
if (PHP_VERSION_ID < 80400 && $nativeLazyObjects) {
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
}
@@ -610,7 +683,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* To be deprecated in 3.1.0
* @deprecated lazy ghost objects are always enabled
*
* @return true
*/
@@ -619,7 +692,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
return true;
}
/** To be deprecated in 3.1.0 */
/** @deprecated lazy ghost objects cannot be disabled */
public function setLazyGhostObjectEnabled(bool $flag): void
{
if (! $flag) {
@@ -630,7 +703,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
}
/** To be deprecated in 3.1.0 */
/** @deprecated rejecting ID collisions in the identity map cannot be disabled */
public function setRejectIdCollisionInIdentityMap(bool $flag): void
{
if (! $flag) {
@@ -642,7 +715,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
/**
* To be deprecated in 3.1.0
* @deprecated rejecting ID collisions in the identity map is always enabled
*
* @return true
*/

View File

@@ -43,7 +43,7 @@ use function method_exists;
*
* $paths = ['/path/to/entity/mapping/files'];
*
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
* $config = ORMSetup::createAttributeMetadataConfig($paths);
* $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
* $entityManager = new EntityManager($connection, $config);
*
@@ -134,12 +134,16 @@ class EntityManager implements EntityManagerInterface
$this->repositoryFactory = $config->getRepositoryFactory();
$this->unitOfWork = new UnitOfWork($this);
$this->proxyFactory = new ProxyFactory(
$this,
$config->getProxyDir(),
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses(),
);
if ($config->isNativeLazyObjectsEnabled()) {
$this->proxyFactory = new ProxyFactory($this);
} else {
$this->proxyFactory = new ProxyFactory(
$this,
$config->getProxyDir(),
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses(),
);
}
if ($config->isSecondLevelCacheEnabled()) {
$cacheConfig = $config->getSecondLevelCacheConfiguration();

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal\UnitOfWork;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Id\AssignedGenerator;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* An {@see InsertBatch} represents a set of entities that are safe to be batched
* together in a single query.
*
* These entities are only those that have all fields already assigned, including the
* identifier field(s).
*
* This data structure only exists for internal {@see UnitOfWork} optimisations, and
* should not be relied upon outside the ORM.
*
* @internal
*
* @template TEntity of object
*/
final class InsertBatch
{
/**
* @param ClassMetadata<TEntity> $class
* @param non-empty-list<TEntity> $entities
*/
public function __construct(
public readonly ClassMetadata $class,
public array $entities,
) {
}
/**
* Note: Code in here is procedural/ugly due to it being in a hot path of the {@see UnitOfWork}
*
* This method will batch the given entity set by type, preserving their order. For example,
* given an input [A1, A2, A3, B1, B2, A4, A5], it will create an [[A1, A2, A3], [B1, B2], [A4, A5]] batch.
*
* Entities for which the identifier needs to be generated or fetched by a sequence are put as single
* items in a batch of their own, since it is unsafe to batch-insert them.
*
* @param list<TEntities> $entities
*
* @return list<self<TEntities>>
*
* @template TEntities of object
*/
public static function batchByEntityType(
EntityManagerInterface $entityManager,
array $entities,
): array {
$currentClass = null;
$batches = [];
$batchIndex = -1;
foreach ($entities as $entity) {
$entityClass = $entityManager->getClassMetadata($entity::class);
if (
$currentClass?->name !== $entityClass->name
|| ! $entityClass->idGenerator instanceof AssignedGenerator
) {
$currentClass = $entityClass;
$batches[] = new InsertBatch($entityClass, [$entity]);
$batchIndex += 1;
continue;
}
$batches[$batchIndex]->entities[] = $entity;
}
return $batches;
}
}

View File

@@ -64,6 +64,18 @@ class FieldBuilder
return $this;
}
/**
* Sets indexed.
*
* @return $this
*/
public function index(bool $flag = true): static
{
$this->mapping['index'] = $flag;
return $this;
}
/**
* Sets column name.
*

View File

@@ -1056,6 +1056,13 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
return $mapping !== false && isset($mapping->nullable) && $mapping->nullable;
}
public function isIndexed(string $fieldName): bool
{
$mapping = $this->getFieldMapping($fieldName);
return isset($mapping->index) && $mapping->index;
}
/**
* Gets a column name for a field name.
* If the column name for the field cannot be found, the given field name

View File

@@ -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;
}

View File

@@ -31,6 +31,7 @@ final class Column implements MappingAttribute
public readonly array $options = [],
public readonly string|null $columnDefinition = null,
public readonly string|null $generated = null,
public readonly bool $index = false,
) {
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use BackedEnum;
use BcMath\Number;
use DateInterval;
use DateTime;
use DateTimeImmutable;
@@ -40,7 +41,12 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
/** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
public function __construct(array $typedFieldMappings = [])
{
$this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings);
$defaultMappings = self::DEFAULT_TYPED_FIELD_MAPPINGS;
if (defined(Types::class . '::NUMBER')) { // DBAL 4.3+
$defaultMappings[Number::class] = Types::NUMBER;
}
$this->typedFieldMappings = array_merge($defaultMappings, $typedFieldMappings);
}
/**

View File

@@ -710,6 +710,7 @@ class AttributeDriver implements MappingDriver
* length: int,
* unique: bool,
* nullable: bool,
* index: bool,
* precision: int,
* enumType?: class-string,
* options?: mixed[],
@@ -726,6 +727,7 @@ class AttributeDriver implements MappingDriver
'length' => $column->length,
'unique' => $column->unique,
'nullable' => $column->nullable,
'index' => $column->index,
'precision' => $column->precision,
];

View File

@@ -754,6 +754,7 @@ class XmlDriver extends FileDriver
* scale?: int,
* unique?: bool,
* nullable?: bool,
* index?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* enumType?: string,
@@ -792,6 +793,10 @@ class XmlDriver extends FileDriver
$mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']);
}
if (isset($fieldMapping['index'])) {
$mapping['index'] = $this->evaluateBoolean($fieldMapping['index']);
}
if (isset($fieldMapping['nullable'])) {
$mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']);
}

View File

@@ -42,6 +42,8 @@ final class FieldMapping implements ArrayAccess
public int|null $scale = null;
/** Whether a unique constraint should be generated for the column. */
public bool|null $unique = null;
/** Whether an index should be generated for the column. */
public bool|null $index = null;
/**
* @var class-string|null This is set when the field is inherited by this
* class from another (inheritance) parent <em>entity</em> class. The value
@@ -93,6 +95,7 @@ final class FieldMapping implements ArrayAccess
* length?: int|null,
* id?: bool|null,
* nullable?: bool|null,
* index?: bool|null,
* notInsertable?: bool|null,
* notUpdatable?: bool|null,
* columnDefinition?: string|null,

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\Mapping\Driver\XmlDriver;
use Psr\Cache\CacheItemPoolInterface;
@@ -20,6 +21,8 @@ use function extension_loaded;
use function md5;
use function sys_get_temp_dir;
use const PHP_VERSION_ID;
final class ORMSetup
{
/**
@@ -33,12 +36,39 @@ final class ORMSetup
string|null $proxyDir = null,
CacheItemPoolInterface|null $cache = null,
): Configuration {
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'%s is deprecated in favor of %s, and will be removed in 4.0.',
__METHOD__,
self::class . '::createAttributeMetadataConfig()',
);
}
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl(new AttributeDriver($paths));
return $config;
}
/**
* Creates a configuration with an attribute metadata driver.
*
* @param string[] $paths
*/
public static function createAttributeMetadataConfig(
array $paths,
bool $isDevMode = false,
string|null $cacheNamespaceSeed = null,
CacheItemPoolInterface|null $cache = null,
): Configuration {
$config = self::createConfig($isDevMode, $cacheNamespaceSeed, $cache);
$config->setMetadataDriverImpl(new AttributeDriver($paths));
return $config;
}
/**
* Creates a configuration with an XML metadata driver.
*
@@ -51,12 +81,44 @@ final class ORMSetup
CacheItemPoolInterface|null $cache = null,
bool $isXsdValidationEnabled = true,
): Configuration {
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'%s is deprecated in favor of %s, and will be removed in 4.0.',
__METHOD__,
self::class . '::createXMLMetadataConfig()',
);
}
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
$config->setMetadataDriverImpl(new XmlDriver($paths, XmlDriver::DEFAULT_FILE_EXTENSION, $isXsdValidationEnabled));
return $config;
}
/**
* Creates a configuration with an XML metadata driver.
*
* @param string[] $paths
*/
public static function createXMLMetadataConfig(
array $paths,
bool $isDevMode = false,
string|null $cacheNamespaceSeed = null,
CacheItemPoolInterface|null $cache = null,
bool $isXsdValidationEnabled = true,
): Configuration {
$config = self::createConfig($isDevMode, $cacheNamespaceSeed, $cache);
$config->setMetadataDriverImpl(new XmlDriver(
$paths,
XmlDriver::DEFAULT_FILE_EXTENSION,
$isXsdValidationEnabled,
));
return $config;
}
/**
* Creates a configuration without a metadata driver.
*/
@@ -65,6 +127,16 @@ final class ORMSetup
string|null $proxyDir = null,
CacheItemPoolInterface|null $cache = null,
): Configuration {
if (PHP_VERSION_ID >= 80400 && $proxyDir !== null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'%s is deprecated in favor of %s, and will be removed in 4.0.',
__METHOD__,
self::class . '::createConfig()',
);
}
$proxyDir = $proxyDir ?: sys_get_temp_dir();
$cache = self::createCacheInstance($isDevMode, $proxyDir, $cache);
@@ -81,9 +153,23 @@ final class ORMSetup
return $config;
}
public static function createConfig(
bool $isDevMode = false,
string|null $cacheNamespaceSeed = null,
CacheItemPoolInterface|null $cache = null,
): Configuration {
$cache = self::createCacheInstance($isDevMode, $cacheNamespaceSeed, $cache);
$config = new Configuration();
$config->setMetadataCache($cache);
$config->setQueryCache($cache);
$config->setResultCache($cache);
return $config;
}
private static function createCacheInstance(
bool $isDevMode,
string $proxyDir,
string|null $cacheNamespaceSeed,
CacheItemPoolInterface|null $cache,
): CacheItemPoolInterface {
if ($cache !== null) {
@@ -101,7 +187,7 @@ final class ORMSetup
return new ArrayAdapter();
}
$namespace = 'dc2_' . md5($proxyDir);
$namespace = 'dc2_' . md5($cacheNamespaceSeed ?? 'default');
if (extension_loaded('apcu') && apcu_enabled()) {
return new ApcuAdapter($namespace);

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Proxy;
use Closure;
use Doctrine\Deprecations\Deprecation;
use function file_exists;
use function ltrim;
@@ -15,6 +16,7 @@ use function strlen;
use function substr;
use const DIRECTORY_SEPARATOR;
use const PHP_VERSION_ID;
/**
* Special Autoloader for Proxy classes, which are not PSR-0 compliant.
@@ -34,6 +36,15 @@ final class Autoloader
*/
public static function resolveFile(string $proxyDir, string $proxyNamespace, string $className): string
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Class "%s" is deprecated. Use native lazy objects instead.',
self::class,
);
}
if (! str_starts_with($className, $proxyNamespace)) {
throw new NotAProxyClass($className, $proxyNamespace);
}
@@ -59,6 +70,15 @@ final class Autoloader
string $proxyNamespace,
Closure|null $notFoundCallback = null,
): Closure {
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Class "%s" is deprecated. Use native lazy objects instead.',
self::class,
);
}
$proxyNamespace = ltrim($proxyNamespace, '\\');
$autoloader = /** @param class-string $className */ static function (string $className) use ($proxyDir, $proxyNamespace, $notFoundCallback): void {

View File

@@ -4,12 +4,15 @@ declare(strict_types=1);
namespace Doctrine\ORM\Proxy;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Persistence\Mapping\ProxyClassNameResolver;
use Doctrine\Persistence\Proxy;
use function strrpos;
use function substr;
use const PHP_VERSION_ID;
/**
* Class-related functionality for objects that might or not be proxy objects
* at the moment.
@@ -18,6 +21,15 @@ final class DefaultProxyClassNameResolver implements ProxyClassNameResolver
{
public function resolveClassName(string $className): string
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Class "%s" is deprecated. Use native lazy objects instead.',
self::class,
);
}
$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
if ($pos === false) {
@@ -30,6 +42,15 @@ final class DefaultProxyClassNameResolver implements ProxyClassNameResolver
/** @return class-string */
public static function getClass(object $object): string
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Class "%s" is deprecated. Use native lazy objects instead.',
self::class,
);
}
return (new self())->resolveClassName($object::class);
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Proxy;
use Closure;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityNotFoundException;
use Doctrine\ORM\ORMInvalidArgumentException;
@@ -30,6 +31,7 @@ use function dirname;
use function file_exists;
use function file_put_contents;
use function filemtime;
use function func_num_args;
use function is_bool;
use function is_dir;
use function is_int;
@@ -132,6 +134,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,10 +148,19 @@ 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 (PHP_VERSION_ID >= 80400 && func_num_args() > 1) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Passing more than just the EntityManager to the %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
if (! $proxyDir && ! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
throw ORMInvalidArgumentException::proxyDirectoryRequired();
}
@@ -159,6 +173,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 +196,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) {

View File

@@ -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);
}
}

View File

@@ -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)
{
}

View File

@@ -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();
}

View File

@@ -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
*

View 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);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Tools\Console\MetadataFilter;
use InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
@@ -19,6 +20,8 @@ use function mkdir;
use function realpath;
use function sprintf;
use const PHP_VERSION_ID;
/**
* Command to (re)generate the proxy classes used by doctrine.
*
@@ -39,6 +42,14 @@ class GenerateProxiesCommand extends AbstractEntityManagerCommand
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Generating proxies is deprecated and will be impossible in Doctrine ORM 4.0.',
);
}
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();
$em = $this->getEntityManager($input);

View File

@@ -10,6 +10,8 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\FieldMapping;
use Doctrine\Persistence\Mapping\MappingException;
use InvalidArgumentException;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -19,6 +21,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
use function array_filter;
use function array_map;
use function array_merge;
use function array_values;
use function count;
use function current;
use function get_debug_type;
@@ -32,6 +35,7 @@ use function preg_match;
use function preg_quote;
use function print_r;
use function sprintf;
use function str_replace;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
@@ -73,6 +77,20 @@ EOT);
return 0;
}
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('entityName')) {
$entityManager = $this->getEntityManager($input);
$entities = array_map(
static fn (string $fqcn) => str_replace('\\', '\\\\', $fqcn),
$this->getMappedEntities($entityManager),
);
$suggestions->suggestValues(array_values($entities));
}
}
/**
* Display all the mapping information for a single Entity.
*

View File

@@ -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(

View File

@@ -505,6 +505,11 @@ class SchemaTool
if ($isUnique) {
$table->addUniqueIndex([$columnName]);
}
$isIndex = $mapping->index ?? false;
if ($isIndex) {
$table->addIndex([$columnName]);
}
}
/**

View File

@@ -31,6 +31,7 @@ use Doctrine\ORM\Id\AssignedGenerator;
use Doctrine\ORM\Internal\HydrationCompleteHandler;
use Doctrine\ORM\Internal\StronglyConnectedComponents;
use Doctrine\ORM\Internal\TopologicalSort;
use Doctrine\ORM\Internal\UnitOfWork\InsertBatch;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
@@ -1037,30 +1038,36 @@ class UnitOfWork implements PropertyChangedListener
*/
private function executeInserts(): void
{
$entities = $this->computeInsertExecutionOrder();
$batchedByType = InsertBatch::batchByEntityType($this->em, $this->computeInsertExecutionOrder());
$eventsToDispatch = [];
foreach ($entities as $entity) {
$oid = spl_object_id($entity);
$class = $this->em->getClassMetadata($entity::class);
foreach ($batchedByType as $batch) {
$class = $batch->class;
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
$persister = $this->getEntityPersister($class->name);
$persister->addInsert($entity);
foreach ($batch->entities as $entity) {
$oid = spl_object_id($entity);
unset($this->entityInsertions[$oid]);
$persister->addInsert($entity);
unset($this->entityInsertions[$oid]);
}
$persister->executeInserts();
if (! isset($this->entityIdentifiers[$oid])) {
//entity was not added to identity map because some identifiers are foreign keys to new entities.
//add it now
$this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity);
}
foreach ($batch->entities as $entity) {
$oid = spl_object_id($entity);
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
if (! isset($this->entityIdentifiers[$oid])) {
//entity was not added to identity map because some identifiers are foreign keys to new entities.
//add it now
$this->addToEntityIdentifiersAndEntityMap($class, $oid, $entity);
}
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
$eventsToDispatch[] = ['class' => $class, 'entity' => $entity, 'invoke' => $invoke];
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
$eventsToDispatch[] = ['class' => $class, 'entity' => $entity, 'invoke' => $invoke];
}
}
}
@@ -2383,9 +2390,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 +3057,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);
}

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 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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -13,6 +13,8 @@ use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
*/
class EntityPersisterMock extends BasicEntityPersister
{
/** @var int<0, max> */
private int $countOfExecuteInsertCalls = 0;
private array $inserts = [];
private array $updates = [];
private array $deletes = [];
@@ -40,6 +42,8 @@ class EntityPersisterMock extends BasicEntityPersister
public function executeInserts(): void
{
$this->countOfExecuteInsertCalls += 1;
foreach ($this->postInsertIds as $item) {
$this->em->getUnitOfWork()->assignPostInsertId($item['entity'], $item['generatedId']);
}
@@ -86,6 +90,7 @@ class EntityPersisterMock extends BasicEntityPersister
public function reset(): void
{
$this->countOfExecuteInsertCalls = 0;
$this->existsCalled = false;
$this->identityColumnValueCounter = 0;
$this->inserts = [];
@@ -97,4 +102,10 @@ class EntityPersisterMock extends BasicEntityPersister
{
return $this->existsCalled;
}
/** @return int<0, max> */
public function countOfExecuteInsertCalls(): int
{
return $this->countOfExecuteInsertCalls;
}
}

View File

@@ -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;
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\Models\TypedProperties;
use BcMath\Number;
use DateInterval;
use DateTime;
use DateTimeImmutable;
@@ -54,6 +55,9 @@ class UserTyped
#[ORM\Embedded]
public Contact|null $contact = null;
#[ORM\Column(precision: 5, scale: 2)]
public Number|null $bodyHeight = null;
public static function loadMetadata(ClassMetadata $metadata): void
{
$metadata->setInheritanceType(ClassMetadata::INHERITANCE_TYPE_NONE);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityRepository;
@@ -17,6 +18,8 @@ use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Tests\Models\DDC753\DDC753CustomRepository;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RequiresPhp;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemPoolInterface;
@@ -25,6 +28,8 @@ use Psr\Cache\CacheItemPoolInterface;
*/
class ConfigurationTest extends TestCase
{
use VerifyDeprecations;
private Configuration $configuration;
protected function setUp(): void
@@ -34,6 +39,7 @@ class ConfigurationTest extends TestCase
$this->configuration = new Configuration();
}
#[WithoutErrorHandler]
public function testSetGetProxyDir(): void
{
self::assertNull($this->configuration->getProxyDir()); // defaults
@@ -42,6 +48,7 @@ class ConfigurationTest extends TestCase
self::assertSame(__DIR__, $this->configuration->getProxyDir());
}
#[WithoutErrorHandler]
public function testSetGetAutoGenerateProxyClasses(): void
{
self::assertSame(ProxyFactory::AUTOGENERATE_ALWAYS, $this->configuration->getAutoGenerateProxyClasses()); // defaults
@@ -56,6 +63,7 @@ class ConfigurationTest extends TestCase
self::assertSame(ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS, $this->configuration->getAutoGenerateProxyClasses());
}
#[WithoutErrorHandler]
public function testSetGetProxyNamespace(): void
{
self::assertNull($this->configuration->getProxyNamespace()); // defaults
@@ -212,4 +220,28 @@ class ConfigurationTest extends TestCase
$this->configuration->setTypedFieldMapper($defaultTypedFieldMapper);
self::assertSame($defaultTypedFieldMapper, $this->configuration->getTypedFieldMapper());
}
#[RequiresPhp('8.4')]
#[WithoutErrorHandler]
public function testDisablingNativeLazyObjectsIsDeprecated(): void
{
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
$this->configuration->enableNativeLazyObjects(false);
}
#[RequiresPhp('<8.4')]
public function testNotEnablingNativeLazyObjectIsFineOnPhpLowerThan84(): void
{
$this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
self::assertFalse($this->configuration->isNativeLazyObjectsEnabled());
}
#[RequiresPhp('8.4')]
#[WithoutErrorHandler]
public function testNotEnablingNativeLazyObjectIsDeprecatedOnPhp84(): void
{
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
self::assertFalse($this->configuration->isNativeLazyObjectsEnabled());
}
}

View File

@@ -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());
}

View File

@@ -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'

View File

@@ -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.');
}

View File

@@ -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();

View File

@@ -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',
);
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function reset;
class GH11982Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH11982ColumnIndex::class,
]);
}
#[Group('GH-11982')]
public function testSchema(): void
{
if ($this->_em->getConnection()->getDatabasePlatform() instanceof PostgreSQLPlatform) {
self::markTestSkipped('This test does not work on psql.');
}
$indexes = $this->createSchemaManager()
->introspectTable('GH11982ColumnIndex')
->getIndexes();
self::assertCount(3, $indexes); // primary + 2 custom indexes
self::assertArrayHasKey('class_idx', $indexes);
unset($indexes['primary']);
unset($indexes['class_idx']);
$unnamedIndexColumns = reset($indexes)->getColumns();
self::assertCount(1, $unnamedIndexColumns);
self::assertEquals('indexTrue', $unnamedIndexColumns[0]);
}
}
#[ORM\Entity]
#[ORM\Index(
name: 'class_idx',
fields: ['classIndex'],
flags: ['test'],
options: ['test'],
)]
class GH11982ColumnIndex
{
#[ORM\Id]
#[ORM\Column]
public string $noIndex;
#[ORM\Column(index: true)]
public string $indexTrue;
#[ORM\Column]
public string $classIndex;
}

View File

@@ -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);

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Internal\UnitOfWork;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Id\AssignedGenerator;
use Doctrine\ORM\Id\IdentityGenerator;
use Doctrine\ORM\Internal\UnitOfWork\InsertBatch;
use Doctrine\ORM\Mapping\ClassMetadata;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\Stub;
use PHPUnit\Framework\TestCase;
#[CoversClass(InsertBatch::class)]
#[Group('#11977')]
final class InsertBatchTest extends TestCase
{
private EntityManagerInterface&Stub $entityManager;
protected function setUp(): void
{
$this->entityManager = $this->createStub(EntityManagerInterface::class);
$entityAMetadata = new ClassMetadata(EntityA::class);
$entityBMetadata = new ClassMetadata(EntityB::class);
$entityCMetadata = new ClassMetadata(EntityC::class);
$entityAMetadata->idGenerator = new AssignedGenerator();
$entityBMetadata->idGenerator = new AssignedGenerator();
$entityCMetadata->idGenerator = new IdentityGenerator();
$this->entityManager->method('getClassMetadata')
->willReturnMap([
[EntityA::class, $entityAMetadata],
[EntityB::class, $entityBMetadata],
[EntityC::class, $entityCMetadata],
]);
}
public function testWillProduceEmptyBatchOnNoGivenEntities(): void
{
self::assertEmpty(InsertBatch::batchByEntityType($this->entityManager, []));
}
public function testWillBatchSameEntityOperationsInSingleBatch(): void
{
$batches = InsertBatch::batchByEntityType(
$this->entityManager,
[
new EntityA(),
new EntityA(),
new EntityA(),
],
);
self::assertCount(1, $batches);
self::assertSame(EntityA::class, $batches[0]->class->name);
self::assertCount(3, $batches[0]->entities);
}
public function testWillBatchInterleavedEntityOperationsInGroups(): void
{
$batches = InsertBatch::batchByEntityType(
$this->entityManager,
[
new EntityA(),
new EntityA(),
new EntityB(),
new EntityB(),
new EntityA(),
new EntityA(),
],
);
self::assertCount(3, $batches);
self::assertSame(EntityA::class, $batches[0]->class->name);
self::assertCount(2, $batches[0]->entities);
self::assertSame(EntityB::class, $batches[1]->class->name);
self::assertCount(2, $batches[1]->entities);
self::assertSame(EntityA::class, $batches[2]->class->name);
self::assertCount(2, $batches[2]->entities);
}
public function testWillNotBatchOperationsForAGeneratedIdentifierEntity(): void
{
$batches = InsertBatch::batchByEntityType(
$this->entityManager,
[
new EntityC(),
new EntityC(),
new EntityC(),
],
);
self::assertCount(3, $batches);
self::assertSame(EntityC::class, $batches[0]->class->name);
self::assertCount(1, $batches[0]->entities);
self::assertSame(EntityC::class, $batches[1]->class->name);
self::assertCount(1, $batches[1]->entities);
self::assertSame(EntityC::class, $batches[2]->class->name);
self::assertCount(1, $batches[2]->entities);
}
public function testWillIsolateBatchesForEntitiesWithGeneratedIdentifiers(): void
{
$batches = InsertBatch::batchByEntityType(
$this->entityManager,
[
new EntityA(),
new EntityA(),
new EntityC(),
new EntityC(),
new EntityA(),
new EntityA(),
],
);
self::assertCount(4, $batches);
self::assertSame(EntityA::class, $batches[0]->class->name);
self::assertCount(2, $batches[0]->entities);
self::assertSame(EntityC::class, $batches[1]->class->name);
self::assertCount(1, $batches[1]->entities);
self::assertSame(EntityC::class, $batches[2]->class->name);
self::assertCount(1, $batches[2]->entities);
self::assertSame(EntityA::class, $batches[3]->class->name);
self::assertCount(2, $batches[3]->entities);
}
}
class EntityA
{
}
class EntityB
{
}
class EntityC
{
}

View File

@@ -60,6 +60,7 @@ use function array_keys;
use function assert;
use function class_exists;
use function count;
use function defined;
use function serialize;
use function str_contains;
use function str_replace;
@@ -199,6 +200,12 @@ class ClassMetadataTest extends OrmTestCase
// float
$cm->mapField(['fieldName' => 'float']);
self::assertEquals('float', $cm->getTypeOfField('float'));
// number, requires DBAL 4.3+
if (defined(Types::class . '::NUMBER')) {
$cm->mapField(['fieldName' => 'bodyHeight']);
self::assertEquals('number', $cm->getTypeOfField('bodyHeight'));
}
}
#[TestGroup('GH10313')]

View File

@@ -227,11 +227,12 @@ abstract class MappingDriverTestCase extends OrmTestCase
#[Depends('testEntityTableNameAndInheritance')]
public function testFieldMappings(ClassMetadata $class): ClassMetadata
{
self::assertEquals(4, count($class->fieldMappings));
self::assertEquals(5, count($class->fieldMappings));
self::assertTrue(isset($class->fieldMappings['id']));
self::assertTrue(isset($class->fieldMappings['name']));
self::assertTrue(isset($class->fieldMappings['email']));
self::assertTrue(isset($class->fieldMappings['version']));
self::assertTrue(isset($class->fieldMappings['indexed']));
return $class;
}
@@ -262,6 +263,7 @@ abstract class MappingDriverTestCase extends OrmTestCase
self::assertEquals(50, $class->fieldMappings['name']->length);
self::assertTrue($class->fieldMappings['name']->nullable);
self::assertTrue($class->fieldMappings['name']->unique);
self::assertTrue($class->fieldMappings['indexed']->index);
return $class;
}
@@ -1006,6 +1008,10 @@ class User
#[ORM\Version]
public $version;
/** @var string */
#[ORM\Column(index: true)]
public $indexed;
#[ORM\PrePersist]
public function doStuffOnPrePersist(): void
{
@@ -1065,6 +1071,12 @@ class User
$mapping = ['fieldName' => 'version', 'type' => 'integer'];
$metadata->setVersionMapping($mapping);
$metadata->mapField($mapping);
$metadata->mapField([
'fieldName' => 'indexed',
'type' => 'string',
'columnName' => 'indexed',
'index' => true,
]);
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
$metadata->mapOneToOne(
[

View File

@@ -11,10 +11,13 @@ use Doctrine\ORM\Mapping\TypedFieldMapper;
use Doctrine\Tests\Models\TypedProperties\UserTyped;
use Doctrine\Tests\ORM\Mapping\TypedFieldMapper\CustomIntAsStringTypedFieldMapper;
use Doctrine\Tests\OrmTestCase;
use Generator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use ReflectionClass;
use function defined;
#[Group('GH10313')]
class TypedFieldMapperTest extends OrmTestCase
{
@@ -36,7 +39,7 @@ class TypedFieldMapperTest extends OrmTestCase
/**
* Data Provider for NamingStrategy#classToTableName
*
* @return array<
* @return Generator<
* array{
* TypedFieldMapper,
* ReflectionClass,
@@ -44,28 +47,30 @@ class TypedFieldMapperTest extends OrmTestCase
* array{fieldName: string, enumType?: string, type?: mixed}
* }>
*/
public static function dataFieldToMappedField(): array
public static function dataFieldToMappedField(): Generator
{
$reflectionClass = new ReflectionClass(UserTyped::class);
return [
// DefaultTypedFieldMapper
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::INTEGER]],
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]],
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateInterval'], ['fieldName' => 'dateInterval', 'type' => Types::DATEINTERVAL]],
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTime'], ['fieldName' => 'dateTime', 'type' => Types::DATETIME_MUTABLE]],
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTimeImmutable'], ['fieldName' => 'dateTimeImmutable', 'type' => Types::DATETIME_IMMUTABLE]],
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'array'], ['fieldName' => 'array', 'type' => Types::JSON]],
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'boolean'], ['fieldName' => 'boolean', 'type' => Types::BOOLEAN]],
[self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'float'], ['fieldName' => 'float', 'type' => Types::FLOAT]],
// DefaultTypedFieldMapper
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::INTEGER]];
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]];
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateInterval'], ['fieldName' => 'dateInterval', 'type' => Types::DATEINTERVAL]];
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTime'], ['fieldName' => 'dateTime', 'type' => Types::DATETIME_MUTABLE]];
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'dateTimeImmutable'], ['fieldName' => 'dateTimeImmutable', 'type' => Types::DATETIME_IMMUTABLE]];
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'array'], ['fieldName' => 'array', 'type' => Types::JSON]];
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'boolean'], ['fieldName' => 'boolean', 'type' => Types::BOOLEAN]];
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'float'], ['fieldName' => 'float', 'type' => Types::FLOAT]];
// CustomIntAsStringTypedFieldMapper
[self::customTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]],
if (defined(Types::class . '::NUMBER')) {
yield [self::defaultTypedFieldMapper(), $reflectionClass, ['fieldName' => 'bodyHeight'], ['fieldName' => 'bodyHeight', 'type' => Types::NUMBER]];
}
// ChainTypedFieldMapper
[self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]],
[self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]],
];
// CustomIntAsStringTypedFieldMapper
yield [self::customTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]];
// ChainTypedFieldMapper
yield [self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'id'], ['fieldName' => 'id', 'type' => Types::STRING]];
yield [self::chainTypedFieldMapper(), $reflectionClass, ['fieldName' => 'username'], ['fieldName' => 'username', 'type' => Types::STRING]];
}
/**

View File

@@ -183,7 +183,7 @@ class XmlMappingDriverTest extends MappingDriverTestCase
[
User::class,
'cms_users',
['name', 'email', 'version', 'id'],
['name', 'email', 'version', 'indexed', 'id'],
['address', 'phonenumbers', 'groups'],
],
[

View File

@@ -56,6 +56,8 @@
<field name="version" type="integer" version="true" />
<field name="indexed" type="string" index="true" />
<one-to-one field="address" target-entity="Address" inversed-by="user">
<cascade><cascade-remove /></cascade>
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" />

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Mapping as MappingNamespace;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
@@ -12,6 +13,7 @@ use Doctrine\ORM\ORMSetup;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
use PHPUnit\Framework\Attributes\RequiresSetting;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;
use ReflectionProperty;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
@@ -20,10 +22,19 @@ use Symfony\Component\Cache\Adapter\ArrayAdapter;
use function sys_get_temp_dir;
use const PHP_VERSION_ID;
class ORMSetupTest extends TestCase
{
use VerifyDeprecations;
#[WithoutErrorHandler]
public function testAttributeConfiguration(): void
{
if (PHP_VERSION_ID >= 80400) {
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
}
$config = ORMSetup::createAttributeMetadataConfiguration([], true);
self::assertInstanceOf(Configuration::class, $config);
@@ -32,8 +43,21 @@ class ORMSetupTest extends TestCase
self::assertInstanceOf(AttributeDriver::class, $config->getMetadataDriverImpl());
}
public function testAttributeConfig(): void
{
$config = ORMSetup::createAttributeMetadataConfig([], true);
self::assertInstanceOf(Configuration::class, $config);
self::assertInstanceOf(AttributeDriver::class, $config->getMetadataDriverImpl());
}
#[WithoutErrorHandler]
public function testXMLConfiguration(): void
{
if (PHP_VERSION_ID >= 80400) {
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
}
$config = ORMSetup::createXMLMetadataConfiguration([], true);
self::assertInstanceOf(Configuration::class, $config);
@@ -44,14 +68,18 @@ class ORMSetupTest extends TestCase
{
$this->expectNotToPerformAssertions();
ORMSetup::createXMLMetadataConfiguration(paths: [], isXsdValidationEnabled: false);
ORMSetup::createXMLMetadataConfig(paths: [], isXsdValidationEnabled: false);
}
#[RequiresPhpExtension('apcu')]
#[RequiresSetting('apc.enable_cli', '1')]
#[RequiresSetting('apc.enabled', '1')]
public function testCacheNamespaceShouldBeGeneratedForApcu(): void
public function testCacheNamespaceShouldBeGeneratedForApcuWhenUsingLegacyConstructor(): void
{
if (PHP_VERSION_ID >= 80400) {
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
}
$config = ORMSetup::createConfiguration(false, '/foo');
$cache = $config->getMetadataCache();
@@ -61,7 +89,22 @@ class ORMSetupTest extends TestCase
self::assertSame('dc2_1effb2475fcfba4f9e8b8a1dbc8f3caf:', $namespaceProperty->getValue($cache));
}
#[RequiresPhpExtension('apcu')]
#[RequiresSetting('apc.enable_cli', '1')]
#[RequiresSetting('apc.enabled', '1')]
public function testCacheNamespaceShouldBeGeneratedForApcu(): void
{
$config = ORMSetup::createConfig(false, '/foo');
$cache = $config->getMetadataCache();
$namespaceProperty = new ReflectionProperty(AbstractAdapter::class, 'namespace');
self::assertInstanceOf(ApcuAdapter::class, $cache);
self::assertSame('dc2_1effb2475fcfba4f9e8b8a1dbc8f3caf:', $namespaceProperty->getValue($cache));
}
#[Group('DDC-1350')]
#[WithoutErrorHandler]
public function testConfigureProxyDir(): void
{
$config = ORMSetup::createAttributeMetadataConfiguration([], true, '/foo');
@@ -72,7 +115,7 @@ class ORMSetupTest extends TestCase
public function testConfigureCache(): void
{
$cache = new ArrayAdapter();
$config = ORMSetup::createAttributeMetadataConfiguration([], true, null, $cache);
$config = ORMSetup::createAttributeMetadataConfig([], true, null, $cache);
self::assertSame($cache, $config->getResultCache());
self::assertSame($cache, $config->getQueryCache());
@@ -83,7 +126,7 @@ class ORMSetupTest extends TestCase
public function testConfigureCacheCustomInstance(): void
{
$cache = new ArrayAdapter();
$config = ORMSetup::createConfiguration(true, null, $cache);
$config = ORMSetup::createConfig(true, null, $cache);
self::assertSame($cache, $config->getResultCache());
self::assertSame($cache, $config->getQueryCache());

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -4,13 +4,16 @@ 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;
use Doctrine\Tests\OrmFunctionalTestCase;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Symfony\Component\Console\Tester\CommandTester;
/**
@@ -19,6 +22,8 @@ use Symfony\Component\Console\Tester\CommandTester;
#[CoversClass(MappingDescribeCommand::class)]
class MappingDescribeCommandTest extends OrmFunctionalTestCase
{
use ApplicationCompatibility;
private Application $application;
private MappingDescribeCommand $command;
@@ -30,7 +35,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);
@@ -74,4 +79,37 @@ class MappingDescribeCommandTest extends OrmFunctionalTestCase
],
);
}
/**
* @param string[] $input
* @param string[] $expectedSuggestions
*/
#[DataProvider('provideCompletionSuggestions')]
public function testComplete(array $input, array $expectedSuggestions): void
{
$this->useModelSet('cache');
parent::setUp();
$completionTester = new CommandCompletionTester(new MappingDescribeCommand(new SingleManagerProvider($this->_em)));
$suggestions = $completionTester->complete($input);
foreach ($expectedSuggestions as $expected) {
self::assertContains($expected, $suggestions);
}
}
/** @return iterable<string, array{string[], string[]}> */
public static function provideCompletionSuggestions(): iterable
{
yield 'entityName' => [
[''],
[
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Restaurant',
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Beach',
'Doctrine\\\\Tests\\\\Models\\\\Cache\\\\Bar',
],
];
}
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
@@ -32,6 +33,7 @@ use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\Models\Forum\ForumAvatar;
use Doctrine\Tests\Models\Forum\ForumUser;
use Doctrine\Tests\Models\MixedToOneIdentity\Country;
use Doctrine\Tests\OrmTestCase;
use Exception as BaseException;
use PHPUnit\Framework\Attributes\DataProvider;
@@ -40,6 +42,7 @@ use PHPUnit\Framework\MockObject\MockObject;
use stdClass;
use function enum_exists;
use function is_object;
use function random_int;
use function uniqid;
@@ -141,6 +144,25 @@ class UnitOfWorkTest extends OrmTestCase
self::assertIsNumeric($user->id);
}
#[Group('#11977')]
public function testMultipleInsertsAreBatchedInThePersister(): void
{
$userPersister = new EntityPersisterMock($this->_emMock, $this->_emMock->getClassMetadata(Country::class));
$this->_unitOfWork->setEntityPersister(Country::class, $userPersister);
$country1 = new Country();
$country1->country = 'Italy';
$country2 = new Country();
$country2->country = 'Germany';
$this->_unitOfWork->persist($country1);
$this->_unitOfWork->persist($country2);
$this->_unitOfWork->commit();
self::assertCount(2, $userPersister->getInserts());
self::assertSame(1, $userPersister->countOfExecuteInsertCalls());
}
/**
* Tests a scenario where a save() operation is cascaded from a ForumUser
* to its associated ForumAvatar, both entities using IDENTITY id generation.
@@ -278,7 +300,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);
}

View File

@@ -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);
}

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\Proxy;
use Doctrine\ORM\Proxy\Autoloader;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use PHPUnit\Framework\TestCase;
use function class_exists;
@@ -31,6 +32,7 @@ class AutoloaderTest extends TestCase
/** @param class-string $className */
#[DataProvider('dataResolveFile')]
#[WithoutErrorHandler]
public function testResolveFile(
string $proxyDir,
string $proxyNamespace,
@@ -41,6 +43,7 @@ class AutoloaderTest extends TestCase
self::assertEquals($expectedProxyFile, $actualProxyFile);
}
#[WithoutErrorHandler]
public function testAutoload(): void
{
if (file_exists(sys_get_temp_dir() . '/AutoloaderTestClass.php')) {

View File

@@ -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,6 +91,21 @@ 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;
}
if (PHP_VERSION_ID >= 80400 && $enableNativeLazyObjects) {
$configuration->enableNativeLazyObjects(true);
return;
}
$configuration->setProxyDir(__DIR__ . '/Proxies');
$configuration->setProxyNamespace('Doctrine\Tests\Proxies');
}