Compare commits

...

107 Commits
3.3.1 ... 3.3.4

Author SHA1 Message Date
Grégoire Paris
5ab6b74f14 Merge pull request #11984 from doctrine/2.20.x
Merge 2.20.x up into 3.3.x
2025-06-14 13:32:43 +02:00
Grégoire Paris
71550106d4 Merge pull request #11975 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.3.0
Bump doctrine/.github from 7.2.2 to 7.3.0
2025-06-09 22:24:12 +02:00
dependabot[bot]
36011f0d0f Bump doctrine/.github from 7.2.2 to 7.3.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.2.2 to 7.3.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.2.2...7.3.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 07:13:58 +00:00
Grégoire Paris
c97d775370 Merge pull request #11963 from dbu/update-doc-building
cleanup doc building instructions
2025-06-09 07:45:09 +02:00
Grégoire Paris
e9f0345a97 Merge pull request #11966 from greg0ire/partial-revert-10162-2
Partially revert to stdout
2025-06-07 09:41:03 +02:00
Grégoire Paris
05c8c5f114 Merge pull request #11969 from greg0ire/3.3.x
Merge 2.20.x up into 3.3.x
2025-06-06 12:29:34 +02:00
Gregoire PARIS
1dbdb0e895 Merge remote-tracking branch 'origin/2.20.x' into 3.3.x 2025-06-06 12:08:24 +02:00
Grégoire Paris
0feb09d0d6 Partially revert to stdout
This command's purpose is to provide structured data, except for a call
to caution() that warns the user in case they do not have any mapped
entities or they have errors.
2025-06-06 07:57:38 +02:00
Grégoire Paris
fe5f8bbaa1 Merge pull request #11965 from greg0ire/partial-revert-10162
Revert to stdout for MappingDescribeCommand
2025-06-06 00:04:07 +02:00
Grégoire Paris
ecf3cec376 chore: ignore deprecations from Symfony
Symfony 7.3 is not available to all of our users, so we cannot switch to
native lazy objects, which require a PHP version higher than the lowest
PHP version we support.
2025-06-05 23:19:32 +02:00
Grégoire Paris
0a714db4d9 Revert to stdout for MappingDescribeCommand
In f256d996cc, I did a global move to
stderr for notifications, and went a bit overboard for
MappingDescribeCommand, which purpose is to output a description.
2025-06-05 23:07:28 +02:00
David Buchmann
471fda8d0b cleanup doc building instructions 2025-06-05 07:44:37 +02:00
Grégoire Paris
dfe32c2f74 Unwrap literalinclude block (#11962)
For some reason, it does not appear to work when nested inside a
code-block directive. Anyway, if you specify the language attribute, you
get markup identical to what you obtain when using code-block and
literalinclude, so this wrapping seems unneeded.
2025-06-04 00:18:59 +02:00
Grégoire Paris
c51ba3ce6b Merge pull request #11951 from dbu/fix-doc-syntax
insert blank line before code in code-block
2025-05-27 14:08:44 +02:00
David Buchmann
fe025e8d23 insert blank line before code in code-block 2025-05-27 08:59:14 +02:00
Grégoire Paris
7111cc09f3 Merge pull request #11945 from greg0ire/3.3.x
Merge 2.20.x up into 3.3.x
2025-05-25 18:12:06 +02:00
Grégoire Paris
777504b9c4 Merge remote-tracking branch 'origin/2.20.x' into 3.3.x 2025-05-24 19:19:52 +02:00
Dimitri Dovgan
9d9985076a Add missing closing bracket (#11937)
Corrected a malformed attribute in the documentation.
2025-05-18 12:08:50 +02:00
Grégoire Paris
ae2957cf7e Merge pull request #11932 from dbannik/2.20.3-issue-11931
#11931 Bug when change sql filter [Related issue #11694]
2025-05-06 07:53:48 +02:00
Dmitry Bannik
e172b3bf9c #11931 Bug when change sql filter [Related issue #11694]
This fix takes into account the invalidation of the filter sql for SingleTablePersister and JoinedSubclassPersister
2025-05-05 23:43:27 +03:00
Grégoire Paris
c9c6e8da2e Merge pull request #11834 from dbu/document-generated-columns
document how to work with generated columns
2025-05-05 10:05:55 +02:00
Grégoire Paris
1f1891d3e2 Merge pull request #11926 from greg0ire/3.3.x
Merge 2.20.x up into 3.3.x
2025-05-02 19:42:51 +02:00
Grégoire Paris
c9fc4d90e5 Merge remote-tracking branch 'origin/2.20.x' into 3.3.x 2025-05-02 19:17:15 +02:00
Grégoire Paris
17d28b5c4c Merge pull request #11917 from stof/lazy_ghost_postload
Fix the initialization of lazy-ghost proxies with postLoad listeners
2025-05-02 19:07:53 +02:00
Christophe Coevoet
a2d510c6f4 Fix the initialization of lazy-ghost proxies with postLoad listeners
PostLoad listeners might initialize values for transient properties, so
the proxy should not skip initialization when using transient
properties.

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
2025-05-02 15:27:59 +02:00
Anton
0e3cff0c6a Update composite-primary-keys.rst (#11919)
Fix examples
2025-05-01 23:56:00 +02:00
Grégoire Paris
5c50ed925a Merge pull request #11924 from xabbuh/dbal-6867
prefer primary key constraints over Index::isPrimary()
2025-05-01 13:09:32 +01:00
Christian Flothmann
5a1e560f87 prefer primary key constraints over Index::isPrimary() 2025-05-01 13:46:41 +02:00
Grégoire Paris
78e8887759 Merge pull request #11911 from xabbuh/dbal-6890
disable detecting modified indexes with DBAL 4.3
2025-04-25 11:02:23 +02:00
Grégoire Paris
5eb298b99b Merge pull request #11914 from xabbuh/dbal-6886
do not use deprecated index features
2025-04-23 10:54:33 +02:00
Christian Flothmann
5eb0255f47 do not use deprecated index features 2025-04-23 08:01:37 +02:00
Grégoire Paris
28575f58af Merge pull request #11903 from xabbuh/dbal-6867
do not use deprecated primary key constraint features
2025-04-23 07:56:01 +02:00
Christian Flothmann
457d2d2841 do not use deprecated primary key constraint features 2025-04-23 07:07:30 +02:00
Christophe Coevoet
2a4ebca90e Refactor tests to avoid using instance properties to track postLoad
The old proxy implementation of doctrine/common was triggered by public
methods rather than access to properties (making public properties
unsupported in entities), so tests could use public instance properties
to track the state of postLoad lifecycle callbacks without triggering
the proxy initialization when reading that state (which then changes the
state of triggering the postLoad callback).
As the new proxy implementation hooks into properties instead, the tests
now use a static method (ensuring it is reset properly before loading
the instance for which we care about the tracking) instead of an
instance property.
2025-04-22 17:39:29 +02:00
Christian Flothmann
5d01c66c84 disable detecting modified indexes with DBAL 4.3 2025-04-22 12:45:20 +02:00
Yevhen Sidelnyk
a2516b67dc [DOC]: fix setFileExtension() xml mapping documentation (#11905)
* doc: fix setFileExtension() xml mapping documentation

* Update docs/en/reference/xml-mapping.rst

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>

---------

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2025-04-21 22:21:43 +02:00
Grégoire Paris
3d4e9b3ecf Merge pull request #11912 from xabbuh/dbal-6823
[#6823] pass unquoted identifier folding to AbstractPlatform
2025-04-20 11:09:40 +02:00
Grégoire Paris
d1ee40e7d5 Merge pull request #11908 from xabbuh/dbal-6674
use the platform to quote table names
2025-04-20 11:08:07 +02:00
Christian Flothmann
65d2720764 [#6823] pass unquoted identifier folding to AbstractPlatform 2025-04-17 23:56:20 +02:00
Grégoire Paris
3e18a58de6 Merge pull request #11907 from xabbuh/dbal-6728
do not use deprecated foreign key constraint features
2025-04-17 23:10:47 +02:00
Christian Flothmann
a9f9202c00 do not use deprecated foreign key constraint features 2025-04-17 21:18:35 +02:00
Grégoire Paris
d8cb71fe4a Merge pull request #11906 from xabbuh/dbal-6710
no longer use Table::columnsAreIndexed()
2025-04-17 08:28:28 +02:00
Christian Flothmann
d659591b6c use the platform to quote table names 2025-04-15 12:49:24 +02:00
Christian Flothmann
e2430ac9a7 no longer use Table::columnsAreIndexed() 2025-04-15 12:43:51 +02:00
Grégoire Paris
0b373f6c27 Merge pull request #11900 from xabbuh/dbal-6886
replace Index::overrules() with custom checks
2025-04-14 23:17:07 +02:00
Christian Flothmann
36b9064dbe replace Index::overrules() with custom checks 2025-04-07 08:53:18 +02:00
Grégoire Paris
cc29ae0d36 Merge pull request #11891 from mpdude/expression-matching-caveats
Add more detailed caveats for using the Collection filtering API
2025-03-29 11:31:36 +01:00
Grégoire Paris
bd4a053d29 Merge pull request #11894 from DavidPetrasek/3.3.x
Fix URL's in xml-mapping.rst
2025-03-27 20:59:26 +01:00
David Petrásek
52fbfb3785 Revert to http for namespace name
These URLs are meant as identifiers rather than actual urls intended to
be used to perform an HTTP request.
2025-03-27 17:39:10 +01:00
Matthias Pigulla
c259371e5f Remove property hooks mention 2025-03-26 18:58:17 +01:00
Matthias Pigulla
dcdd58b642 working-with-associations.rst aktualisieren
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2025-03-26 18:14:01 +01:00
Matthias Pigulla
7b9c53854f Add more detailed caveats for using the Collection filtering API 2025-03-26 13:38:52 +01:00
Grégoire Paris
cdc5fe11dd Merge pull request #11889 from Rixafy/docs-typo-fix
Fix docs typo (nulable -> nullable)
2025-03-25 23:16:01 +01:00
Rixafy
69ece00564 Fix docs typo (nulable -> nullable) 2025-03-25 22:25:00 +01:00
Grégoire Paris
4baa7bd252 Merge pull request #11882 from xabbuh/dbal-6867
favor Table::addPrimaryKeyConstraint() over Table::setPrimaryKey()
2025-03-25 16:06:32 +01:00
Christian Flothmann
c55f10e501 favor Table::addPrimaryKeyConstraint() over Table::setPrimaryKey() 2025-03-25 12:40:28 +01:00
Grégoire Paris
f3fb79658e Merge pull request #11886 from greg0ire/3.3.x
Merge 2.20.x up into 3.3.x
2025-03-25 08:44:22 +01:00
Grégoire Paris
10a32cb824 Merge remote-tracking branch 'origin/2.20.x' into 3.3.x 2025-03-25 07:56:47 +01:00
Grégoire Paris
c679d1b007 Merge pull request #11885 from greg0ire/no-triple-stars
Avoid triple stars
2025-03-25 07:51:08 +01:00
Grégoire Paris
1e15b22dcb Avoid triple stars
They don't have a special meaning, and are rendered like this:
<strong>*REQUIRED</strong>*.
2025-03-25 07:49:53 +01:00
Grégoire Paris
d88371331d Merge pull request #11883 from xabbuh/dbal-6864
adapt assertions for new consistent PRIMARY KEY formatting
2025-03-24 22:02:26 +01:00
Christian Flothmann
b260ec8a00 adapt assertions for new consistent PRIMARY KEY formatting 2025-03-24 21:40:11 +01:00
Grégoire Paris
44057b4683 Merge pull request #11845 from lacatoire/update-message-annotation-to-attribute
Update message of `ORMInvalidArgumentException`
2025-03-24 10:58:02 +01:00
Grégoire Paris
013df03795 Upgrade to doctrine/coding-standard 13 (#11881) 2025-03-24 07:18:07 +01:00
Louis-Arnaud
2d2a34407c Use attributes in exception message 2025-03-23 16:07:31 +01:00
Grégoire Paris
1072ea6db4 Merge pull request #11877 from greg0ire/iso-phpunit-config
Iso phpunit config
2025-03-18 20:41:25 +01:00
Grégoire Paris
baf2c60cc4 Use more similar PHPUnit configurations
The dev configuration and CI configuration should not diverge this much.
I do not think the current situation was intended. A difference that
remains after my changes is the bootstrap file, which in dev seems aimed
at helping contributors setup their environment.
2025-03-18 20:08:09 +01:00
Grégoire Paris
8e620cad40 Do not run CI jobs when changing dev PHPUnit file
This file is never used in the CI.
2025-03-18 20:08:09 +01:00
Grégoire Paris
daf0f82884 Merge pull request #11874 from doctrine/2.20.x
Merge 2.20.x up into 3.3.x
2025-03-18 13:52:20 +01:00
Matteo Beccati
3303cd3b5d Fix non-deterministic test (#11866) 2025-03-14 00:09:36 +01:00
Grégoire Paris
afcf91e839 Merge pull request #11863 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.2.2
Bump doctrine/.github from 7.2.1 to 7.2.2
2025-03-10 09:24:12 +01:00
dependabot[bot]
c61a9b3b6d Bump doctrine/.github from 7.2.1 to 7.2.2
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.2.1 to 7.2.2.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.2.1...7.2.2)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 06:43:02 +00:00
Grégoire Paris
bd0509a064 Merge pull request #11825 from eltharin/bug_arg_order
FIX - nested dto's doesn't have arguments in good order and Dto with only objects bug
2025-03-05 15:38:16 +01:00
eltharin
708bd84fe2 fix bugs
nested dto are not in good argument order
dto with only dto does'nt work
2025-02-21 10:35:19 +01:00
Louis-Arnaud
3aed6912a3 Update ORMInvalidArgumentException.php
update message to use attribute instead of annotation
2025-02-21 10:10:22 +01:00
Grégoire Paris
36bef3f959 Merge pull request #11839 from greg0ire/3.3.x
Merge 2.20.x up into 3.3.x
2025-02-18 22:42:29 +01:00
Grégoire Paris
980ccc58dc Merge remote-tracking branch 'origin/2.20.x' into 3.3.x 2025-02-18 21:40:58 +01:00
Grégoire Paris
62ca4624a9 Merge pull request #11837 from greg0ire/restore-exception
Restore logic exception
2025-02-17 20:39:14 +01:00
Grégoire Paris
1aed318b7b Restore logic exception
This exception was dropped by mistake during a merge-up, and should be
present until Doctrine officially supports property hooks.
2025-02-17 19:17:20 +01:00
David Buchmann
8ce7b310c5 document how to work with generated columns 2025-02-17 15:06:18 +01:00
Grégoire Paris
158605bf24 Merge pull request #11833 from HypeMC/fix-dql
Fix DQL example with composite key
2025-02-13 23:49:22 +01:00
Grégoire Paris
2c2ef65817 Merge pull request #11826 from aprat84/gh-11741
Clone query hints and parameters in `LimitSubqueryOutputWalker` constructor
2025-02-12 08:52:33 +01:00
Gregor Harlan
f8fdeaf41e UPGRADE.md: fix typo (#11832) 2025-02-12 00:10:14 +01:00
HypeMC
1c33a86983 Fix DQL example with composite key 2025-02-11 16:10:59 +01:00
Albert Prat
310fe1cccb Clone query hints and parameters in LimitSubqueryOutputWalker constructor
This fixes a bug that arises when using Pagination and an entity relation is mapped with fetch-mode EAGER but setFetchMode LAZY (or anything that is not EAGER) has been used on the query. If the query use WITH condition, an exception is incorrectly raised (Associations with fetch-mode=EAGER may not be using WITH conditions).
The class LimitSubqueryOutputWalker clones the query, but not its parameters and hints, so the generated subquery does not know that fetch-mode has been overridden.

Fixes #11741
2025-02-11 10:48:14 +01:00
Grégoire Paris
a67f677747 Merge pull request #11707 from jorenMartens/2.20.x
[DDC-551] fix, add filter support in oneToOne relation 2.20.x
2025-02-07 08:23:53 +01:00
Grégoire Paris
c9557c588b Merge remote-tracking branch 'origin/2.20.x' into 3.3.x 2025-02-04 20:43:15 +01:00
Grégoire Paris
19912de927 Merge pull request #11820 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-7.2.1
Bump doctrine/.github from 7.1.0 to 7.2.1
2025-02-04 20:17:01 +01:00
dependabot[bot]
737cca5b78 Bump doctrine/.github from 7.1.0 to 7.2.1
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.1.0 to 7.2.1.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.1.0...7.2.1)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 18:23:54 +00:00
Grégoire Paris
aff82af7de Merge pull request #11811 from greg0ire/quoteIdentifier-depr
Address quoteIdentifier() deprecation
2025-01-28 23:17:29 +01:00
Grégoire Paris
9e999ea1ff Merge pull request #11792 from dbannik/11783-failure-with-indexed-relation
11783 failure with indexed relation
2025-01-28 15:32:25 +01:00
Bob van de Vijver
6755bb0c7b Fix Hydration when use ManyToMany[indexBy]
The bug related (#11694) and fixed mapping of sql column alias to field in entity (#11783) and
invalidate cache [cache/persisted/entity|cache/persisted/collection] when sql filter changes
2025-01-27 15:35:59 +03:00
Grégoire Paris
aa141bf001 Address quoteIdentifier() deprecation
We should be using quoteSingleIdentifier(), assuming we only ever pass
single identifiers here.

See https://github.com/doctrine/dbal/pull/6590
2025-01-26 12:01:14 +01:00
Grégoire Paris
cf39e00553 Merge pull request #11807 from greg0ire/display-deprecations
Display Doctrine deprecations when running PHPUnit
2025-01-25 11:09:40 +01:00
Grégoire Paris
27b47841be Display Doctrine deprecations when running PHPUnit
This will give a signal that there is work to be done without blocking
other contributions by failing the build.
2025-01-25 10:53:36 +01:00
Grégoire Paris
c2a49327a7 Merge pull request #11799 from HypeMC/enum-array-error
Fix invalid enum value in array of enums
2025-01-22 11:54:59 +01:00
Maxime COLIN
9bd7242376 Introduce testNotListedValueInEnumArray 2025-01-22 02:25:34 +01:00
pawel-slowik
fff085b63f Fix documentation for JoinColumn nullable (#11798)
Nullability is not inherited from the PHP type. The change that enabled
this feature was reversed in https://github.com/doctrine/orm/pull/8732.
2025-01-22 00:12:00 +01:00
Grégoire Paris
5ad5b11ae1 Merge pull request #11769 from HypeMC/fix-reportfieldswheredeclared
Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared`
2025-01-20 23:48:32 +01:00
Grégoire Paris
c12fd2cb94 Merge pull request #11793 from greg0ire/doctrine-common-support
Ignore deprecations from doctrine/common
2025-01-18 22:29:13 +01:00
Grégoire Paris
44d5d4a779 Ignore deprecations from doctrine/common
These new issues are caused by doctrine/common 3.5.0, released 2 weeks
ago.
2025-01-17 08:33:24 +01:00
Grégoire Paris
5a599233c9 Merge pull request #11791 from jonnyeom/patch-2
UPGRADE: Document QueryBuilder::setParameters() Type enforcement
2025-01-17 07:55:07 +01:00
Jonny Eom
596da353c2 UPGRADE: Document QueryBuilder::setParameters() Type enforcement 2025-01-16 17:14:20 +01:00
Jamie Purchase
68c87740aa Update working-with-objects.rst (#7553)
Spelling.
2025-01-02 00:25:25 +01:00
Florian Sylvain
55dc02c39f changed confusing negative wording (#11775) 2024-12-31 00:09:28 +01:00
HypeMC
4feaa470af Fix fields of transient classes being considered duplicate with reportFieldsWhereDeclared 2024-12-18 15:42:12 +01:00
Joren Martens
14866461c5 [DDC-551] fix, add filter support in oneToOne relation 2024-11-07 10:48:16 +01:00
122 changed files with 2247 additions and 403 deletions

View File

@@ -24,4 +24,4 @@ on:
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.1.0"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@7.3.0"

View File

@@ -9,7 +9,6 @@ on:
- ci/**
- composer.*
- src/**
- phpunit.xml.dist
- tests/**
push:
branches:
@@ -19,7 +18,6 @@ on:
- ci/**
- composer.*
- src/**
- phpunit.xml.dist
- tests/**
env:

View File

@@ -17,4 +17,4 @@ on:
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@7.1.0"
uses: "doctrine/.github/.github/workflows/documentation.yml@7.3.0"

View File

@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.1.0"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.3.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

View File

@@ -124,6 +124,36 @@ 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)`
The argument $parameters can no longer be a key=>value array. Only ArrayCollection types are allowed.
### Before
```php
$qb = $em->createQueryBuilder()
->select('u')
->from('User', 'u')
->where('u.id = :user_id1 OR u.id = :user_id2')
->setParameters(array(
'user_id1' => 1,
'user_id2' => 2
));
```
### After
```php
$qb = $em->createQueryBuilder()
->select('u')
->from('User', 'u')
->where('u.id = :user_id1 OR u.id = :user_id2')
->setParameters(new ArrayCollection(array(
new Parameter('user_id1', 1),
new Parameter('user_id2', 2)
)));
```
## BC BREAK: `Doctrine\ORM\Persister\Entity\EntityPersister::executeInserts()` return type changed to `void`
Implementors should adapt to the new signature, and should call

View File

@@ -3,6 +3,11 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
failOnWarning="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
@@ -19,6 +24,7 @@
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<testsuites>
@@ -27,7 +33,7 @@
</testsuite>
</testsuites>
<source>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory suffix=".php">../../../src</directory>
</include>

View File

@@ -3,6 +3,11 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
failOnWarning="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
@@ -19,6 +24,7 @@
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<testsuites>
@@ -27,7 +33,7 @@
</testsuite>
</testsuites>
<source>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory suffix=".php">../../../src</directory>
</include>

View File

@@ -3,6 +3,11 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
failOnWarning="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
@@ -16,6 +21,7 @@
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<testsuites>
@@ -24,7 +30,7 @@
</testsuite>
</testsuites>
<source>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory suffix=".php">../../../src</directory>
</include>

View File

@@ -3,6 +3,11 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
failOnWarning="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
@@ -14,6 +19,7 @@
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<testsuites>
@@ -22,7 +28,7 @@
</testsuite>
</testsuites>
<source>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory suffix=".php">../../../src</directory>
</include>

View File

@@ -3,6 +3,11 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
failOnWarning="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
@@ -16,6 +21,7 @@
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<testsuites>
@@ -24,7 +30,7 @@
</testsuite>
</testsuites>
<source>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory suffix=".php">../../../src</directory>
</include>

View File

@@ -3,6 +3,11 @@
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
failOnWarning="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
@@ -14,6 +19,7 @@
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<testsuites>
@@ -22,7 +28,7 @@
</testsuite>
</testsuites>
<source>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory suffix=".php">../../../src</directory>
</include>

View File

@@ -37,7 +37,7 @@
"symfony/var-exporter": "^6.3.9 || ^7.0"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"doctrine/coding-standard": "^13.0",
"phpbench/phpbench": "^1.0",
"phpdocumentor/guides-cli": "^1.4",
"phpstan/extension-installer": "^1.4",
@@ -45,7 +45,7 @@
"phpstan/phpstan-deprecation-rules": "^2",
"phpunit/phpunit": "^10.4.0",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"squizlabs/php_codesniffer": "3.12.0",
"symfony/cache": "^5.4 || ^6.2 || ^7.0"
},
"suggest": {

3
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
composer.lock
vendor/
build/

24
docs/Makefile Normal file
View File

@@ -0,0 +1,24 @@
# Makefile for Doctrine ORM documentation
#
# You can set these variables from the command line.
DOCOPTS =
BUILD = vendor/bin/guides
BUILDDIR = build
# Internal variables.
ALLGUIDESOPTS = $(DOCOPTS) en/
.PHONY: help clean html
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
clean:
-rm -rf ./$(BUILDDIR)/*
html:
$(BUILD) $(ALLGUIDESOPTS) --output=$(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

View File

@@ -1,18 +1,22 @@
# Doctrine ORM Documentation
The documentation is written in [ReStructured Text](https://docutils.sourceforge.io/rst.html).
## How to Generate:
Using Ubuntu 14.04 LTS:
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
In the `docs/` folder, run
It will generate the documentation into the build directory of the checkout.
composer update
Then compile the documentation with:
## Theme issues
make html
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
in which case you will need to run:
This will generate the documentation into the `build` subdirectory.
1. git submodule init
2. git submodule update
To browse the documentation, you need to run a webserver:
cd build/html
php -S localhost:8000
Now the documentation is available at [http://localhost:8000](http://localhost:8000).

View File

@@ -1,10 +0,0 @@
#!/bin/bash
EXECPATH=`dirname $0`
cd $EXECPATH
cd ..
rm build -Rf
sphinx-build en build
sphinx-build -b latex en build/pdf
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex

View File

@@ -1,2 +0,0 @@
#!/bin/bash
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments

10
docs/composer.json Normal file
View File

@@ -0,0 +1,10 @@
{
"name": "doctrine/orm-docs",
"description": "Documentation for the Object-Relational Mapper\"",
"type": "library",
"license": "MIT",
"require": {
"phpdocumentor/guides-cli": "1.7.1",
"phpdocumentor/filesystem": "1.7.1"
}
}

View File

@@ -1,89 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Doctrine2ORM.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
"run these through (pdf)latex."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@@ -0,0 +1,44 @@
Generated Columns
=================
Generated columns, sometimes also called virtual columns, are populated by
the database engine itself. They are a tool for performance optimization, to
avoid calculating a value on each query.
You can define generated columns on entities and have Doctrine map the values
to your entity.
Declaring a generated column
----------------------------
There is no explicit mapping instruction for generated columns. Instead, you
specify that the column should not be written to, and define a custom column
definition.
.. literalinclude:: generated-columns/Person.php
:language: php
* ``insertable``, ``updatable``: Setting these to false tells Doctrine to never
write this column - writing to a generated column would result in an error
from the database.
* ``columnDefinition``: We specify the full DDL to create the column. To allow
to use database specific features, this attribute does not use Doctrine Query
Language but native SQL. Note that you need to reference columns by their
database name (either explicitly set in the mapping or per the current
:doc:`naming strategy <../reference/namingstrategy>`).
Be aware that specifying a column definition makes the ``SchemaTool``
completely ignore all other configuration for this column. See also
:ref:`#[Column] <attrref_column>`
* ``generated``: Specifying that this column is always generated tells Doctrine
to update the field on the entity with the value from the database after
every write operation.
Advanced example: Extracting a value from a JSON structure
----------------------------------------------------------
Lets assume we have an entity that stores a blogpost as structured JSON.
To avoid extracting all titles on the fly when listing the posts, we create a
generated column with the field.
.. literalinclude:: generated-columns/Article.php
:language: php

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Article
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
/**
* When working with Postgres, it is recommended to use the jsonb
* format for better performance.
*/
#[ORM\Column(options: ['jsonb' => true])]
private array $content;
/**
* Because we specify NOT NULL, inserting will fail if the content does
* not have a string in the title field.
*/
#[ORM\Column(
insertable: false,
updatable: false,
columnDefinition: "VARCHAR(255) generated always as (content->>'title') stored NOT NULL",
generated: 'ALWAYS',
)]
private string $title;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Person
{
#[ORM\Column(type: 'string')]
private string $firstName;
#[ORM\Column(type: 'string', name: 'name')]
private string $lastName;
#[ORM\Column(
type: 'string',
insertable: false,
updatable: false,
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstName, ' ', name) stored NOT NULL",
generated: 'ALWAYS',
)]
private string $fullName;
}

View File

@@ -102,6 +102,7 @@ Cookbook
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
:doc:`Generated/Virtual Columns <cookbook/generated-columns>` \|
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
@@ -121,4 +122,5 @@ Cookbook
* **Custom Datatypes**
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Custom Mapping Types <cookbook/custom-mapping-types>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`

View File

@@ -71,8 +71,8 @@ Configuration Options
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
Proxy Directory (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy Directory (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -85,8 +85,8 @@ classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy Namespace (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -98,8 +98,8 @@ Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (***REQUIRED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Metadata Driver (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -139,8 +139,8 @@ accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
Metadata Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Metadata Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -161,8 +161,8 @@ For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
Query Cache (***RECOMMENDED***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Query Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -184,8 +184,8 @@ For development you should use an array cache like
``Symfony\Component\Cache\Adapter\ArrayAdapter``
which only caches data on a per-request basis.
SQL Logger (***Optional***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
SQL Logger (**Optional**)
~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: php
@@ -197,8 +197,8 @@ Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Auto-generating Proxy Classes (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
@@ -441,7 +441,7 @@ correctly if sub-namespaces use different metadata driver
implementations.
Default Repository (***OPTIONAL***)
Default Repository (**OPTIONAL**)
-----------------------------------
Specifies the FQCN of a subclass of the EntityRepository.
@@ -456,7 +456,7 @@ That will be available for all entities without a custom repository class.
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (***OPTIONAL***)
Ignoring entities (**OPTIONAL**)
-----------------------------------
Specifies the Entity FQCNs to ignore.

View File

@@ -903,8 +903,7 @@ defaults to "id", just as in one-to-one or many-to-one mappings.
Additionally, when using typed properties with Doctrine 2.9 or newer
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
associations as they will be set based on type. Also ``nullable``
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
associations as they will be set based on type. So that:
.. configuration-block::
@@ -931,7 +930,7 @@ Is essentially the same as following:
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment $shipment;
.. code-block:: annotation
@@ -940,7 +939,7 @@ Is essentially the same as following:
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private Shipment $shipment;
@@ -949,7 +948,7 @@ Is essentially the same as following:
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" nulable=false />
<join-column name="shipment_id" referenced-column-name="id" nullable=false />
</one-to-one>
</entity>
</doctrine-mapping>

View File

@@ -214,12 +214,15 @@ Optional parameters:
- ``check``: Adds a check constraint type to the column (might not
be supported by all vendors).
- **columnDefinition**: DDL SQL snippet that starts after the column
- **columnDefinition**: Specify the DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute allows to make use of advanced RMDBS features.
However you should make careful use of this feature and the
consequences. ``SchemaTool`` will not detect changes on the column correctly
anymore if you use ``columnDefinition``.
However, as this needs to be specified in the DDL native to the database,
the resulting schema changes are no longer portable. If you specify a
``columnDefinition``, the ``SchemaTool`` ignores all other attributes
that are normally used to build the definition DDL. Changes to the
``columnDefinition`` are not detected, you will need to manually create a
migration to apply changes.
Additionally you should remember that the ``type``
attribute still handles the conversion between PHP and Database
@@ -262,10 +265,11 @@ Examples:
)]
protected $loginCount;
// MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
// columnDefinition is raw SQL, not DQL. This example works for MySQL:
#[Column(
type: "string",
name: "user_fullname",
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstname,' ',lastname))",
insertable: false,
updatable: false
)]
@@ -366,7 +370,7 @@ Optional parameters:
- **type**: By default this is string.
- **length**: By default this is 255.
- **columnDefinition**: By default this is null the definition according to the type will be used. This option allows to override it.
- **columnDefinition**: Allows to override how the column is generated. See the "columnDefinition" attribute on :ref:`#[Column] <attrref_column>`
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
- **options**: See "options" attribute on :ref:`#[Column] <attrref_column>`.
@@ -677,8 +681,10 @@ Optional parameters:
- **onDelete**: Cascade Action (Database-level)
- **columnDefinition**: DDL SQL snippet that starts after the column
name and specifies the complete (non-portable!) column definition.
This attribute enables the use of advanced RMDBS features. Using
this attribute on ``#[JoinColumn]`` is necessary if you need slightly
This attribute enables the use of advanced RMDBS features. Note that you
need to reference columns by their database name (either explicitly set in
the mapping or per the current :doc:`naming strategy <namingstrategy>`).
Using this attribute on ``#[JoinColumn]`` is necessary if you need
different column definitions for joining columns, for example
regarding NULL/NOT NULL defaults. However by default a
"columnDefinition" attribute on :ref:`#[Column] <attrref_column>` also sets
@@ -1133,7 +1139,7 @@ Marker attribute that defines a specified column as version attribute used in
an :ref:`optimistic locking <transactions-and-concurrency_optimistic-locking>`
scenario. It only works on :ref:`#[Column] <attrref_column>` attributes that have
the type ``integer`` or ``datetime``. Setting ``#[Version]`` on a property with
:ref:`#[Id <attrref_id>` is not supported.
:ref:`#[Id] <attrref_id>` is not supported.
Example:

View File

@@ -265,7 +265,7 @@ specific to a particular entity class's lifecycle.
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="User">

View File

@@ -16,7 +16,7 @@ is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can
appear in the middle of an otherwise mapped inheritance hierarchy
(through Single Table Inheritance or Class Table Inheritance). They
are not query-able, and need not have an ``#[Id]`` property.
are not query-able, and do not require an ``#[Id]`` property.
No database table will be created for a mapped superclass itself,
only for entity classes inheriting from it. That implies that a

View File

@@ -299,7 +299,7 @@ level cache region.
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Country">
@@ -351,7 +351,7 @@ It caches the primary keys of association and cache each element will be cached
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="State">

View File

@@ -736,6 +736,35 @@ methods:
.. note::
There is a limitation on the compatibility of Criteria comparisons.
You have to use scalar values only as the value in a comparison or
the behaviour between different backends is not the same.
Depending on whether the collection has already been loaded from the
database or not, criteria matching may happen at the database/SQL level
or on objects in memory. This may lead to different results and come
surprising, for example when a code change in one place leads to a collection
becoming initialized and, as a side effect, returning a different result
or even breaking a ``matching()`` call somewhere else. Also, collection
initialization state in practical use cases may differ from the one covered
in unit tests.
Database level comparisons are based on scalar representations of the values
stored in entity properties. The field names passed to expressions correspond
to property names. Comparison and sorting may be affected by
database-specific behavior. For example, MySQL enum types sort by index position,
not lexicographically by value.
In-memory handling is based on the ``Selectable`` API of `Doctrine Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html#matching>`.
In this case, field names passed to expressions are being used to derive accessor
method names. Strict type comparisons are used for equal and not-equal checks,
and generally PHP language rules are being used for other comparison operators
or sorting.
As a general guidance, for consistent results use the Criteria API with scalar
values only. Note that ``DateTime`` and ``DateTimeImmutable`` are two predominant
examples of value objects that are *not* scalars.
Refrain from using special database-level column types or custom Doctrine Types
that may lead to database-specific comparison or sorting rules being applied, or
to database-level values being different from object field values.
Provide accessor methods for all entity fields used in criteria expressions,
and implement those methods in a way that their return value is the
same as the database-level value.

View File

@@ -166,7 +166,7 @@ your code. See the following code:
Traversing the object graph for parts that are lazy-loaded will
easily trigger lots of SQL queries and will perform badly if used
to heavily. Make sure to use DQL to fetch-join all the parts of the
too heavily. Make sure to use DQL to fetch-join all the parts of the
object-graph that you need as efficiently as possible.

View File

@@ -18,7 +18,7 @@ setup for the latest code in trunk.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -46,7 +46,7 @@ In order to work, this requires certain conventions:
.. code-block:: php
<?php
$driver->setFileExtension('.xml');
$driver->getLocator()->setFileExtension('.xml');
It is recommended to put all XML mapping documents in a single
folder but you can spread the documents over several folders if you
@@ -104,7 +104,7 @@ of several common elements:
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -770,7 +770,7 @@ entity relationship. You can define this in XML with the "association-key" attri
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

View File

@@ -64,6 +64,7 @@
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/generated-columns
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes

View File

@@ -54,7 +54,7 @@ and year of production as primary keys:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -85,11 +85,12 @@ And for querying you can use arrays to both DQL and EntityRepositories:
namespace VehicleCatalogue\Model;
// $em is the EntityManager
$audi = $em->find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010));
$audi = $em->find("VehicleCatalogue\Model\Car", ["name" => "Audi A8", "year" => 2010]);
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.name = ?1 AND c.year = ?2";
$audi = $em->createQuery($dql)
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
->setParameter(1, "Audi A8")
->setParameter(2, 2010)
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
@@ -174,7 +175,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -249,7 +250,7 @@ of products purchased and maybe even the current price.
public function __construct(
#[ManyToOne(targetEntity: Customer::class)]
private Customer $customer,
private Customer $customer
) {
$this->items = new ArrayCollection();
$this->created = new DateTime("now");
@@ -294,6 +295,7 @@ of products purchased and maybe even the current price.
$this->order = $order;
$this->product = $product;
$this->offeredPrice = $product->getCurrentPrice();
$this->amount = $amount;
}
}

View File

@@ -71,7 +71,7 @@ switch to extra lazy as shown in these examples:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

View File

@@ -514,7 +514,7 @@ methods, but you only need to choose one.
<!-- config/xml/Product.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -1023,7 +1023,7 @@ the ``Product`` before:
<!-- config/xml/Bug.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -1103,7 +1103,7 @@ Finally, we'll add metadata mappings for the ``User`` entity.
<!-- config/xml/User.dcm.xml -->
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
@@ -1586,7 +1586,7 @@ we have to adjust the metadata slightly.
.. code-block:: xml
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

View File

@@ -78,7 +78,7 @@ here are the code and mappings for it:
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

View File

@@ -1350,12 +1350,72 @@ parameters:
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedColumnNames\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedTableName\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencingColumnNames\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Index\:\:getIndexedColumns\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getPrimaryKeyConstraint\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to method getColumnName\(\) on an unknown class Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
identifier: class.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to method getColumnNames\(\) on an unknown class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint\.$#'
identifier: class.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to method toString\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
identifier: class.notFound
count: 5
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Cannot call method getName\(\) on Doctrine\\DBAL\\Schema\\Column\|false\.$#'
identifier: method.nonObject
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Class Doctrine\\DBAL\\Schema\\Index\\IndexType not found\.$#'
identifier: class.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint not found\.$#'
identifier: class.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\> and Doctrine\\ORM\\Mapping\\ClassMetadata will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
@@ -1392,12 +1452,30 @@ parameters:
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#'
identifier: argument.unresolvableType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#2 \$columnName of method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getFieldNameForColumn\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 4
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \$indexedColumn of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
identifier: class.notFound
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \$name of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
identifier: class.notFound
count: 5
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver\:\:__construct\(\) has parameter \$fileExtension with no type specified\.$#'
identifier: missingType.parameter
@@ -3048,12 +3126,72 @@ parameters:
count: 3
path: src/Tools/SchemaTool.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedColumnNames\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedTableName\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencingColumnNames\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Index\:\:getIndexedColumns\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:dropForeignKey\(\)\.$#'
identifier: method.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to function is_numeric\(\) with int\<0, max\> will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to method getColumnName\(\) on an unknown class Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
identifier: class.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to method getColumnNames\(\) on an unknown class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint\.$#'
identifier: class.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Call to method toString\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
identifier: class.notFound
count: 3
path: src/Tools/SchemaTool.php
-
message: '#^Class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint not found\.$#'
identifier: class.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\DBAL\\Schema\\AbstractSchemaManager\<Doctrine\\DBAL\\Platforms\\AbstractPlatform\>\:\:createComparator\(\) invoked with 1 parameter, 0 required\.$#'
identifier: arguments.count
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:addDiscriminatorColumnDefinition\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
@@ -3156,6 +3294,18 @@ parameters:
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \$indexedColumn of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
identifier: class.notFound
count: 1
path: src/Tools/SchemaTool.php
-
message: '#^Parameter \$name of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
identifier: class.notFound
count: 3
path: src/Tools/SchemaTool.php
-
message: '#^Property Doctrine\\ORM\\Tools\\SchemaTool\:\:\$schemaManager with generic class Doctrine\\DBAL\\Schema\\AbstractSchemaManager does not specify its types\: T$#'
identifier: missingType.generics

View File

@@ -22,6 +22,18 @@ parameters:
message: '~.*getTrimExpression.*expects int.*~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Call to static method unquoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Instantiated class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName not found\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
path: src/Tools/SchemaTool.php
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
# To be removed in 4.0

View File

@@ -20,6 +20,18 @@ parameters:
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '~^Call to static method unquoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Instantiated class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName not found\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
path: src/Tools/SchemaTool.php
# Compatibility with DBAL 3
# See https://github.com/doctrine/dbal/pull/3480
-

View File

@@ -14,6 +14,8 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
failOnNotice="true"
@@ -67,5 +69,12 @@
<var name="privileged_db_port" value="3306"/>
-->
<env name="COLUMNS" value="120"/>
<env name="DOCTRINE_DEPRECATIONS" value="trigger"/>
</php>
<source ignoreSuppressionOfDeprecations="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>

View File

@@ -286,7 +286,7 @@ abstract class AbstractQuery
$key = Parameter::normalizeName($key);
$filteredParameters = $this->parameters->filter(
static fn (Parameter $parameter): bool => $parameter->getName() === $key
static fn (Parameter $parameter): bool => $parameter->getName() === $key,
);
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;

View File

@@ -29,11 +29,14 @@ class CollectionCacheKey extends CacheKey
public readonly string $entityClass,
public readonly string $association,
array $ownerIdentifier,
string $filterHash = '',
) {
ksort($ownerIdentifier);
$this->ownerIdentifier = $ownerIdentifier;
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
$filterHash = $filterHash === '' ? '' : '_' . $filterHash;
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association . $filterHash);
}
}

View File

@@ -18,6 +18,7 @@ use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\UnitOfWork;
use function array_values;
@@ -35,6 +36,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
protected array $queuedCache = [];
protected string $regionName;
protected FilterCollection $filters;
protected CollectionHydrator $hydrator;
protected CacheLogger|null $cacheLogger;
@@ -48,6 +50,10 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
$cacheConfig = $configuration->getSecondLevelCacheConfiguration();
$cacheFactory = $cacheConfig->getCacheFactory();
$this->region = $region;
$this->persister = $persister;
$this->association = $association;
$this->filters = $em->getFilters();
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
@@ -135,7 +141,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
public function count(PersistentCollection $collection): int
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
$entry = $this->region->get($key);
if ($entry !== null) {

View File

@@ -36,7 +36,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
$this->persister->delete($collection);
@@ -53,7 +53,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
}
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
// Invalidate non initialized collections OR ordered collection
if ($isDirty && ! $isInitialized || $this->association->isOrdered()) {

View File

@@ -61,7 +61,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
$lock = $this->region->lock($key);
$this->persister->delete($collection);
@@ -88,7 +88,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$this->persister->update($collection);
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId);
$key = new CollectionCacheKey($this->sourceEntity->rootEntityName, $this->association->fieldName, $ownerId, $this->filters->getHash());
$lock = $this->region->lock($key);
if ($lock === null) {

View File

@@ -24,10 +24,12 @@ use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use function array_merge;
use function func_get_args;
use function serialize;
use function sha1;
@@ -43,6 +45,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
protected TimestampCacheKey $timestampKey;
protected EntityHydrator $hydrator;
protected Cache $cache;
protected FilterCollection $filters;
protected CacheLogger|null $cacheLogger = null;
protected string $regionName;
@@ -64,6 +67,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$cacheFactory = $cacheConfig->getCacheFactory();
$this->cache = $em->getCache();
$this->filters = $em->getFilters();
$this->regionName = $region->getName();
$this->uow = $em->getUnitOfWork();
$this->metadataFactory = $em->getMetadataFactory();
@@ -215,7 +219,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
? $this->persister->expandCriteriaParameters($criteria)
: $this->persister->expandParameters($criteria);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $this->filters->getHash());
}
/**
@@ -472,7 +476,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
$list = $persister->loadCollectionCache($collection, $key);
if ($list !== null) {
@@ -503,7 +507,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
$key = $this->buildCollectionCacheKey($assoc, $ownerId, $this->filters->getHash());
$list = $persister->loadCollectionCache($collection, $key);
if ($list !== null) {
@@ -546,12 +550,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/** @param array<string, mixed> $ownerId */
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId): CollectionCacheKey
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, /* string $filterHash */): CollectionCacheKey
{
$filterHash = (string) (func_get_args()[2] ?? ''); // todo: move to argument in next major release
return new CollectionCacheKey(
$this->metadataFactory->getMetadataFor($association->sourceEntity)->rootEntityName,
$association->fieldName,
$ownerId,
$filterHash,
);
}
}

View File

@@ -18,12 +18,14 @@ use Generator;
use LogicException;
use ReflectionClass;
use function array_key_exists;
use function array_map;
use function array_merge;
use function count;
use function end;
use function in_array;
use function is_array;
use function ksort;
/**
* Base class for all hydrators. A hydrator is a class that provides some form
@@ -263,6 +265,17 @@ abstract class AbstractHydrator
{
$rowData = ['data' => [], 'newObjects' => []];
foreach ($this->rsm->newObjectMappings as $mapping) {
if (! array_key_exists($mapping['objIndex'], $this->rsm->newObject)) {
$this->rsm->newObject[$mapping['objIndex']] = $mapping['className'];
}
}
foreach ($this->rsm->newObject as $objIndex => $newObject) {
$rowData['newObjects'][$objIndex]['class'] = new ReflectionClass($newObject);
$rowData['newObjects'][$objIndex]['args'] = [];
}
foreach ($data as $key => $value) {
$cacheKeyInfo = $this->hydrateColumnInfo($key);
if ($cacheKeyInfo === null) {
@@ -282,7 +295,6 @@ abstract class AbstractHydrator
$value = $this->buildEnum($value, $cacheKeyInfo['enumType']);
}
$rowData['newObjects'][$objIndex]['class'] = $cacheKeyInfo['class'];
$rowData['newObjects'][$objIndex]['args'][$argIndex] = $value;
break;
@@ -336,21 +348,17 @@ abstract class AbstractHydrator
}
}
foreach ($this->resultSetMapping()->nestedNewObjectArguments as $objIndex => ['ownerIndex' => $ownerIndex, 'argIndex' => $argIndex]) {
if (! isset($rowData['newObjects'][$ownerIndex . ':' . $argIndex])) {
continue;
foreach ($this->resultSetMapping()->nestedNewObjectArguments as ['ownerIndex' => $ownerIndex, 'argIndex' => $argIndex, 'argAlias' => $argAlias]) {
if (array_key_exists($argAlias, $rowData['newObjects'])) {
ksort($rowData['newObjects'][$argAlias]['args']);
$rowData['newObjects'][$ownerIndex]['args'][$argIndex] = $rowData['newObjects'][$argAlias]['class']->newInstanceArgs($rowData['newObjects'][$argAlias]['args']);
unset($rowData['newObjects'][$argAlias]);
}
$newObject = $rowData['newObjects'][$ownerIndex . ':' . $argIndex];
unset($rowData['newObjects'][$ownerIndex . ':' . $argIndex]);
$obj = $newObject['class']->newInstanceArgs($newObject['args']);
$rowData['newObjects'][$ownerIndex]['args'][$argIndex] = $obj;
}
foreach ($rowData['newObjects'] as $objIndex => $newObject) {
$obj = $newObject['class']->newInstanceArgs($newObject['args']);
ksort($rowData['newObjects'][$objIndex]['args']);
$obj = $rowData['newObjects'][$objIndex]['class']->newInstanceArgs($rowData['newObjects'][$objIndex]['args']);
$rowData['newObjects'][$objIndex]['obj'] = $obj;
}
@@ -454,7 +462,6 @@ abstract class AbstractHydrator
'type' => Type::getType($this->rsm->typeMappings[$key]),
'argIndex' => $mapping['argIndex'],
'objIndex' => $mapping['objIndex'],
'class' => new ReflectionClass($mapping['className']),
'enumType' => $this->rsm->enumMappings[$key] ?? null,
];

View File

@@ -17,6 +17,7 @@ use function array_search;
use function assert;
use function count;
use function in_array;
use function is_array;
use function key;
use function reset;
use function sprintf;
@@ -138,14 +139,21 @@ class SimpleObjectHydrator extends AbstractHydrator
}
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
$originalValue = $value;
$originalValue = $currentValue = $value;
try {
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
if (! is_array($originalValue)) {
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
} else {
$value = [];
foreach ($originalValue as $i => $currentValue) {
$value[$i] = $this->buildEnum($currentValue, $cacheKeyInfo['enumType']);
}
}
} catch (ValueError $e) {
throw MappingException::invalidEnumValue(
$entityName,
$cacheKeyInfo['fieldName'],
(string) $originalValue,
(string) $currentValue,
$cacheKeyInfo['enumType'],
$e,
);

View File

@@ -57,6 +57,8 @@ use function strtolower;
use function trait_exists;
use function trim;
use const PHP_VERSION_ID;
/**
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
* of an entity and its associations.
@@ -2673,6 +2675,10 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
}
}
if (PHP_VERSION_ID >= 80400 && $reflectionProperty !== null && count($reflectionProperty->getHooks()) > 0) {
throw new LogicException('Doctrine ORM does not support property hooks in this version. Check https://github.com/doctrine/orm/issues/11624 for details of versions that support property hooks.');
}
return $reflectionProperty;
}
}

View File

@@ -24,7 +24,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
public function getColumnName(string $fieldName, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($class->fieldMappings[$fieldName]->quoted)
? $platform->quoteIdentifier($class->fieldMappings[$fieldName]->columnName)
? $platform->quoteSingleIdentifier($class->fieldMappings[$fieldName]->columnName)
: $class->fieldMappings[$fieldName]->columnName;
}
@@ -42,7 +42,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
return isset($class->table['quoted'])
? $platform->quoteIdentifier($tableName)
? $platform->quoteSingleIdentifier($tableName)
: $tableName;
}
@@ -52,14 +52,14 @@ class DefaultQuoteStrategy implements QuoteStrategy
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($definition['quoted'])
? $platform->quoteIdentifier($definition['sequenceName'])
? $platform->quoteSingleIdentifier($definition['sequenceName'])
: $definition['sequenceName'];
}
public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($joinColumn->quoted)
? $platform->quoteIdentifier($joinColumn->name)
? $platform->quoteSingleIdentifier($joinColumn->name)
: $joinColumn->name;
}
@@ -69,7 +69,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
AbstractPlatform $platform,
): string {
return isset($joinColumn->quoted)
? $platform->quoteIdentifier($joinColumn->referencedColumnName)
? $platform->quoteSingleIdentifier($joinColumn->referencedColumnName)
: $joinColumn->referencedColumnName;
}
@@ -87,7 +87,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
$tableName = $association->joinTable->name;
if (isset($association->joinTable->quoted)) {
$tableName = $platform->quoteIdentifier($tableName);
$tableName = $platform->quoteSingleIdentifier($tableName);
}
return $schema . $tableName;
@@ -113,7 +113,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
$joinColumns = $assoc->joinColumns;
$assocQuotedColumnNames = array_map(
static fn (JoinColumnMapping $joinColumn) => isset($joinColumn->quoted)
? $platform->quoteIdentifier($joinColumn->name)
? $platform->quoteSingleIdentifier($joinColumn->name)
: $joinColumn->name,
$joinColumns,
);

View File

@@ -6,6 +6,12 @@ namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Index\IndexedColumn;
use Doctrine\DBAL\Schema\Index\IndexType;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
@@ -21,12 +27,15 @@ use TypeError;
use function array_diff;
use function array_keys;
use function array_map;
use function array_merge;
use function assert;
use function count;
use function current;
use function enum_exists;
use function get_debug_type;
use function in_array;
use function method_exists;
use function preg_replace;
use function sort;
use function sprintf;
@@ -187,7 +196,7 @@ class DatabaseDriver implements MappingDriver
foreach ($this->manyToManyTables as $manyTable) {
foreach ($manyTable->getForeignKeys() as $foreignKey) {
// foreign key maps to the table of the current entity, many to many association probably exists
if (! (strtolower($tableName) === strtolower($foreignKey->getForeignTableName()))) {
if (! (strtolower($tableName) === strtolower(self::getReferencedTableName($foreignKey)))) {
continue;
}
@@ -207,22 +216,22 @@ class DatabaseDriver implements MappingDriver
continue;
}
$localColumn = current($myFk->getLocalColumns());
$localColumn = current(self::getReferencingColumnNames($myFk));
$associationMapping = [];
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current($otherFk->getLocalColumns()), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable($otherFk->getForeignTableName());
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current(self::getReferencingColumnNames($otherFk)), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable(self::getReferencedTableName($otherFk));
if (current($manyTable->getColumns())->getName() === $localColumn) {
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true);
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current(self::getReferencingColumnNames($myFk)), true);
$associationMapping['joinTable'] = [
'name' => strtolower($manyTable->getName()),
'joinColumns' => [],
'inverseJoinColumns' => [],
];
$fkCols = $myFk->getForeignColumns();
$cols = $myFk->getLocalColumns();
$fkCols = self::getReferencedColumnNames($myFk);
$cols = self::getReferencingColumnNames($myFk);
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['joinColumns'][] = [
@@ -231,8 +240,8 @@ class DatabaseDriver implements MappingDriver
];
}
$fkCols = $otherFk->getForeignColumns();
$cols = $otherFk->getLocalColumns();
$fkCols = self::getReferencedColumnNames($otherFk);
$cols = self::getReferencingColumnNames($otherFk);
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['inverseJoinColumns'][] = [
@@ -241,7 +250,7 @@ class DatabaseDriver implements MappingDriver
];
}
} else {
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current($myFk->getLocalColumns()), true);
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current(self::getReferencingColumnNames($myFk)), true);
}
$metadata->mapManyToMany($associationMapping);
@@ -267,10 +276,15 @@ class DatabaseDriver implements MappingDriver
$allForeignKeyColumns = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
$allForeignKeyColumns = array_merge($allForeignKeyColumns, self::getReferencingColumnNames($foreignKey));
}
if (method_exists($table, 'getPrimaryKeyConstraint')) {
$primaryKey = $table->getPrimaryKeyConstraint();
} else {
$primaryKey = $table->getPrimaryKey();
}
$primaryKey = $table->getPrimaryKey();
if ($primaryKey === null) {
throw new MappingException(
'Table ' . $tableName . ' has no primary key. Doctrine does not ' .
@@ -278,7 +292,11 @@ class DatabaseDriver implements MappingDriver
);
}
$pkColumns = $primaryKey->getColumns();
if ($primaryKey instanceof PrimaryKeyConstraint) {
$pkColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKey->getColumnNames());
} else {
$pkColumns = self::getIndexedColumns($primaryKey);
}
sort($pkColumns);
sort($allForeignKeyColumns);
@@ -301,17 +319,25 @@ class DatabaseDriver implements MappingDriver
*/
private function buildIndexes(ClassMetadata $metadata): void
{
$tableName = $metadata->table['name'];
$indexes = $this->tables[$tableName]->getIndexes();
$tableName = $metadata->table['name'];
$table = $this->tables[$tableName];
$primaryKey = self::getPrimaryKey($table);
$indexes = $table->getIndexes();
foreach ($indexes as $index) {
if ($index->isPrimary()) {
if ($index === $primaryKey) {
continue;
}
if (enum_exists(IndexType::class) && method_exists($index, 'getType')) {
$isUnique = $index->getType() === IndexType::UNIQUE;
} else {
$isUnique = $index->isUnique();
}
$indexName = $index->getName();
$indexColumns = $index->getColumns();
$constraintType = $index->isUnique()
$indexColumns = self::getIndexedColumns($index);
$constraintType = $isUnique
? 'uniqueConstraints'
: 'indexes';
@@ -331,7 +357,7 @@ class DatabaseDriver implements MappingDriver
$allForeignKeys = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeys = array_merge($allForeignKeys, $foreignKey->getLocalColumns());
$allForeignKeys = array_merge($allForeignKeys, self::getReferencingColumnNames($foreignKey));
}
$ids = [];
@@ -441,9 +467,9 @@ class DatabaseDriver implements MappingDriver
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
foreach ($foreignKeys as $foreignKey) {
$foreignTableName = $foreignKey->getForeignTableName();
$fkColumns = $foreignKey->getLocalColumns();
$fkForeignColumns = $foreignKey->getForeignColumns();
$foreignTableName = self::getReferencedTableName($foreignKey);
$fkColumns = self::getReferencingColumnNames($foreignKey);
$fkForeignColumns = self::getReferencedColumnNames($foreignKey);
$localColumn = current($fkColumns);
$associationMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true),
@@ -482,7 +508,11 @@ class DatabaseDriver implements MappingDriver
private function getTablePrimaryKeys(Table $table): array
{
try {
return $table->getPrimaryKey()->getColumns();
if (method_exists($table, 'getPrimaryKeyConstraint')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $table->getPrimaryKeyConstraint()->getColumnNames());
}
return self::getIndexedColumns($table->getPrimaryKey());
} catch (SchemaException) {
// Do nothing
}
@@ -527,4 +557,66 @@ class DatabaseDriver implements MappingDriver
return $this->inflector->camelize($columnName);
}
private static function getReferencedTableName(ForeignKeyConstraint $foreignKey): string
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencedTableName')) {
return $foreignKey->getReferencedTableName()->toString();
}
return $foreignKey->getForeignTableName();
}
/** @return string[] */
private static function getReferencingColumnNames(ForeignKeyConstraint $foreignKey): array
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencingColumnNames')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $foreignKey->getReferencingColumnNames());
}
return $foreignKey->getLocalColumns();
}
/** @return string[] */
private static function getReferencedColumnNames(ForeignKeyConstraint $foreignKey): array
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencedColumnNames')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $foreignKey->getReferencedColumnNames());
}
return $foreignKey->getForeignColumns();
}
/** @return string[] */
private static function getIndexedColumns(Index $index): array
{
if (method_exists(Index::class, 'getIndexedColumns')) {
return array_map(static fn (IndexedColumn $indexedColumn) => $indexedColumn->getColumnName()->toString(), $index->getIndexedColumns());
}
return $index->getColumns();
}
private static function getPrimaryKey(Table $table): Index|null
{
$primaryKeyConstraint = null;
if (method_exists(Table::class, 'getPrimaryKeyConstraint')) {
$primaryKeyConstraint = $table->getPrimaryKeyConstraint();
}
foreach ($table->getIndexes() as $index) {
if ($primaryKeyConstraint !== null) {
$primaryKeyConstraintColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKeyConstraint->getColumnNames());
if ($primaryKeyConstraintColumns === self::getIndexedColumns($index)) {
return $index;
}
} elseif ($index->isPrimary()) {
return $index;
}
}
return null;
}
}

View File

@@ -22,8 +22,13 @@ trait ReflectionBasedDriver
*/
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool
{
/** @var class-string $declaringClass */
$declaringClass = $property->class;
if ($this->isTransient($declaringClass)) {
return isset($metadata->fieldMappings[$property->name]);
}
if (
isset($metadata->fieldMappings[$property->name]->declared)
&& $metadata->fieldMappings[$property->name]->declared === $declaringClass

View File

@@ -185,7 +185,7 @@ EXCEPTION
. ' configured to cascade persist operations for entity: ' . self::objToStr($entity) . '.'
. ' To solve this issue: Either explicitly call EntityManager#persist()'
. ' on this unknown entity or configure cascade persist'
. ' this association in the mapping for example @ManyToOne(..,cascade={"persist"}).'
. ' this association in the mapping for example #[ORM\ManyToOne(..., cascade: [\'persist\'])].'
. ($entity instanceof Stringable
? ''
: ' If you cannot find out which entity causes the problem implement \''

View File

@@ -201,6 +201,16 @@ class BasicEntityPersister implements EntityPersister
);
}
final protected function isFilterHashUpToDate(): bool
{
return $this->filterHash === $this->em->getFilters()->getHash();
}
final protected function updateFilterHash(): void
{
$this->filterHash = $this->em->getFilters()->getHash();
}
public function getClassMetadata(): ClassMetadata
{
return $this->class;
@@ -1231,7 +1241,7 @@ class BasicEntityPersister implements EntityPersister
*/
protected function getSelectColumnsSQL(): string
{
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->filterHash === $this->em->getFilters()->getHash()) {
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->isFilterHashUpToDate()) {
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -1334,6 +1344,12 @@ class BasicEntityPersister implements EntityPersister
$joinCondition[] = $this->getSQLTableAlias($association->sourceEntity, $assocAlias) . '.' . $sourceCol . ' = '
. $this->getSQLTableAlias($association->targetEntity) . '.' . $targetCol;
}
// Add filter SQL
$filterSql = $this->generateFilterConditionSQL($eagerEntity, $joinTableAlias);
if ($filterSql) {
$joinCondition[] = $filterSql;
}
}
$this->currentPersisterContext->selectJoinSql .= ' ' . $joinTableName . ' ' . $joinTableAlias . ' ON ';
@@ -1341,7 +1357,7 @@ class BasicEntityPersister implements EntityPersister
}
$this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
$this->filterHash = $this->em->getFilters()->getHash();
$this->updateFilterHash();
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -1500,7 +1516,15 @@ class BasicEntityPersister implements EntityPersister
$tableAlias = $this->getSQLTableAlias($class->name, $root);
$fieldMapping = $class->fieldMappings[$field];
$sql = sprintf('%s.%s', $tableAlias, $this->quoteStrategy->getColumnName($field, $class, $this->platform));
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
$columnAlias = null;
if ($this->currentPersisterContext->rsm->hasColumnAliasByField($alias, $field)) {
$columnAlias = $this->currentPersisterContext->rsm->getColumnAliasByField($alias, $field);
}
if ($columnAlias === null) {
$columnAlias = $this->getSQLColumnAlias($fieldMapping->columnName);
}
$this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field);
if (! empty($fieldMapping->enumType)) {

View File

@@ -358,7 +358,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
protected function getSelectColumnsSQL(): string
{
// Create the column list fragment only once
if ($this->currentPersisterContext->selectColumnListSql !== null) {
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->isFilterHashUpToDate()) {
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -445,6 +445,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
}
$this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
$this->updateFilterHash();
return $this->currentPersisterContext->selectColumnListSql;
}

View File

@@ -35,7 +35,7 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
protected function getSelectColumnsSQL(): string
{
$columnList = [];
if ($this->currentPersisterContext->selectColumnListSql !== null) {
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->isFilterHashUpToDate()) {
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -89,6 +89,7 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
}
$this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
$this->updateFilterHash();
return $this->currentPersisterContext->selectColumnListSql;
}

View File

@@ -233,7 +233,7 @@ EOPHP;
$class = $entityPersister->getClassMetadata();
foreach ($class->getReflectionProperties() as $property) {
if (! $property || isset($identifier[$property->getName()]) || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
if (! $property || isset($identifier[$property->getName()])) {
continue;
}
@@ -262,7 +262,7 @@ EOPHP;
foreach ($reflector->getProperties($filter) as $property) {
$name = $property->name;
if ($property->isStatic() || (($class->hasField($name) || $class->hasAssociation($name)) && ! isset($identifiers[$name]))) {
if ($property->isStatic() || ! isset($identifiers[$name])) {
continue;
}
@@ -383,6 +383,7 @@ EOPHP;
private function generateUseLazyGhostTrait(ClassMetadata $class): string
{
// @phpstan-ignore staticMethod.deprecated (Because we support Symfony < 7.3)
$code = ProxyHelper::generateLazyGhost($class->getReflectionClass());
$code = substr($code, 7 + (int) strpos($code, "\n{"));
$code = substr($code, 0, (int) strpos($code, "\n}"));

View File

@@ -6,6 +6,9 @@ namespace Doctrine\ORM\Query\AST;
use Doctrine\ORM\Query\SqlWalker;
use function func_get_arg;
use function func_num_args;
/**
* NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
*
@@ -13,13 +16,18 @@ use Doctrine\ORM\Query\SqlWalker;
*/
class NewObjectExpression extends Node
{
/** @param mixed[] $args */
/**
* @param class-string $className
* @param mixed[] $args
*/
public function __construct(public string $className, public array $args)
{
}
public function dispatch(SqlWalker $walker): string
public function dispatch(SqlWalker $walker /*, string|null $parentAlias = null */): string
{
return $walker->walkNewObject($this);
$parentAlias = func_num_args() > 1 ? func_get_arg(1) : null;
return $walker->walkNewObject($this, $parentAlias);
}
}

View File

@@ -1767,6 +1767,7 @@ final class Parser
$useNamedArguments = true;
}
/** @var class-string $className */
$className = $this->AbstractSchemaName(); // note that this is not yet validated
$token = $this->lexer->token;
@@ -1854,7 +1855,11 @@ final class Parser
if ($this->lexer->isNextToken(TokenType::T_AS)) {
$this->match(TokenType::T_AS);
$fieldAlias = $this->AliasIdentificationVariable();
$this->match(TokenType::T_IDENTIFIER);
assert($this->lexer->token !== null);
$fieldAlias = $this->lexer->token->value;
}
return $expression;

View File

@@ -66,6 +66,13 @@ class ResultSetMapping
*/
public array $fieldMappings = [];
/**
* Map field names for each class to alias
*
* @var array<class-string, array<string, array<string, string>>>
*/
public array $columnAliasMappings = [];
/**
* Maps column names in the result set to the alias/field name to use in the mapped result.
*
@@ -152,6 +159,13 @@ class ResultSetMapping
*/
public array $newObjectMappings = [];
/**
* Maps object Ids in the result set to classnames.
*
* @phpstan-var array<string|int, class-string>
*/
public array $newObject = [];
/**
* Maps last argument for new objects in order to initiate object construction
*
@@ -328,7 +342,10 @@ class ResultSetMapping
// column name => alias of owner
$this->columnOwnerMap[$columnName] = $alias;
// field name => class name of declaring class
$this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
$declaringClass = $declaringClass ?: $this->aliasMap[$alias];
$this->declaringClasses[$columnName] = $declaringClass;
$this->columnAliasMappings[$declaringClass][$alias][$fieldName] = $columnName;
if (! $this->isMixed && $this->scalarMappings) {
$this->isMixed = true;
@@ -337,6 +354,20 @@ class ResultSetMapping
return $this;
}
public function hasColumnAliasByField(string $alias, string $fieldName): bool
{
$declaringClass = $this->aliasMap[$alias];
return isset($this->columnAliasMappings[$declaringClass][$alias][$fieldName]);
}
public function getColumnAliasByField(string $alias, string $fieldName): string
{
$declaringClass = $this->aliasMap[$alias];
return $this->columnAliasMappings[$declaringClass][$alias][$fieldName];
}
/**
* Adds a joined entity result.
*

View File

@@ -24,10 +24,8 @@ use function array_filter;
use function array_keys;
use function array_map;
use function array_merge;
use function array_pop;
use function assert;
use function count;
use function end;
use function implode;
use function in_array;
use function is_array;
@@ -84,13 +82,6 @@ class SqlWalker
*/
private int $newObjectCounter = 0;
/**
* Contains nesting levels of new objects arguments
*
* @phpstan-var array<int, array{0: string|int, 1: int}>
*/
private array $newObjectStack = [];
private readonly EntityManagerInterface $em;
private readonly Connection $conn;
@@ -1507,14 +1498,7 @@ class SqlWalker
public function walkNewObject(AST\NewObjectExpression $newObjectExpression, string|null $newObjectResultAlias = null): string
{
$sqlSelectExpressions = [];
$objOwner = $objOwnerIdx = null;
if ($this->newObjectStack !== []) {
[$objOwner, $objOwnerIdx] = end($this->newObjectStack);
$objIndex = $objOwner . ':' . $objOwnerIdx;
} else {
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
}
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
foreach ($newObjectExpression->args as $argIndex => $e) {
$resultAlias = $this->scalarResultCounter++;
@@ -1523,10 +1507,8 @@ class SqlWalker
switch (true) {
case $e instanceof AST\NewObjectExpression:
$this->newObjectStack[] = [$objIndex, $argIndex];
$sqlSelectExpressions[] = $e->dispatch($this);
array_pop($this->newObjectStack);
$this->rsm->nestedNewObjectArguments[$columnAlias] = ['ownerIndex' => $objIndex, 'argIndex' => $argIndex];
$sqlSelectExpressions[] = $e->dispatch($this, $columnAlias);
$this->rsm->nestedNewObjectArguments[$columnAlias] = ['ownerIndex' => $objIndex, 'argIndex' => $argIndex, 'argAlias' => $columnAlias];
break;
case $e instanceof AST\Subselect:
@@ -1576,12 +1558,13 @@ class SqlWalker
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
$this->rsm->newObjectMappings[$columnAlias] = [
'className' => $newObjectExpression->className,
'objIndex' => $objIndex,
'argIndex' => $argIndex,
];
}
$this->rsm->newObject[$objIndex] = $newObjectExpression->className;
return implode(', ', $sqlSelectExpressions);
}

View File

@@ -504,7 +504,7 @@ class QueryBuilder implements Stringable
$key = Parameter::normalizeName($key);
$filteredParameters = $this->parameters->filter(
static fn (Parameter $parameter): bool => $key === $parameter->getName()
static fn (Parameter $parameter): bool => $key === $parameter->getName(),
);
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;

View File

@@ -34,7 +34,7 @@ EOT);
protected function execute(InputInterface $input, OutputInterface $output): int
{
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();
$ui = new SymfonyStyle($input, $output);
$entityManager = $this->getEntityManager($input);
@@ -43,7 +43,7 @@ EOT);
->getAllClassNames();
if (! $entityClassNames) {
$ui->caution(
$ui->getErrorStyle()->caution(
[
'You do not have any mapped Doctrine ORM entities according to the current configuration.',
'If you have entities or mapping files you should check your mapping configuration for errors.',

View File

@@ -64,7 +64,7 @@ EOT);
protected function execute(InputInterface $input, OutputInterface $output): int
{
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();
$ui = new SymfonyStyle($input, $output);
$entityManager = $this->getEntityManager($input);
@@ -162,7 +162,7 @@ EOT);
$matches = array_filter(
$this->getMappedEntities($entityManager),
static fn ($mappedEntity) => preg_match('{' . preg_quote($entityName) . '}', $mappedEntity)
static fn ($mappedEntity) => preg_match('{' . preg_quote($entityName) . '}', $mappedEntity),
);
if (! $matches) {

View File

@@ -89,17 +89,24 @@ class LimitSubqueryOutputWalker extends SqlOutputWalker
$this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
$this->rsm = $parserResult->getResultSetMapping();
$query = clone $query;
$cloneQuery = clone $query;
$cloneQuery->setParameters(clone $query->getParameters());
$cloneQuery->setCacheable(false);
foreach ($query->getHints() as $name => $value) {
$cloneQuery->setHint($name, $value);
}
// Reset limit and offset
$this->firstResult = $query->getFirstResult();
$this->maxResults = $query->getMaxResults();
$query->setFirstResult(0)->setMaxResults(null);
$this->firstResult = $cloneQuery->getFirstResult();
$this->maxResults = $cloneQuery->getMaxResults();
$cloneQuery->setFirstResult(0)->setMaxResults(null);
$this->em = $query->getEntityManager();
$this->em = $cloneQuery->getEntityManager();
$this->quoteStrategy = $this->em->getConfiguration()->getQuoteStrategy();
parent::__construct($query, $parserResult, $queryComponents);
parent::__construct($cloneQuery, $parserResult, $queryComponents);
}
/**

View File

@@ -8,7 +8,13 @@ use BackedEnum;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\ComparatorConfig;
use Doctrine\DBAL\Schema\ForeignKeyConstraintEditor;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Index\IndexedColumn;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Table;
use Doctrine\ORM\EntityManagerInterface;
@@ -31,12 +37,15 @@ use function array_diff_key;
use function array_filter;
use function array_flip;
use function array_intersect_key;
use function array_map;
use function assert;
use function class_exists;
use function count;
use function current;
use function implode;
use function in_array;
use function is_numeric;
use function method_exists;
use function strtolower;
/**
@@ -282,7 +291,7 @@ class SchemaTool
}
if ($pkColumns !== []) {
$table->setPrimaryKey($pkColumns);
self::addPrimaryKeyConstraint($table, $pkColumns);
}
}
} else {
@@ -306,7 +315,7 @@ class SchemaTool
}
if (! $table->hasIndex('primary')) {
$table->setPrimaryKey($pkColumns);
self::addPrimaryKeyConstraint($table, $pkColumns);
}
// there can be unique indexes automatically created for join column
@@ -315,7 +324,7 @@ class SchemaTool
$primaryKey = $table->getIndex('primary');
foreach ($table->getIndexes() as $idxKey => $existingIndex) {
if ($primaryKey->overrules($existingIndex)) {
if ($existingIndex !== $primaryKey && $primaryKey->spansColumns(self::getIndexedColumns($existingIndex))) {
$table->dropIndex($idxKey);
}
}
@@ -346,7 +355,7 @@ class SchemaTool
}
}
$table->addUniqueIndex($uniqIndex->getColumns(), is_numeric($indexName) ? null : $indexName, $indexData['options'] ?? []);
$table->addUniqueIndex(self::getIndexedColumns($uniqIndex), is_numeric($indexName) ? null : $indexName, $indexData['options'] ?? []);
}
}
@@ -572,7 +581,7 @@ class SchemaTool
$blacklistedFks,
);
$theJoinTable->setPrimaryKey($primaryKeyColumns);
self::addPrimaryKeyConstraint($theJoinTable, $primaryKeyColumns);
}
}
}
@@ -725,7 +734,18 @@ class SchemaTool
) {
foreach ($theJoinTable->getForeignKeys() as $fkName => $key) {
if (
count(array_diff($key->getLocalColumns(), $localColumns)) === 0
class_exists(ForeignKeyConstraintEditor::class)
&& count(array_diff(array_map(static fn (UnqualifiedName $name) => $name->toString(), $key->getReferencingColumnNames()), $localColumns)) === 0
&& (($key->getReferencedTableName()->toString() !== $foreignTableName)
|| 0 < count(array_diff(array_map(static fn (UnqualifiedName $name) => $name->toString(), $key->getReferencedColumnNames()), $foreignColumns)))
) {
$theJoinTable->dropForeignKey($fkName);
break;
}
if (
! class_exists(ForeignKeyConstraintEditor::class)
&& count(array_diff($key->getLocalColumns(), $localColumns)) === 0
&& (($key->getForeignTableName() !== $foreignTableName)
|| 0 < count(array_diff($key->getForeignColumns(), $foreignColumns)))
) {
@@ -843,12 +863,22 @@ class SchemaTool
}
foreach ($schema->getTables() as $table) {
$primaryKey = $table->getPrimaryKey();
if (method_exists($table, 'getPrimaryKeyConstraint')) {
$primaryKey = $table->getPrimaryKeyConstraint();
} else {
$primaryKey = $table->getPrimaryKey();
}
if ($primaryKey === null) {
continue;
}
$columns = $primaryKey->getColumns();
if ($primaryKey instanceof PrimaryKeyConstraint) {
$columns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKey->getColumnNames());
} else {
$columns = self::getIndexedColumns($primaryKey);
}
if (count($columns) === 1) {
$checkSequence = $table->getName() . '_' . $columns[0] . '_seq';
if ($deployedSchema->hasSequence($checkSequence) && ! $schema->hasSequence($checkSequence)) {
@@ -888,7 +918,13 @@ class SchemaTool
{
$toSchema = $this->getSchemaFromMetadata($classes);
$fromSchema = $this->createSchemaForComparison($toSchema);
$comparator = $this->schemaManager->createComparator();
if (class_exists(ComparatorConfig::class)) {
$comparator = $this->schemaManager->createComparator((new ComparatorConfig())->withReportModifiedIndexes(false));
} else {
$comparator = $this->schemaManager->createComparator();
}
$schemaDiff = $comparator->compareSchemas($fromSchema, $toSchema);
return $this->platform->getAlterSchemaSQL($schemaDiff);
@@ -923,4 +959,30 @@ class SchemaTool
$config->setSchemaAssetsFilter($previousFilter);
}
}
/** @param string[] $primaryKeyColumns */
private function addPrimaryKeyConstraint(Table $table, array $primaryKeyColumns): void
{
if (class_exists(PrimaryKeyConstraint::class)) {
$primaryKeyColumnNames = [];
foreach ($primaryKeyColumns as $primaryKeyColumn) {
$primaryKeyColumnNames[] = new UnqualifiedName(Identifier::unquoted($primaryKeyColumn));
}
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, $primaryKeyColumnNames, true));
} else {
$table->setPrimaryKey($primaryKeyColumns);
}
}
/** @return string[] */
private static function getIndexedColumns(Index $index): array
{
if (method_exists(Index::class, 'getIndexedColumns')) {
return array_map(static fn (IndexedColumn $indexedColumn) => $indexedColumn->getColumnName()->toString(), $index->getIndexedColumns());
}
return $index->getColumns();
}
}

View File

@@ -49,6 +49,7 @@ use Exception;
use InvalidArgumentException;
use RuntimeException;
use Stringable;
use Symfony\Component\VarExporter\Hydrator;
use UnexpectedValueException;
use function array_chunk;
@@ -2024,7 +2025,7 @@ class UnitOfWork implements PropertyChangedListener
$associationMappings = array_filter(
$class->associationMappings,
static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRefresh()
static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRefresh(),
);
foreach ($associationMappings as $assoc) {
@@ -2065,7 +2066,7 @@ class UnitOfWork implements PropertyChangedListener
$associationMappings = array_filter(
$class->associationMappings,
static fn (AssociationMapping $assoc): bool => $assoc->isCascadeDetach()
static fn (AssociationMapping $assoc): bool => $assoc->isCascadeDetach(),
);
foreach ($associationMappings as $assoc) {
@@ -2111,7 +2112,7 @@ class UnitOfWork implements PropertyChangedListener
$associationMappings = array_filter(
$class->associationMappings,
static fn (AssociationMapping $assoc): bool => $assoc->isCascadePersist()
static fn (AssociationMapping $assoc): bool => $assoc->isCascadePersist(),
);
foreach ($associationMappings as $assoc) {
@@ -2168,7 +2169,7 @@ class UnitOfWork implements PropertyChangedListener
$associationMappings = array_filter(
$class->associationMappings,
static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRemove()
static fn (AssociationMapping $assoc): bool => $assoc->isCascadeRemove(),
);
if ($associationMappings) {
@@ -2379,6 +2380,8 @@ class UnitOfWork implements PropertyChangedListener
if ($this->isUninitializedObject($entity)) {
$entity->__setInitialized(true);
Hydrator::hydrate($entity, (array) $class->reflClass->newInstanceWithoutConstructor());
} else {
if (
! isset($hints[Query::HINT_REFRESH])

View File

@@ -6,7 +6,7 @@ namespace Doctrine\Tests\Models\CMS;
class CmsAddressDTO
{
public function __construct(public string|null $country = null, public string|null $city = null, public string|null $zip = null, public CmsAddressDTO|string|null $address = null)
public function __construct(public string|null $country = null, public string|null $city = null, public string|null $zip = null, public string|null $address = null, public CmsDumbDTO|null $other = null)
{
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\CMS;
class CmsDumbDTO
{
public function __construct(
public mixed $val1 = null,
public mixed $val2 = null,
public mixed $val3 = null,
public mixed $val4 = null,
public mixed $val5 = null,
) {
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH11524;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'gh11524_entities')]
class GH11524Entity
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
public int|null $id = null;
#[ORM\ManyToOne(targetEntity: GH11524Relation::class)]
#[ORM\JoinColumn(name: 'relation_id', referencedColumnName: 'id', nullable: true)]
public GH11524Relation|null $relation = null;
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH11524;
use Doctrine\ORM\Event\PostLoadEventArgs;
class GH11524Listener
{
public function postLoad(PostloadEventArgs $eventArgs): void
{
$object = $eventArgs->getObject();
if (! $object instanceof GH11524Relation) {
return;
}
$object->setCurrentLocale('en');
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\GH11524;
use Doctrine\ORM\Mapping as ORM;
use LogicException;
#[ORM\Entity]
#[ORM\Table(name: 'gh11524_relations')]
class GH11524Relation
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
public int|null $id;
#[ORM\Column(type: 'string')]
public string $name;
private string|null $currentLocale;
public function setCurrentLocale(string $locale): void
{
$this->currentLocale = $locale;
}
public function getTranslation(): string
{
if ($this->currentLocale === null) {
throw new LogicException('The current locale must be set to retrieve translation.');
}
return 'fake';
}
}

View File

@@ -8,10 +8,14 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Table;
use PHPUnit\Framework\Attributes\Group;
use function array_change_key_case;
use function class_exists;
use function count;
use function strtolower;
@@ -36,12 +40,24 @@ class DatabaseDriverTest extends DatabaseDriverTestCase
{
$user = new Table('ddc2059_user');
$user->addColumn('id', 'integer');
$user->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$user->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$user->setPrimaryKey(['id']);
}
$project = new Table('ddc2059_project');
$project->addColumn('id', 'integer');
$project->addColumn('user_id', 'integer');
$project->addColumn('user', 'string');
$project->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$project->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$project->setPrimaryKey(['id']);
}
$project->addForeignKeyConstraint('ddc2059_user', ['user_id'], ['id']);
$metadata = $this->convertToClassMetadata([$project, $user], []);
@@ -54,7 +70,13 @@ class DatabaseDriverTest extends DatabaseDriverTestCase
{
$table = new Table('dbdriver_foo');
$table->addColumn('id', 'integer');
$table->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$table->setPrimaryKey(['id']);
}
$table->addColumn('bar', 'string', ['notnull' => false, 'length' => 200]);
$this->dropAndCreateTable($table);
@@ -81,13 +103,24 @@ class DatabaseDriverTest extends DatabaseDriverTestCase
{
$tableB = new Table('dbdriver_bar');
$tableB->addColumn('id', 'integer');
$tableB->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$tableB->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$tableB->setPrimaryKey(['id']);
}
$this->dropAndCreateTable($tableB);
$tableA = new Table('dbdriver_baz');
$tableA->addColumn('id', 'integer');
$tableA->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$tableA->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$tableA->setPrimaryKey(['id']);
}
$tableA->addColumn('bar_id', 'integer');
$tableA->addForeignKeyConstraint('dbdriver_bar', ['bar_id'], ['id']);
@@ -127,11 +160,21 @@ class DatabaseDriverTest extends DatabaseDriverTestCase
{
$tableB = new Table('dbdriver_bar');
$tableB->addColumn('id', 'integer');
$tableB->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$tableB->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$tableB->setPrimaryKey(['id']);
}
$tableA = new Table('dbdriver_baz');
$tableA->addColumn('id', 'integer');
$tableA->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$tableA->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$tableA->setPrimaryKey(['id']);
}
$tableMany = new Table('dbdriver_bar_baz');
$tableMany->addColumn('bar_id', 'integer');
@@ -148,7 +191,13 @@ class DatabaseDriverTest extends DatabaseDriverTestCase
$table = new Table('dbdriver_foo');
$table->addColumn('id', 'integer', ['unsigned' => true]);
$table->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$table->setPrimaryKey(['id']);
}
$table->addColumn('column_unsigned', 'integer', ['unsigned' => true]);
$table->addColumn('column_comment', 'string', ['length' => 16, 'comment' => 'test_comment']);
$table->addColumn('column_default', 'string', ['length' => 16, 'default' => 'test_default']);

View File

@@ -8,9 +8,11 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Doctrine\Tests\OrmFunctionalTestCase;
use function count;
use function iterator_to_array;
class EagerFetchCollectionTest extends OrmFunctionalTestCase
{
@@ -97,6 +99,16 @@ class EagerFetchCollectionTest extends OrmFunctionalTestCase
$this->assertIsString($query->getSql());
}
public function testSubselectFetchJoinWithAllowedWhenOverriddenNotEagerPaginator(): void
{
$query = $this->_em->createQuery('SELECT o, c FROM ' . EagerFetchOwner::class . ' o JOIN o.children c WITH c.id = 1');
$query->setMaxResults(1);
$query->setFetchMode(EagerFetchChild::class, 'owner', ORM\ClassMetadata::FETCH_LAZY);
$paginator = new Paginator($query, true);
$this->assertIsArray(iterator_to_array($paginator));
}
public function testEagerFetchWithIterable(): void
{
$this->createOwnerWithChildren(2);

View File

@@ -66,10 +66,11 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
self::assertTrue($entity->postPersistCallbackInvoked);
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$query = $this->_em->createQuery('select e from Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity e');
$result = $query->getResult();
self::assertTrue($result[0]->postLoadCallbackInvoked);
self::assertTrue($result[0]::$postLoadCallbackInvoked);
$result[0]->value = 'hello again';
@@ -130,12 +131,14 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
$id = $entity->getId();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$reference = $this->_em->getReference(LifecycleCallbackTestEntity::class, $id);
self::assertFalse($reference->postLoadCallbackInvoked);
self::assertFalse($reference::$postLoadCallbackInvoked);
$this->assertTrue($this->isUninitializedObject($reference));
$reference->getValue(); // trigger proxy load
self::assertTrue($reference->postLoadCallbackInvoked);
self::assertTrue($reference::$postLoadCallbackInvoked);
}
#[Group('DDC-958')]
@@ -148,13 +151,14 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
$id = $entity->getId();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$reference = $this->_em->find(LifecycleCallbackTestEntity::class, $id);
self::assertTrue($reference->postLoadCallbackInvoked);
$reference->postLoadCallbackInvoked = false;
self::assertTrue($reference::$postLoadCallbackInvoked);
$reference::$postLoadCallbackInvoked = false;
$this->_em->refresh($reference);
self::assertTrue($reference->postLoadCallbackInvoked, 'postLoad should be invoked when refresh() is called.');
self::assertTrue($reference::$postLoadCallbackInvoked, 'postLoad should be invoked when refresh() is called.');
}
#[Group('DDC-113')]
@@ -195,6 +199,7 @@ class LifecycleCallbackTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$dql = <<<'DQL'
SELECT
@@ -212,9 +217,9 @@ DQL;
->createQuery(sprintf($dql, $e1->getId(), $e2->getId()))
->getResult();
self::assertTrue(current($entities)->postLoadCallbackInvoked);
self::assertTrue(current($entities)::$postLoadCallbackInvoked);
self::assertTrue(current($entities)->postLoadCascaderNotNull);
self::assertTrue(current($entities)->cascader->postLoadCallbackInvoked);
self::assertTrue(current($entities)->cascader::$postLoadCallbackInvoked);
self::assertEquals(current($entities)->cascader->postLoadEntitiesCount, 2);
}
@@ -235,6 +240,8 @@ DQL;
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
LifecycleCallbackCascader::$postLoadCallbackInvoked = false;
$dql = <<<'DQL'
SELECT
@@ -252,7 +259,7 @@ DQL;
$iterableResult = iterator_to_array($query->toIterable());
foreach ($iterableResult as $entity) {
self::assertTrue($entity->postLoadCallbackInvoked);
self::assertTrue($entity::$postLoadCallbackInvoked);
self::assertFalse($entity->postLoadCascaderNotNull);
break;
@@ -268,15 +275,15 @@ DQL;
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
$query = $this->_em->createQuery(
$query = $this->_em->createQuery(
'SELECT e FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity AS e',
);
$result = iterator_to_array($query->toIterable([], Query::HYDRATE_SIMPLEOBJECT));
foreach ($result as $entity) {
self::assertTrue($entity->postLoadCallbackInvoked);
self::assertTrue($entity::$postLoadCallbackInvoked);
self::assertFalse($entity->postLoadCascaderNotNull);
break;
@@ -301,6 +308,8 @@ DQL;
$this->_em->flush();
$this->_em->clear();
LifecycleCallbackTestEntity::$postLoadCallbackInvoked = false; // Reset the tracking of the postLoad invocation
LifecycleCallbackCascader::$postLoadCallbackInvoked = false;
$dql = <<<'DQL'
SELECT
@@ -318,9 +327,9 @@ DQL;
->createQuery($dql)->setParameter('entA_id', $entA->getId())
->getOneOrNullResult();
self::assertTrue($fetchedA->postLoadCallbackInvoked);
self::assertTrue($fetchedA::$postLoadCallbackInvoked);
foreach ($fetchedA->entities as $fetchJoinedEntB) {
self::assertTrue($fetchJoinedEntB->postLoadCallbackInvoked);
self::assertTrue($fetchJoinedEntB::$postLoadCallbackInvoked);
}
}
@@ -455,7 +464,7 @@ class LifecycleCallbackTestEntity
public $postPersistCallbackInvoked = false;
/** @var bool */
public $postLoadCallbackInvoked = false;
public static $postLoadCallbackInvoked = false;
/** @var bool */
public $postLoadCascaderNotNull = false;
@@ -502,7 +511,7 @@ class LifecycleCallbackTestEntity
#[PostLoad]
public function doStuffOnPostLoad(): void
{
$this->postLoadCallbackInvoked = true;
self::$postLoadCallbackInvoked = true;
$this->postLoadCascaderNotNull = isset($this->cascader);
}
@@ -526,7 +535,7 @@ class LifecycleCallbackCascader
{
/* test stuff */
/** @var bool */
public $postLoadCallbackInvoked = false;
public static $postLoadCallbackInvoked = false;
/** @var int */
public $postLoadEntitiesCount = 0;
@@ -548,7 +557,7 @@ class LifecycleCallbackCascader
#[PostLoad]
public function doStuffOnPostLoad(): void
{
$this->postLoadCallbackInvoked = true;
self::$postLoadCallbackInvoked = true;
$this->postLoadEntitiesCount = count($this->entities);
}

View File

@@ -11,6 +11,7 @@ use Doctrine\ORM\Query\QueryException;
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\CmsEmail;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
use Doctrine\Tests\Models\CMS\CmsUser;
@@ -1031,7 +1032,8 @@ class NewOperatorTest extends OrmFunctionalTestCase
a.country,
a.city,
a.zip,
new CmsAddressDTO(
\'Abbey Road\',
new CmsDumbDTO(
a.country,
a.city,
a.zip
@@ -1078,6 +1080,18 @@ class NewOperatorTest extends OrmFunctionalTestCase
self::assertSame($this->fixtures[1]->address->country, $result[1]['user']->address->country);
self::assertSame($this->fixtures[2]->address->country, $result[2]['user']->address->country);
self::assertInstanceOf(CmsDumbDTO::class, $result[0]['user']->address->other);
self::assertInstanceOf(CmsDumbDTO::class, $result[1]['user']->address->other);
self::assertInstanceOf(CmsDumbDTO::class, $result[2]['user']->address->other);
self::assertSame($this->fixtures[0]->address->country, $result[0]['user']->address->other->val1);
self::assertSame($this->fixtures[1]->address->country, $result[1]['user']->address->other->val1);
self::assertSame($this->fixtures[2]->address->country, $result[2]['user']->address->other->val1);
self::assertSame($this->fixtures[0]->address->city, $result[0]['user']->address->other->val2);
self::assertSame($this->fixtures[1]->address->city, $result[1]['user']->address->other->val2);
self::assertSame($this->fixtures[2]->address->city, $result[2]['user']->address->other->val2);
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']);
@@ -1087,6 +1101,135 @@ class NewOperatorTest extends OrmFunctionalTestCase
self::assertSame($this->fixtures[2]->username, $result[2]['cmsUserUsername']);
}
public function testShouldSupportNestedNewOperatorsWithDtoFirst(): void
{
$dql = '
SELECT
new CmsUserDTO(
u.name,
e.email,
new CmsAddressDTO(
a.country,
a.city,
a.zip
),
555812452
) as user,
u.status,
u.username as cmsUserUsername
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(CmsUserDTO::class, $result[0]['user']);
self::assertInstanceOf(CmsUserDTO::class, $result[1]['user']);
self::assertInstanceOf(CmsUserDTO::class, $result[2]['user']);
self::assertInstanceOf(CmsAddressDTO::class, $result[0]['user']->address);
self::assertInstanceOf(CmsAddressDTO::class, $result[1]['user']->address);
self::assertInstanceOf(CmsAddressDTO::class, $result[2]['user']->address);
self::assertSame($this->fixtures[0]->name, $result[0]['user']->name);
self::assertSame($this->fixtures[1]->name, $result[1]['user']->name);
self::assertSame($this->fixtures[2]->name, $result[2]['user']->name);
self::assertSame($this->fixtures[0]->email->email, $result[0]['user']->email);
self::assertSame($this->fixtures[1]->email->email, $result[1]['user']->email);
self::assertSame($this->fixtures[2]->email->email, $result[2]['user']->email);
self::assertSame($this->fixtures[0]->address->city, $result[0]['user']->address->city);
self::assertSame($this->fixtures[1]->address->city, $result[1]['user']->address->city);
self::assertSame($this->fixtures[2]->address->city, $result[2]['user']->address->city);
self::assertSame($this->fixtures[0]->address->country, $result[0]['user']->address->country);
self::assertSame($this->fixtures[1]->address->country, $result[1]['user']->address->country);
self::assertSame($this->fixtures[2]->address->country, $result[2]['user']->address->country);
self::assertSame(555812452, $result[0]['user']->phonenumbers);
self::assertSame(555812452, $result[1]['user']->phonenumbers);
self::assertSame(555812452, $result[2]['user']->phonenumbers);
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]['cmsUserUsername']);
self::assertSame($this->fixtures[1]->username, $result[1]['cmsUserUsername']);
self::assertSame($this->fixtures[2]->username, $result[2]['cmsUserUsername']);
}
public function testOnlyObjectInObject(): void
{
$dql = '
SELECT
new CmsDumbDTO(
new CmsDumbDTO(
u.name,
e.email
),
new CmsAddressDTO(
a.country,
a.city,
a.zip
)
) as user
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::assertInstanceOf(CmsDumbDTO::class, $result[0]->val1);
self::assertInstanceOf(CmsDumbDTO::class, $result[1]->val1);
self::assertInstanceOf(CmsDumbDTO::class, $result[2]->val1);
self::assertSame($this->fixtures[0]->name, $result[0]->val1->val1);
self::assertSame($this->fixtures[1]->name, $result[1]->val1->val1);
self::assertSame($this->fixtures[2]->name, $result[2]->val1->val1);
self::assertSame($this->fixtures[0]->email->email, $result[0]->val1->val2);
self::assertSame($this->fixtures[1]->email->email, $result[1]->val1->val2);
self::assertSame($this->fixtures[2]->email->email, $result[2]->val1->val2);
self::assertInstanceOf(CmsAddressDTO::class, $result[0]->val2);
self::assertInstanceOf(CmsAddressDTO::class, $result[1]->val2);
self::assertInstanceOf(CmsAddressDTO::class, $result[2]->val2);
self::assertSame($this->fixtures[0]->address->country, $result[0]->val2->country);
self::assertSame($this->fixtures[1]->address->country, $result[1]->val2->country);
self::assertSame($this->fixtures[2]->address->country, $result[2]->val2->country);
self::assertSame($this->fixtures[0]->address->city, $result[0]->val2->city);
self::assertSame($this->fixtures[1]->address->city, $result[1]->val2->city);
self::assertSame($this->fixtures[2]->address->city, $result[2]->val2->city);
self::assertSame($this->fixtures[0]->address->zip, $result[0]->val2->zip);
self::assertSame($this->fixtures[1]->address->zip, $result[1]->val2->zip);
self::assertSame($this->fixtures[2]->address->zip, $result[2]->val2->zip);
}
public function testNamedArguments(): void
{
$dql = <<<'SQL'
@@ -1159,6 +1302,111 @@ class NewOperatorTest extends OrmFunctionalTestCase
self::assertSame($this->fixtures[2]->username, $result[2]['cmsUserUsername']);
}
public function testShouldSupportNestedNewOperatorsWithNestedDtoNotLast(): void
{
$dql = '
SELECT
new CmsUserDTO(
u.name,
e.email,
new CmsAddressDTO(
a.country,
a.city,
a.zip,
\'Abbey Road\',
new CmsDumbDTO(
a.country,
a.city,
new CmsDumbDTO(
a.zip,
456
),
a.zip
)
),
555812452
) as user,
u.status,
u.username as cmsUserUsername
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(CmsUserDTO::class, $result[0]['user']);
self::assertInstanceOf(CmsUserDTO::class, $result[1]['user']);
self::assertInstanceOf(CmsUserDTO::class, $result[2]['user']);
self::assertSame($this->fixtures[0]->name, $result[0]['user']->name);
self::assertSame($this->fixtures[1]->name, $result[1]['user']->name);
self::assertSame($this->fixtures[2]->name, $result[2]['user']->name);
self::assertSame($this->fixtures[0]->email->email, $result[0]['user']->email);
self::assertSame($this->fixtures[1]->email->email, $result[1]['user']->email);
self::assertSame($this->fixtures[2]->email->email, $result[2]['user']->email);
self::assertInstanceOf(CmsAddressDTO::class, $result[0]['user']->address);
self::assertInstanceOf(CmsAddressDTO::class, $result[1]['user']->address);
self::assertInstanceOf(CmsAddressDTO::class, $result[2]['user']->address);
self::assertSame($this->fixtures[0]->address->country, $result[0]['user']->address->country);
self::assertSame($this->fixtures[1]->address->country, $result[1]['user']->address->country);
self::assertSame($this->fixtures[2]->address->country, $result[2]['user']->address->country);
self::assertSame($this->fixtures[2]->address->city, $result[2]['user']->address->city);
self::assertSame($this->fixtures[0]->address->city, $result[0]['user']->address->city);
self::assertSame($this->fixtures[1]->address->city, $result[1]['user']->address->city);
self::assertInstanceOf(CmsDumbDTO::class, $result[0]['user']->address->other);
self::assertInstanceOf(CmsDumbDTO::class, $result[1]['user']->address->other);
self::assertInstanceOf(CmsDumbDTO::class, $result[2]['user']->address->other);
self::assertSame($this->fixtures[0]->address->country, $result[0]['user']->address->other->val1);
self::assertSame($this->fixtures[1]->address->country, $result[1]['user']->address->other->val1);
self::assertSame($this->fixtures[2]->address->country, $result[2]['user']->address->other->val1);
self::assertSame($this->fixtures[0]->address->city, $result[0]['user']->address->other->val2);
self::assertSame($this->fixtures[1]->address->city, $result[1]['user']->address->other->val2);
self::assertSame($this->fixtures[2]->address->city, $result[2]['user']->address->other->val2);
self::assertInstanceOf(CmsDumbDTO::class, $result[0]['user']->address->other->val3);
self::assertInstanceOf(CmsDumbDTO::class, $result[1]['user']->address->other->val3);
self::assertInstanceOf(CmsDumbDTO::class, $result[2]['user']->address->other->val3);
self::assertSame($this->fixtures[0]->address->zip, $result[0]['user']->address->other->val3->val1);
self::assertSame($this->fixtures[1]->address->zip, $result[1]['user']->address->other->val3->val1);
self::assertSame($this->fixtures[2]->address->zip, $result[2]['user']->address->other->val3->val1);
self::assertSame(456, $result[0]['user']->address->other->val3->val2);
self::assertSame(456, $result[1]['user']->address->other->val3->val2);
self::assertSame(456, $result[2]['user']->address->other->val3->val2);
self::assertSame($this->fixtures[0]->address->zip, $result[0]['user']->address->other->val4);
self::assertSame($this->fixtures[1]->address->zip, $result[1]['user']->address->other->val4);
self::assertSame($this->fixtures[2]->address->zip, $result[2]['user']->address->other->val4);
self::assertSame(555812452, $result[0]['user']->phonenumbers);
self::assertSame(555812452, $result[1]['user']->phonenumbers);
self::assertSame(555812452, $result[2]['user']->phonenumbers);
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]['cmsUserUsername']);
self::assertSame($this->fixtures[1]->username, $result[1]['cmsUserUsername']);
self::assertSame($this->fixtures[2]->username, $result[2]['cmsUserUsername']);
}
public function testVariadicArgument(): void
{
$dql = <<<'SQL'

View File

@@ -12,6 +12,7 @@ use Doctrine\ORM\Query\AST\Literal;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\AST\SelectStatement;
use Doctrine\ORM\Query\AST\WhereClause;
use Doctrine\ORM\Query\SqlOutputWalker;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use Doctrine\ORM\Tools\Pagination\Paginator;
@@ -643,7 +644,7 @@ class PaginationTest extends OrmFunctionalTestCase
self::assertCount(2, $getCountQuery->invoke($paginator)->getParameters());
self::assertCount(9, $paginator);
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SqlWalker::class);
$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SqlOutputWalker::class);
$paginator = new Paginator($query);

View File

@@ -14,6 +14,7 @@ use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Tests\OrmFunctionalTestCase;
use Generator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
use ReflectionMethod;
use Symfony\Component\VarExporter\Instantiator;
use Symfony\Component\VarExporter\VarExporter;
@@ -34,6 +35,7 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
/** @param Closure(ParserResult): ParserResult $toSerializedAndBack */
#[DataProvider('provideToSerializedAndBack')]
#[WithoutErrorHandler]
public function testSerializeParserResultForQueryWithSqlWalker(Closure $toSerializedAndBack): void
{
$query = $this->_em

View File

@@ -538,7 +538,9 @@ class QueryTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$data = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u')
$query = 'SELECT u FROM ' . CmsUser::class . ' u ORDER BY u.username';
$data = $this->_em->createQuery($query)
->setFirstResult(1)
->setMaxResults(2)
->getResult();
@@ -547,7 +549,7 @@ class QueryTest extends OrmFunctionalTestCase
self::assertEquals('gblanco1', $data[0]->username);
self::assertEquals('gblanco2', $data[1]->username);
$data = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u')
$data = $this->_em->createQuery($query)
->setFirstResult(3)
->setMaxResults(2)
->getResult();
@@ -556,7 +558,7 @@ class QueryTest extends OrmFunctionalTestCase
self::assertEquals('gblanco3', $data[0]->username);
self::assertEquals('gblanco4', $data[1]->username);
$data = $this->_em->createQuery('SELECT u FROM ' . CmsUser::class . ' u')
$data = $this->_em->createQuery($query)
->setFirstResult(3)
->setMaxResults(2)
->getScalarResult();

View File

@@ -503,6 +503,21 @@ class SQLFilterTest extends OrmFunctionalTestCase
self::assertEquals(2, count($query->getResult()));
}
public function testOneToOneInverseSideWithFilter(): void
{
$this->loadFixtureData();
$conf = $this->_em->getConfiguration();
$conf->addFilter('country', '\Doctrine\Tests\ORM\Functional\CMSCountryFilter');
$this->_em->getFilters()->enable('country')->setParameterList('country', ['Germany'], Types::STRING);
$user = $this->_em->find(CmsUser::class, $this->userId);
self::assertNotEmpty($user->address);
$user2 = $this->_em->find(CmsUser::class, $this->userId2);
self::assertEmpty($user2->address);
}
public function testManyToManyFilter(): void
{
$this->loadFixtureData();

View File

@@ -5,11 +5,13 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\SchemaTool;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\DBAL\Schema\ComparatorConfig;
use Doctrine\Tests\Models;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function array_filter;
use function class_exists;
use function implode;
use function str_contains;
@@ -68,7 +70,13 @@ class DDC214Test extends OrmFunctionalTestCase
$fromSchema = $sm->introspectSchema();
$toSchema = $this->getSchemaForModels(...$classes);
$comparator = $sm->createComparator();
if (class_exists(ComparatorConfig::class)) {
$comparator = $sm->createComparator((new ComparatorConfig())->withReportModifiedIndexes(false));
} else {
$comparator = $sm->createComparator();
}
$schemaDiff = $comparator->compareSchemas($fromSchema, $toSchema);
$sql = $this->_em->getConnection()->getDatabasePlatform()->getAlterSchemaSQL($schemaDiff);

View File

@@ -50,24 +50,58 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
$tool = new SchemaTool($this->_em);
$sql = $tool->getCreateSchemaSql($classes);
self::assertEquals('CREATE TABLE cms_groups (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[0]);
self::assertThat($sql[0], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE cms_groups (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_groups (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(50) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertThat($sql[1], self::logicalOr(
// DBAL 3
self::equalTo('CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, email_id INT DEFAULT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4 (see https://github.com/doctrine/dbal/pull/4777)
self::equalTo('CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_users (id INT AUTO_INCREMENT NOT NULL, status VARCHAR(50) DEFAULT NULL, username VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_3AF03EC5F85E0677 (username), UNIQUE INDEX UNIQ_3AF03EC5A832C1C9 (email_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertThat($sql[2], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY (user_id, group_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertThat($sql[3], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE cms_users_tags (user_id INT NOT NULL, tag_id INT NOT NULL, INDEX IDX_93F5A1ADA76ED395 (user_id), INDEX IDX_93F5A1ADBAD26311 (tag_id), PRIMARY KEY(user_id, tag_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_users_tags (user_id INT NOT NULL, tag_id INT NOT NULL, INDEX IDX_93F5A1ADA76ED395 (user_id), INDEX IDX_93F5A1ADBAD26311 (tag_id), PRIMARY KEY (user_id, tag_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertThat($sql[4], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE cms_tags (id INT AUTO_INCREMENT NOT NULL, tag_name VARCHAR(50) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_tags (id INT AUTO_INCREMENT NOT NULL, tag_name VARCHAR(50) DEFAULT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertEquals('CREATE TABLE cms_users_groups (user_id INT NOT NULL, group_id INT NOT NULL, INDEX IDX_7EA9409AA76ED395 (user_id), INDEX IDX_7EA9409AFE54D947 (group_id), PRIMARY KEY(user_id, group_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[2]);
self::assertEquals('CREATE TABLE cms_users_tags (user_id INT NOT NULL, tag_id INT NOT NULL, INDEX IDX_93F5A1ADA76ED395 (user_id), INDEX IDX_93F5A1ADBAD26311 (tag_id), PRIMARY KEY(user_id, tag_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[3]);
self::assertEquals('CREATE TABLE cms_tags (id INT AUTO_INCREMENT NOT NULL, tag_name VARCHAR(50) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[4]);
self::assertThat($sql[5], self::logicalOr(
// DBAL 3
self::equalTo('CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4 (see https://github.com/doctrine/dbal/pull/4777)
self::equalTo('CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_addresses (id INT AUTO_INCREMENT NOT NULL, country VARCHAR(50) NOT NULL, zip VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, UNIQUE INDEX UNIQ_ACAC157BA76ED395 (user_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertThat($sql[6], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE cms_emails (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(250) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_emails (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(250) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertThat($sql[7], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY (phonenumber)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertEquals('CREATE TABLE cms_emails (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(250) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[6]);
self::assertEquals('CREATE TABLE cms_phonenumbers (phonenumber VARCHAR(50) NOT NULL, user_id INT DEFAULT NULL, INDEX IDX_F21F790FA76ED395 (user_id), PRIMARY KEY(phonenumber)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[7]);
self::assertEquals('ALTER TABLE cms_users ADD CONSTRAINT FK_3AF03EC5A832C1C9 FOREIGN KEY (email_id) REFERENCES cms_emails (id)', $sql[8]);
self::assertEquals('ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AA76ED395 FOREIGN KEY (user_id) REFERENCES cms_users (id)', $sql[9]);
self::assertEquals('ALTER TABLE cms_users_groups ADD CONSTRAINT FK_7EA9409AFE54D947 FOREIGN KEY (group_id) REFERENCES cms_groups (id)', $sql[10]);
@@ -87,7 +121,12 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
$sql = $tool->getCreateSchemaSql($classes);
self::assertCount(1, $sql);
self::assertEquals('CREATE TABLE decimal_model (id INT AUTO_INCREMENT NOT NULL, `decimal` NUMERIC(5, 2) NOT NULL, `high_scale` NUMERIC(14, 4) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[0]);
self::assertThat($sql[0], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE decimal_model (id INT AUTO_INCREMENT NOT NULL, `decimal` NUMERIC(5, 2) NOT NULL, `high_scale` NUMERIC(14, 4) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE decimal_model (id INT AUTO_INCREMENT NOT NULL, `decimal` NUMERIC(5, 2) NOT NULL, `high_scale` NUMERIC(14, 4) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
}
public function testGetCreateSchemaSql3(): void
@@ -98,7 +137,12 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
$sql = $tool->getCreateSchemaSql($classes);
self::assertCount(1, $sql);
self::assertEquals('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[0]);
self::assertThat($sql[0], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
}
#[Group('DBAL-204')]

View File

@@ -6,6 +6,8 @@ namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\ForeignKeyConstraintEditor;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\Table as DbalTable;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorColumn;
@@ -22,7 +24,9 @@ use Doctrine\Tests\ORM\Functional\Ticket\Doctrine\Common\Collections\Collection;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function array_map;
use function assert;
use function class_exists;
use function reset;
class DDC2138Test extends OrmFunctionalTestCase
@@ -42,19 +46,38 @@ class DDC2138Test extends OrmFunctionalTestCase
$table = $schema->getTable('users_followed_objects');
assert($table instanceof DbalTable);
self::assertTrue($table->columnsAreIndexed(['object_id']));
self::assertTrue($table->columnsAreIndexed(['user_id']));
self::assertTrue(self::columnIsIndexed($table, 'object_id'));
self::assertTrue(self::columnIsIndexed($table, 'user_id'));
$foreignKeys = $table->getForeignKeys();
self::assertCount(1, $foreignKeys, 'user_id column has to have FK, but not object_id');
$fk = reset($foreignKeys);
assert($fk instanceof ForeignKeyConstraint);
self::assertEquals('users', $fk->getForeignTableName());
$localColumns = $fk->getLocalColumns();
if (class_exists(ForeignKeyConstraintEditor::class)) {
self::assertEquals('users', $fk->getReferencedTableName()->toString());
$localColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $fk->getReferencingColumnNames());
} else {
self::assertEquals('users', $fk->getForeignTableName());
$localColumns = $fk->getLocalColumns();
}
self::assertContains('user_id', $localColumns);
self::assertCount(1, $localColumns);
}
private static function columnIsIndexed(DbalTable $table, string $column): bool
{
foreach ($table->getIndexes() as $index) {
if ($index->spansColumns([$column])) {
return true;
}
}
return false;
}
}

View File

@@ -27,8 +27,18 @@ class DDC2182Test extends OrmFunctionalTestCase
$this->_em->getClassMetadata(DDC2182OptionChild::class),
],
);
self::assertEquals('CREATE TABLE DDC2182OptionParent (id INT UNSIGNED NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[0]);
self::assertEquals('CREATE TABLE DDC2182OptionChild (id VARCHAR(255) NOT NULL, parent_id INT UNSIGNED DEFAULT NULL, INDEX IDX_B314D4AD727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB', $sql[1]);
self::assertThat($sql[0], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE DDC2182OptionParent (id INT UNSIGNED NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE DDC2182OptionParent (id INT UNSIGNED NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertThat($sql[1], self::logicalOr(
// DBAL < 4.3
self::equalTo('CREATE TABLE DDC2182OptionChild (id VARCHAR(255) NOT NULL, parent_id INT UNSIGNED DEFAULT NULL, INDEX IDX_B314D4AD727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
self::equalTo('CREATE TABLE DDC2182OptionChild (id VARCHAR(255) NOT NULL, parent_id INT UNSIGNED DEFAULT NULL, INDEX IDX_B314D4AD727ACA70 (parent_id), PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
));
self::assertEquals('ALTER TABLE DDC2182OptionChild ADD CONSTRAINT FK_B314D4AD727ACA70 FOREIGN KEY (parent_id) REFERENCES DDC2182OptionParent (id)', $sql[2]);
}
}

View File

@@ -4,11 +4,16 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Table;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Tests\ORM\Functional\DatabaseDriverTestCase;
use PHPUnit\Framework\Attributes\Group;
use function class_exists;
class DDC2387Test extends DatabaseDriverTestCase
{
#[Group('DDC-2387')]
@@ -16,12 +21,23 @@ class DDC2387Test extends DatabaseDriverTestCase
{
$product = new Table('ddc2387_product');
$product->addColumn('id', 'integer');
$product->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$product->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$product->setPrimaryKey(['id']);
}
$attributes = new Table('ddc2387_attributes');
$attributes->addColumn('product_id', 'integer');
$attributes->addColumn('attribute_name', 'string');
$attributes->setPrimaryKey(['product_id', 'attribute_name']);
if (class_exists(PrimaryKeyConstraint::class)) {
$attributes->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('product_id')), new UnqualifiedName(Identifier::unquoted('attribute_name'))], true));
} else {
$attributes->setPrimaryKey(['product_id', 'attribute_name']);
}
$attributes->addForeignKeyConstraint('ddc2387_product', ['product_id'], ['product_id']);
$metadata = $this->convertToClassMetadata([$product, $attributes], []);

View File

@@ -42,14 +42,14 @@ class DDC832Test extends OrmFunctionalTestCase
$platform = $this->_em->getConnection()->getDatabasePlatform();
$sm = $this->createSchemaManager();
$sm->dropTable($platform->quoteIdentifier('TREE_INDEX'));
$sm->dropTable($platform->quoteIdentifier('INDEX'));
$sm->dropTable($platform->quoteIdentifier('LIKE'));
$sm->dropTable($platform->quoteSingleIdentifier('TREE_INDEX'));
$sm->dropTable($platform->quoteSingleIdentifier('INDEX'));
$sm->dropTable($platform->quoteSingleIdentifier('LIKE'));
// DBAL 3
if ($platform instanceof PostgreSQLPlatform && method_exists($platform, 'getIdentitySequenceName')) {
$sm->dropSequence($platform->quoteIdentifier('INDEX_id_seq'));
$sm->dropSequence($platform->quoteIdentifier('LIKE_id_seq'));
$sm->dropSequence($platform->quoteSingleIdentifier('INDEX_id_seq'));
$sm->dropSequence($platform->quoteSingleIdentifier('LIKE_id_seq'));
}
}

View File

@@ -30,6 +30,15 @@ class GH10450Test extends OrmTestCase
yield 'Entity class that redeclares a protected field inherited from a base entity' => [GH10450EntityChildProtected::class];
yield 'Entity class that redeclares a protected field inherited from a mapped superclass' => [GH10450MappedSuperclassChildProtected::class];
}
public function testFieldsOfTransientClassesAreNotConsideredDuplicate(): void
{
$em = $this->getTestEntityManager();
$metadata = $em->getClassMetadata(GH10450Cat::class);
self::assertArrayHasKey('id', $metadata->fieldMappings);
}
}
#[ORM\Entity]
@@ -113,3 +122,26 @@ class GH10450MappedSuperclassChildProtected extends GH10450BaseMappedSuperclassP
#[ORM\Column(type: 'text', name: 'child')]
protected string $field;
}
abstract class GH10450AbstractEntity
{
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue]
protected int $id;
}
#[ORM\Entity]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorMap(['cat' => GH10450Cat::class])]
#[ORM\DiscriminatorColumn(name: 'type')]
abstract class GH10450Animal extends GH10450AbstractEntity
{
#[ORM\Column(type: 'text', name: 'base')]
private string $field;
}
#[ORM\Entity]
class GH10450Cat extends GH10450Animal
{
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Events;
use Doctrine\Tests\Models\GH11524\GH11524Entity;
use Doctrine\Tests\Models\GH11524\GH11524Listener;
use Doctrine\Tests\Models\GH11524\GH11524Relation;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11524Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
GH11524Entity::class,
GH11524Relation::class,
);
$this->_em->getEventManager()->addEventListener(Events::postLoad, new GH11524Listener());
}
public function testPostLoadCalledOnProxy(): void
{
$relation = new GH11524Relation();
$relation->name = 'test';
$this->_em->persist($relation);
$entity = new GH11524Entity();
$entity->relation = $relation;
$this->_em->persist($entity);
$this->_em->flush();
$this->_em->clear();
$reloadedEntity = $this->_em->find(GH11524Entity::class, $entity->id);
$reloadedRelation = $reloadedEntity->relation;
$this->assertTrue($this->isUninitializedObject($reloadedRelation));
$this->assertSame('fake', $reloadedRelation->getTranslation(), 'The property set by the postLoad listener must get initialized on usage.');
}
}

View File

@@ -4,9 +4,14 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\DBAL\Schema\Name\Identifier;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\Table;
use Doctrine\Tests\ORM\Functional\DatabaseDriverTestCase;
use function class_exists;
/**
* Verifies that associations/columns with an inline '_id' get named properly
*
@@ -18,12 +23,23 @@ class GH7684Test extends DatabaseDriverTestCase
{
$table1 = new Table('GH7684_identity_test_table');
$table1->addColumn('id', 'integer');
$table1->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$table1->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$table1->setPrimaryKey(['id']);
}
$table2 = new Table('GH7684_identity_test_assoc_table');
$table2->addColumn('id', 'integer');
$table2->addColumn('gh7684_identity_test_id', 'integer');
$table2->setPrimaryKey(['id']);
if (class_exists(PrimaryKeyConstraint::class)) {
$table2->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, [new UnqualifiedName(Identifier::unquoted('id'))], true));
} else {
$table2->setPrimaryKey(['id']);
}
$table2->addForeignKeyConstraint('GH7684_identity_test', ['gh7684_identity_test_id'], ['id']);
$metadatas = $this->convertToClassMetadata([$table1, $table2]);

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter;
use Doctrine\Tests\OrmFunctionalTestCase;
use function sprintf;
use function str_replace;
abstract class AbstractTestCase extends OrmFunctionalTestCase
{
protected function generateMessage(string $message): string
{
$log = $this->getLastLoggedQuery();
return sprintf("%s\nSQL: %s", $message, str_replace(['?'], (array) $log['params'], $log['sql']));
}
protected function clearCachedData(object ...$entities): void
{
foreach ($entities as $entity) {
$this->_em->refresh($entity);
}
}
protected function persistFlushClear(object ...$entities): void
{
foreach ($entities as $entity) {
$this->_em->persist($entity);
}
$this->_em->flush();
$this->_em->clear();
}
}

View File

@@ -4,12 +4,11 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter;
use Doctrine\Tests\OrmFunctionalTestCase;
use Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity\Order;
use Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity\User;
use Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\SQLFilter\CompanySQLFilter;
use function sprintf;
use function str_replace;
final class ChangeFiltersTest extends OrmFunctionalTestCase
final class ChangeFiltersTest extends AbstractTestCase
{
private const COMPANY_A = 'A';
private const COMPANY_B = 'B';
@@ -130,11 +129,4 @@ final class ChangeFiltersTest extends OrmFunctionalTestCase
self::assertInstanceOf(User::class, $order->user);
self::assertEquals($companyB['userId'], $order->user->id);
}
private function generateMessage(string $message): string
{
$log = $this->getLastLoggedQuery();
return sprintf("%s\nSQL: %s", $message, str_replace(['?'], (array) $log['params'], $log['sql']));
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Insurance
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\Column(type: 'string')]
public string $name;
#[ORM\ManyToOne(targetEntity: Practice::class)]
public Practice $practice;
public function __construct(Practice $practice, string $name)
{
$this->practice = $practice;
$this->name = $name;
}
}

View File

@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter;
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity;
use Doctrine\ORM\Mapping as ORM;

View File

@@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Patient
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\Column(type: 'string')]
public string $name;
/** @var Collection<int, PatientInsurance> */
#[ORM\OneToMany(targetEntity: PatientInsurance::class, mappedBy: 'patient', fetch: 'LAZY', cascade: ['persist'])]
public Collection $insurances;
public function __construct(string $name)
{
$this->name = $name;
$this->insurances = new ArrayCollection();
}
/** @return Collection<PrimaryPatInsurance> */
public function getPrimaryInsurances(): Collection
{
return $this->insurances->filter(static function (PatientInsurance $insurances) {
return $insurances instanceof PrimaryPatInsurance;
});
}
/** @return Collection<SecondaryPatInsurance> */
public function getSecondaryInsurances(): Collection
{
return $this->insurances->filter(static function (PatientInsurance $insurances) {
return $insurances instanceof SecondaryPatInsurance;
});
}
public function addPrimaryInsurance(Insurance $insurance): void
{
$this->insurances[] = new PrimaryPatInsurance($this, $insurance);
}
public function addSecondaryInsurance(Insurance $insurance): void
{
$this->insurances[] = new SecondaryPatInsurance($this, $insurance);
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorMap(['primary' => PrimaryPatInsurance::class, 'secondary' => SecondaryPatInsurance::class])]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
abstract class PatientInsurance
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\ManyToOne(targetEntity: Insurance::class, fetch: 'EAGER', cascade: ['persist'])]
#[ORM\JoinColumn(referencedColumnName: 'id', nullable: false)]
public Insurance $insurance;
#[ORM\ManyToOne(targetEntity: Patient::class, inversedBy: 'insurances')]
public Patient $patient;
public function __construct(Patient $patient, Insurance $insurance)
{
$this->patient = $patient;
$this->insurance = $insurance;
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Practice
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\Column(type: 'string')]
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class PrimaryPatInsurance extends PatientInsurance
{
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\SwitchContextWithFilter\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class SecondaryPatInsurance extends PatientInsurance
{
}

Some files were not shown because too many files have changed in this diff Show More