Compare commits

...

166 Commits

Author SHA1 Message Date
Grégoire Paris
4af029f32c Merge pull request #12351 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2026-01-19 14:53:45 +01:00
Grégoire Paris
026f5bfe1b Merge pull request #12350 from greg0ire/missing-order-by
Add missing ORDER BY clause
2026-01-18 10:41:46 +01:00
Grégoire Paris
0b0f2f4d86 Add missing ORDER BY clause
This causes transient failures with PostgreSQL. Order is not guaranteed.
2026-01-17 13:39:44 +01:00
Grégoire Paris
afaea299ad Merge pull request #12346 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2026-01-16 18:47:35 +01:00
Grégoire Paris
d2418ab074 Merge pull request #12344 from greg0ire/update-baseline
Update PHPStan baseline
2026-01-15 23:38:15 +01:00
Grégoire Paris
39a05e31c9 Update PHPStan baseline
This is caused by the release of doctrine/collections 2.7.0. The error
message is a bit shorter now.
2026-01-15 20:13:05 +01:00
Grégoire Paris
3b8c23c51d Merge pull request #12336 from greg0ire/dmwydo
Stop mocking EventManager
2026-01-08 07:19:16 +01:00
Grégoire Paris
60d4ea694a Stop mocking EventManager
It is defined outside this repository, so let us not mock what we do not
own.
2026-01-07 21:52:47 +01:00
Grégoire Paris
0f8730a6e5 Merge pull request #12331 from greg0ire/a-the
Fix grammatical errors
2025-12-24 11:39:26 +01:00
Grégoire Paris
0aeddd0592 Fix grammatical errors 2025-12-20 15:34:16 +01:00
Sadetdin EYILI
492745d710 docs: add xml example for Single Table Inheritance mapping (#12169) 2025-12-17 10:40:06 +01:00
dependabot[bot]
67419cf951 Bump actions/download-artifact from 6 to 7 (#12321) 2025-12-15 07:56:29 +01:00
dependabot[bot]
1237f5c909 Bump actions/upload-artifact from 5 to 6 (#12322) 2025-12-15 07:55:38 +01:00
dependabot[bot]
dcdd46251e Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12315)
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/release-on-milestone-closed.yml
  dependency-version: 13.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:23:27 +01:00
dependabot[bot]
3d98b43561 Bump doctrine/.github/.github/workflows/composer-lint.yml (#12317)
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/composer-lint.yml
  dependency-version: 13.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:22:42 +01:00
dependabot[bot]
9f3f70944a Bump doctrine/.github/.github/workflows/coding-standards.yml (#12316)
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/coding-standards.yml
  dependency-version: 13.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:22:15 +01:00
dependabot[bot]
05e07c0ae0 Bump doctrine/.github/.github/workflows/documentation.yml (#12318)
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:21:12 +01:00
Grégoire Paris
fea42ab984 Merge pull request #12299 from alexislefebvre/chore-show-parameters-in-name-of-CI-jobs
chore: show parameters in name of CI jobs
2025-11-30 11:14:14 +01:00
Alexander M. Turek
56f8186cf5 Merge release 2.20.9 into 2.21.x (#12311) 2025-11-30 00:15:25 +01:00
Alexis Lefebvre
01fd55e9ea chore: show parameters in name of CI jobs 2025-11-29 23:12:27 +01:00
Alexander M. Turek
87f1ba74e0 Support Symfony Console 8 (#12300) 2025-11-29 15:03:56 +01:00
dependabot[bot]
ab148d3d9d Bump doctrine/.github/.github/workflows/composer-lint.yml (#12288)
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/composer-lint.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:25:08 +01:00
dependabot[bot]
3924c38fab Bump doctrine/.github/.github/workflows/documentation.yml (#12289)
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/documentation.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:24:42 +01:00
dependabot[bot]
9814078a2c Bump doctrine/.github/.github/workflows/coding-standards.yml (#12290)
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/coding-standards.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:24:14 +01:00
dependabot[bot]
6de5684fd9 Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12291)
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/release-on-milestone-closed.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:23:46 +01:00
dependabot[bot]
c142503a52 Bump actions/checkout from 5 to 6 (#12292) 2025-11-24 07:27:27 +01:00
Grégoire Paris
15537bc218 Merge pull request #12285 from HypeMC/fix-is-foreign-key-composite
Fix check for composite foreign key
2025-11-21 19:29:05 +01:00
HypeMC
bc95c7c08d Fix check for composite foreign key 2025-11-21 07:27:53 +01:00
Grégoire Paris
c1becd54e6 Merge pull request #12281 from greg0ire/document-default-expressions
Fix documentation about default values
2025-11-20 17:58:48 +01:00
Grégoire Paris
e4d7df29c2 Fix documentation about default values
Saying it is not possible to get Doctrine to use the `DEFAULT` SQL
keyword is wrong.
2025-11-19 23:17:53 +01:00
Adrian Brajkovic
e38278bfca Fix eager fetch composite foreign key (#11397)
I think #11289 did not completely fix problem for eager fetch.
Change in that PR checked if primary key of target class is composite but that does not matter when loading collection by foreign key.
It should check if foreign key on target class is composite.

Fix from that PR did not work for me because i had entity with regular autogenerated id (single column), but foreign key referenced entity with composite primary key, like SecondLevelWithoutCompositePrimaryKey in this PR.

Checking if foreign key is composite fixed the problem for me.
2025-11-18 21:40:50 +01:00
Grégoire Paris
d90b32bb4f Merge pull request #12263 from doctrine/2.20.x-merge-up-into-2.21.x_7FMImxhr
Merge release 2.20.8 into 2.21.x
2025-11-10 21:25:25 +01:00
Grégoire Paris
5bff0919a7 Merge pull request #12254 from elliotbruneel/fix/empty-array-query
fix: handling of empty array in SQL condition generation
2025-11-10 14:35:45 +01:00
Elliot Bruneel
9ef0f5301b fix: update SQL condition for empty array to 1=0 instead of IN (NULL) 2025-11-10 10:44:48 +01:00
Elliot Bruneel
4989ca6f15 test: add test for finding by nullable field with empty array 2025-11-05 10:03:09 +01:00
Elliot Bruneel
32d1e97ce7 chore: improve empty array check in SQL condition generation 2025-11-05 09:51:33 +01:00
Grégoire Paris
ca8147b148 Merge pull request #12257 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-12.2.0
Bump doctrine/.github from 12.1.0 to 12.2.0
2025-11-03 09:32:58 +01:00
dependabot[bot]
c8ebea77f0 Bump doctrine/.github from 12.1.0 to 12.2.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 12.1.0 to 12.2.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/12.1.0...v12.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 06:11:36 +00:00
Elliot Bruneel
23f22860f1 chore: update phpstan version and regenerate baseline 2025-10-31 09:04:03 +01:00
Elliot Bruneel
b24586b1b5 fix: handling of empty array in SQL condition generation 2025-10-30 17:31:04 +01:00
Grégoire Paris
fe5ee705db Merge pull request #12247 from greg0ire/composer-lint
Setup composer lint workflow
2025-10-29 07:28:39 +01:00
Grégoire Paris
0511a9f790 Merge pull request #12248 from greg0ire/remove-mailing-list
Drop link to mailing list
2025-10-28 22:06:18 +01:00
Grégoire Paris
0e3d5e8c82 Drop link to mailing list
Who still uses this? Not me, that's for sure!
2025-10-28 21:20:15 +01:00
Grégoire Paris
72ffb3bfbf Remove archive exclude list
It is not up-to-date, and we use .gitattributes for this purpose.
2025-10-28 21:04:43 +01:00
Grégoire Paris
2e9a1adc23 Setup composer lint workflow 2025-10-28 21:03:24 +01:00
Grégoire Paris
99c96b40c2 Merge pull request #12242 from doctrine/2.20.x-merge-up-into-2.21.x_urr9kua6
Merge release 2.20.7 into 2.21.x
2025-10-27 23:14:14 +01:00
Grégoire Paris
59938cae57 Merge pull request #12238 from mpdude/fix-collections-deprecation
Avoid triggering a deprecation notice in doctrine/collections
2025-10-27 22:19:59 +01:00
Grégoire Paris
298dc9bb6a Merge pull request #12183 from mpdude/paginator-confused-result-set-mapping-initialized
Paginator with output walker returns count 0 when the query has previously been executed
2025-10-27 21:54:38 +01:00
Matthias Pigulla
da67f323e0 Add PHPStan errors to persistence2 baseline file 2025-10-27 19:51:11 +01:00
Matthias Pigulla
63635cad0e Remove PHPStan error suppressions 2025-10-27 12:28:48 +01:00
Grégoire Paris
a0d401b688 Merge pull request #12239 from doctrine/dependabot/github_actions/2.20.x/actions/download-artifact-6
Bump actions/download-artifact from 5 to 6
2025-10-27 08:38:07 +01:00
Grégoire Paris
6acbadfbbe Merge pull request #12240 from doctrine/dependabot/github_actions/2.20.x/actions/upload-artifact-5
Bump actions/upload-artifact from 4 to 5
2025-10-27 08:37:52 +01:00
dependabot[bot]
c64dcb4d38 Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 06:41:11 +00:00
dependabot[bot]
3304290b21 Bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 06:22:30 +00:00
Matthias Pigulla
1865717721 Avoid triggering a deprecation notice in doctrine/collections
This updates the code to avoid triggering the deprecation introduced in https://github.com/doctrine/collections/pull/472.
2025-10-26 23:20:27 +01:00
Matthias Pigulla
828b06e20f Update the DQL walker cookbook example 2025-10-26 22:13:51 +01:00
Matthias Pigulla
c2b844d2e3 Update src/Tools/Pagination/Paginator.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2025-10-26 22:08:56 +01:00
dependabot[bot]
b45d5329f8 Bump doctrine/.github from 10.1.0 to 12.0.0 (#12227) 2025-10-23 08:10:27 +02:00
Grégoire Paris
c1047b30e3 Merge pull request #12216 from paulinevos/docs-builder
Use docs builder in ORM repo
2025-10-23 07:48:31 +02:00
Grégoire Paris
aa62efa30a Adapt to latest coding standard 2025-10-22 21:12:17 +02:00
Pauline Vos
f71956f001 Use docs-builder to generate ORM docs
Introduces the `composer docs` command to generate the docs locally, and
uses the same tool (`docs-builder`) in the documentation GH workflow.
2025-10-22 21:09:03 +02:00
Ali Sol
96f9b29573 Merge pull request #12233 from alisolphp/fix-docs-typos-2-20
Docs: fix typos and grammar across reference docs
2025-10-21 17:09:12 +02:00
Grégoire Paris
9a55cf4f30 Merge pull request #12190 from mpdude/criteria-matching-custom-type-retry
Fix collection filtering API for `IN`/`NOT IN` comparisons that require type conversions
2025-10-16 07:56:07 +02:00
Matthias Pigulla
9d680a6de4 Do not eagerly set metadata from ResolveTargetEntityListener (#12174)
* Do not eagerly set metadata from ResolveTargetEntityListener

When using the `ResolveTargetEntityListener` to substitute `targetEntities` in association mappings, do not eagerly put the resolved (target) entity into the class metadata cache under the class name of the original entity.

#### Motivation

I have a library that wants to distribute a MappedSuperclass as the base for some functionality. It will be necessary that clients using the library will extend the MappedSuperclass to fill in some blanks, creating the first real `#[Entity]` instance of it.

This client-provided entity will be the primary means of working with the class. Thus, I was following the [note in the documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/inheritance-mapping.html#mapped-superclasses:~:text=It%20is%2C%20however) and using the `ResolveTargetEntityListener` to declare that whenever an association refers to that mapped superclass, the particular entity class shall be used instead.

>  One-To-Many associations are not generally possible on a mapped superclass, since they require the "many" side to hold the foreign key.
> It is, however, possible to use the [ResolveTargetEntityListener](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/cookbook/resolve-target-entity-listener.html) to replace references to a mapped superclass with an entity class at runtime. As long as there is only one entity subclass inheriting from the mapped superclass and all references to the mapped superclass are resolved to that entity class at runtime, the mapped superclass can use One-To-Many associations and be named as the targetEntity on the owning sides.

#### Changes made

The `ResolveTargetEntityListener` primarily does what its name suggests: For newly loaded class metadata, it inspects all associations declared and replaces the `targetEntity` with new (resolved) values.

But additionally, when a loaded class is the target of such a resolution, it would also put the class metadata into the cache under the name of the original entity.

I think that extra step is wrong, and this PR removes it. It had the side effect that when other classes extending the MappedSuperclass were loaded _after_ the resolve target class has been seen for the first time, the metadata for those classes would not inherit from the mapped superclass anymore, but from the target entity class instead. In my real-life use case, this causes weird mapping errors down the road; as of ^3.0, it would throw a mapping exception asking to configure inheritance mapping. But note that there would be no inheritance between the two entity classes at all.

#### More background

The documentation [describes the use of `ResolveTargetEntityListener`](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/cookbook/resolve-target-entity-listener.html) with an interface that is resolved to an entity class. For an interface, adding the extra metadata does not make a difference, since it never interferes with actual entity or mapped superclasses.

The initial idea of adding a copy of the entity class metadata under the interface name came from commit
9c7f3f2747 in #385. The goal was to make it possible to also find entities by interface names, like so:

```
$em->find('Foo\BarBundle\Entity\PersonInterface', 1);
```

It then [turned out that this only worked when the resolution had already been applied](https://github.com/doctrine/orm/pull/385#issuecomment-6658893). So, the new `onClassMetadataNotFound` event was added and the resolution map would be checked in that case as well (#1181). The inital code stayed in place, possibly giving a small performance gain.

In my real-world use case and the test case I added in this PR, the associations are even self-referencing. That should not really be necessary for the problem to surface. I decided to keep it this way to show that the `targetEntity` need not be an interface after all, and that a MappedSuperclass can be used in the same way.
2025-10-15 16:10:51 +02:00
Matthias Pigulla
7a59281157 Fix collection filtering API for IN/NOT IN comparisons that require type conversions
This PR fixes the `Criteria` matching API for `IN` and `NIN` conditions with values that are arrays, by making sure that type information for the matched field is passed to the DBAL level correctly.

Passing the right parameter type to DBAL is important to make sure parameter conversions are applied before matching at the database level.

Memory-based collections (`ArrayCollection`s or initialized collection fields) would perform matching on the objects in memory where no type conversion to the database representation is required, giving correct results.

But uninitialized collections that have their conditions evaluated at the database level need to convert parameter values to the database representation before performing the comparison.

One extra challenge is that the DBAL type system does currently not support array-valued parameters for custom types. Only a [limited list of types](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion) is supported.

I discussed this with @morozov at the Doctrine Hackathon and came to the conclusion that it would be best to work around this limitation at the ORM level. Thus, this fix recognizes array-valued parameters and creates multiple placeholders (like `?, ?, ?`) for them, flattening out the arrays in the parameter list and repeating the type information for each one of them.

Previous stalled attempt to fix this was in #11897.
2025-10-13 23:11:56 +02:00
Grégoire Paris
5def068fe9 Merge pull request #12223 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-10.1.0
Bump doctrine/.github from 8.0.0 to 10.1.0
2025-10-13 08:49:34 +02:00
dependabot[bot]
693acbf812 Bump doctrine/.github from 8.0.0 to 10.1.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 8.0.0 to 10.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/8.0.0...10.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 06:11:35 +00:00
Grégoire Paris
c1ce2bb687 Merge pull request #12220 from mbeccati/fix-mysql9-library
Escape library as a table name in tests (#12170)
2025-10-11 17:08:47 +02:00
Matteo Beccati
5f6896a2f9 Avoid using LIBRARY as table name (#12170)
It is a reserved word since MySQL 9.2:
https://dev.mysql.com/doc/refman/9.2/en/create-library.html
2025-10-11 11:56:29 +02:00
Grégoire Paris
930a790a5a Merge pull request #12212 from mpdude/revert-11769
Revert "Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared` (#11769)"
2025-10-10 08:17:09 +02:00
Christian Flothmann
3b7de17f2e add jobs using PHP 8.5 in the CI (#12180) 2025-10-09 16:39:28 +02:00
Matthias Pigulla
8afaa63d73 Add a recommendation not to use multiple private fields of the same name in entity hierarchies 2025-10-09 11:11:51 +02:00
Matthias Pigulla
2ad720b304 Revert "Fix fields of transient classes being considered duplicate with reportFieldsWhereDeclared (#11769)"
This reverts commit 4feaa470af.
2025-10-09 10:23:55 +02:00
Benjamin Eberlei
a939dc2e0d [GH-9219] Add support for toIterable over mixed or scalar results. (#12187)
* [GH-9219] Add support for toIterable over mixed or scalar results.

* Housekeeping: phpcs

* Update test names
2025-10-08 15:07:52 +02:00
Christophe Coevoet
f8186b1203 Merge pull request #12200 from mpdude/fix-invalid-dql-test-case
Fix DQL JOIN syntax in two test cases
2025-10-08 11:08:24 +02:00
Matthias Pigulla
048e308241 Fix DQL JOIN syntax in two test cases 2025-10-07 17:54:33 +02:00
Alexander M. Turek
18d07a1003 Merge branch '2.20.x' into 2.21.x
* 2.20.x:
  Remove calls to getMockForAbstractClass() (#12003)
  Upgrade to doctrine/coding-standard 14
  Bump doctrine/.github from 7.3.0 to 8.0.0
2025-10-07 16:35:00 +02:00
Alexander M. Turek
4274dac8a2 Remove calls to getMockForAbstractClass() (#12003) 2025-10-07 16:34:22 +02:00
Grégoire Paris
c1af765960 Merge pull request #12185 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-8.0.0
Bump doctrine/.github from 7.3.0 to 8.0.0
2025-10-07 15:40:44 +02:00
Grégoire Paris
5e290952dc Merge pull request #12189 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2025-10-07 15:11:22 +02:00
Christian Flothmann
5f3551852f use the empty string instead of null as an array offset (#12181) 2025-10-07 14:50:23 +02:00
Grégoire Paris
8144cad07c Upgrade to doctrine/coding-standard 14 2025-10-06 09:01:36 +02:00
dependabot[bot]
70fd68cf7f Bump doctrine/.github from 7.3.0 to 8.0.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 7.3.0 to 8.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/7.3.0...8.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-06 06:14:08 +00:00
Matthias Pigulla
437259556c Fix CS 2025-10-01 19:48:04 +02:00
Matthias Pigulla
e7e2fef56c Fix creation of the count query, to that a new RSM is being created 2025-10-01 19:46:58 +02:00
Matthias Pigulla
3e34b8e86a Add a test to reproduce the issue 2025-10-01 19:35:18 +02:00
Christian Flothmann
7d950aba62 do not call setAccessible() on PHP >= 8.1 (#12182) 2025-10-01 16:17:00 +02:00
Grégoire Paris
8fe1200edf Merge pull request #11895 from mpdude/fix-many-to-many-in-expression
Fix `IN`/`NOT IN` expression handling and support enums when matching on to-many-collections
2025-08-26 15:32:52 +02:00
Grégoire Paris
397358c308 Merge pull request #12133 from greg0ire/update-phpstan
PHPStan 2.1.22
2025-08-19 07:36:10 +02:00
Grégoire Paris
ac37a87a3d Merge pull request #12139 from doctrine/dependabot/github_actions/2.20.x/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-18 17:31:47 +02:00
dependabot[bot]
613f52db5a Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 09:50:01 +00:00
Grégoire Paris
cf5503b0d8 PHPCS 3.13.2 (#12134) 2025-08-17 21:53:23 +02:00
Grégoire Paris
ae5e9c8c6c PHPStan 2.1.22 2025-08-17 19:24:33 +02:00
Grégoire Paris
1a2826d147 Merge pull request #12053 from alexislefebvre/chore-remove-run-all.sh
chore: remove run-all.sh
2025-08-11 14:14:20 +02:00
Grégoire Paris
05760f9454 Merge pull request #12127 from doctrine/dependabot/github_actions/2.20.x/actions/download-artifact-5
Bump actions/download-artifact from 4 to 5
2025-08-11 13:06:03 +02:00
dependabot[bot]
26af013842 Bump actions/download-artifact from 4 to 5
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

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

As extending/modifying UnitOfWork in tests in not easily possible, I submit this fix for v2.x without a test.
2025-08-01 15:31:18 +02:00
Grégoire Paris
e0052390e1 Merge pull request #12087 from mvorisek/improve_basic_entity_persister
Improve BasicEntityPersister to be more flexible and cleaner
2025-07-30 09:44:24 +02:00
Michael Voříšek
8c6419e0e0 Prefer strict empty-array comparison over empty() call 2025-07-29 15:15:31 +02:00
Michael Voříšek
6f5ce1aca2 BasicEntityPersister: refactor $values variable into $placeholders
The new variable name is much more clearer.
2025-07-29 15:15:31 +02:00
Michael Voříšek
98e7a53b42 Remove BasicEntityPersister::$insertSql cache property
When the persister is extended to do a multi update, the caching is not
wanted. The impact is minimal as the CPU/time overhead per query is
much bigger and the prepared statement is not cached anyway.
2025-07-29 15:15:31 +02:00
Gerhard Seidel
3aaaf37dfb fix: PrePersistEventTest typos and unnecessary comments 2025-07-29 14:40:20 +02:00
Grégoire Paris
154a4652ee Merge pull request #12086 from mvorisek/add_cache_rw_strict_locking_test
Add functional strict-locking 2nd level cache test
2025-07-29 11:48:25 +02:00
Michael Voříšek
ae7489ff19 Add functional strict-locking 2nd level cache test 2025-07-28 12:14:50 +02:00
Grégoire Paris
1ee01f4473 Merge pull request #12078 from stlgaits/2.20.x
Fix embedded classes display in orm:mapping:command output
2025-07-22 08:48:52 +02:00
stlgaits
8a9ed138a8 Fix embedded classes display in orm:mapping:command output 2025-07-21 15:44:46 +02:00
Alexis Lefebvre
41ea59ac66 chore: use a shorter name for CI on GitHub Actions (#12055) 2025-07-04 00:19:01 +02:00
Alexis Lefebvre
e605e6d569 doc: add links to GitHub Actions on README (#12054) 2025-07-04 00:13:51 +02:00
Alexis Lefebvre
d68c1dcd6d chore: remove run-all.sh 2025-07-02 00:14:28 +02:00
Grégoire Paris
4dd6cd43d3 Merge pull request #12025 from doctrine/2.20.x-merge-up-into-2.21.x_3lkIZGKN
Merge release 2.20.5 into 2.21.x
2025-06-26 20:57:34 +02:00
Grégoire Paris
6307b4fa7d Merge pull request #8012 from sgehrig/bug/#8011-ordering-with-arithmetic-expression
Bug/#8011 ordering with arithmetic expression
2025-06-24 19:50:46 +02:00
Alexander M. Turek
5d21bb158b Fix calls to Application::add() (#12006) 2025-06-18 08:58:26 +02:00
Grégoire Paris
7ecef9d8d0 Merge pull request #11983 from doctrine/2.20.x-merge-up-into-2.21.x_prQgWeaY
Merge release 2.20.4 into 2.21.x
2025-06-14 13:13:20 +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
c027f11dd7 Merge pull request #11968 from doctrine/2.20.x
Merge 2.20.x up into 2.21.x
2025-06-06 12:29:28 +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
de5dacbac7 Merge pull request #11953 from doctrine/2.20.x
2.20.x to 2.21.x
2025-05-28 14:36:25 +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
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
528b8837e1 Merge pull request #11929 from doctrine/2.20.x-merge-up-into-2.21.x_KkdqS0u7
Merge release 2.20.3 into 2.21.x
2025-05-02 21:57:23 +02:00
Matthias Pigulla
9bf407f336 Fix IN/NOT IN expression handling and support enums when matching on to-many-collections
This fixes that using a `Criteria` with an `IN` or `NIN` expression on a to-many collection currently leads to an SQL error (#6173). The `ManyToMany` persister needs to know about the slightly different SQL syntax for `[NOT] IN ()`.

In the case of `[NOT] IN` expressions, the value will be an array, which also required me to change (I guess "fix") the parameter type handling. I have pulled the necessary code from the `BasicEntityPersister` and placed it as static helper methods in `PersisterHelper`.

This is somewhat inspired by #11516, which aims at fixing #11481: By re-using the parameter type handling code, it also fixes using backed enums in `EQ`, `IN` and `NIN` expressions within `Criteria` when `matching()` on one-to-many and many-to-many collections.
2025-03-30 22:44:12 +02:00
Stefan Gehrig
067ad51b3f fixes sqlite sql inconsistency 2025-03-17 08:48:30 +01:00
Stefan Gehrig
00c77213fb fixes codesniffer violation 2025-03-15 09:42:21 +01:00
Stefan Gehrig
c68b8f90b3 adds a test for postgres that uses a HIDDEN result variable for ordering based on arithmetic expression 2025-03-05 09:28:52 +01:00
Stefan Gehrig
aa4f9ce9e9 CS fix based on PHP_CodeSniffer report 2025-03-05 09:22:57 +01:00
Stefan Gehrig
d96fc23327 skips tests when running on postgres 2025-02-27 10:30:21 +01:00
Gerhard Seidel
4fb044d5f6 fix: cs 2025-02-20 10:01:35 +08:00
David Buchmann
8ce7b310c5 document how to work with generated columns 2025-02-17 15:06:18 +01:00
Gerhard Seidel
2a953c5e2b fix: PrePersistEventTest and cs 2025-02-17 14:01:08 +08:00
Gerhard Seidel
abc6a40ccb fix: calling scheduleForInsert twice
If scheduleForInsert was called in prePersist hook already, then persistNew need to check this case first, otherwise a ORMInvalidArgumentException will be thrown
2025-02-14 12:45:13 +08:00
Grégoire Paris
73e68f3c7d Merge pull request #11821 from doctrine/2.20.x-merge-up-into-2.21.x_8O8nHxqC
Merge release 2.20.2 into 2.21.x
2025-02-04 20:24:01 +01:00
Alexander M. Turek
73777d0bd4 Merge branch '2.20.x' into 2.21.x
* 2.20.x:
  Introduce testNotListedValueInEnumArray
  Fix documentation for JoinColumn nullable (#11798)
  Ignore deprecations from doctrine/common
  Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared`
2025-01-26 19:56:20 +01:00
Stefan Gehrig
ec6d1b9f72 fixes whitespace
Signed-off-by: Stefan Gehrig <stefan.gehrig.hn@googlemail.com>
2025-01-07 08:51:19 +01:00
Stefan Gehrig
d809fed52a fixes code sniffer complaints
Signed-off-by: Stefan Gehrig <stefan.gehrig.hn@googlemail.com>
2025-01-07 08:48:42 +01:00
Stefan Gehrig
0e4786dfa8 adds testcases for order by items enclosed in ((...)) (double brackets - just one bracket does not work)
just one bracket (...) gives

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

1
.gitattributes vendored
View File

@@ -11,7 +11,6 @@ build.properties.dev export-ignore
build.xml export-ignore
CONTRIBUTING.md export-ignore
phpunit.xml.dist export-ignore
run-all.sh export-ignore
phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore

View File

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

20
.github/workflows/composer-lint.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: "Composer Lint"
on:
pull_request:
branches:
- "*.x"
paths:
- ".github/workflows/composer-lint.yml"
- "composer.json"
push:
branches:
- "*.x"
paths:
- ".github/workflows/composer-lint.yml"
- "composer.json"
jobs:
composer-lint:
name: "Composer Lint"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@13.1.0"

View File

@@ -1,4 +1,4 @@
name: "Continuous Integration"
name: "CI: PHPUnit"
on:
pull_request:
@@ -27,7 +27,14 @@ env:
jobs:
phpunit-smoke-check:
name: "PHPUnit with SQLite"
name: >
SQLite -
${{ format('PHP {0} - DBAL {1} - ext. {2} - proxy {3}',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø',
matrix.proxy || 'Ø'
) }}
runs-on: "ubuntu-22.04"
strategy:
@@ -41,6 +48,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
extension:
@@ -64,7 +72,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -98,14 +106,20 @@ jobs:
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v6"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.proxy }}-coverage"
path: "coverage*.xml"
phpunit-postgres:
name: "PHPUnit with PostgreSQL"
name: >
${{ format('PostgreSQL {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.postgres-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
@@ -115,6 +129,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3@dev"
@@ -147,7 +162,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -172,14 +187,20 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v6"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
phpunit-mariadb:
name: "PHPUnit with MariaDB"
name: >
${{ format('MariaDB {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.mariadb-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
@@ -189,6 +210,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3@dev"
@@ -218,7 +240,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -243,14 +265,20 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v6"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
phpunit-mysql:
name: "PHPUnit with MySQL"
name: >
${{ format('MySQL {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.mysql-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
@@ -260,6 +288,7 @@ jobs:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3@dev"
@@ -289,7 +318,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -321,14 +350,19 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v4"
uses: "actions/upload-artifact@v6"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
phpunit-lower-php-versions:
name: "PHPUnit with SQLite"
name: >
SQLite -
${{ format('PHP {0} - deps {1}',
matrix.php-version || 'Ø',
matrix.deps || 'Ø'
) }}
runs-on: "ubuntu-22.04"
strategy:
@@ -341,7 +375,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -373,12 +407,12 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v4"
uses: "actions/download-artifact@v7"
with:
path: "reports"

View File

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

View File

@@ -36,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
with:
fetch-depth: 2

View File

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

View File

@@ -40,7 +40,7 @@ jobs:
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"

View File

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

View File

@@ -1248,7 +1248,7 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio
## Scalar mappings can now be omitted from DQL result
You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore.
You are now allowed to mark scalar SELECT expressions as HIDDEN and they are not hydrated anymore.
Example:
SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10

View File

@@ -1,29 +1,39 @@
{
"name": "doctrine/orm",
"type": "library",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
"type": "library",
"keywords": [
"orm",
"database"
],
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
"sort-packages": true
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"require": {
"php": "^7.1 || ^8.0",
"composer-runtime-api": "^2",
"ext-ctype": "*",
"composer-runtime-api": "^2",
"doctrine/cache": "^1.12.1 || ^2.1.1",
"doctrine/collections": "^1.5 || ^2.1",
"doctrine/common": "^3.0.3",
@@ -35,23 +45,22 @@
"doctrine/lexer": "^2 || ^3",
"doctrine/persistence": "^2.4 || ^3",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0",
"symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
"symfony/polyfill-php72": "^1.23",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^13.0",
"doctrine/coding-standard": "^9.0.2 || ^14.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/extension-installer": "~1.1.0 || ^1.4",
"phpstan/phpstan": "~1.4.10 || 2.0.3",
"phpstan/phpstan": "~1.4.10 || 2.1.23",
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.12.0",
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"
@@ -62,17 +71,29 @@
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "src" }
"psr-4": {
"Doctrine\\ORM\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Tests",
"Doctrine\\Performance\\": "tests/Performance",
"Doctrine\\StaticAnalysis\\": "tests/StaticAnalysis",
"Doctrine\\Performance\\": "tests/Performance"
"Doctrine\\Tests\\": "tests/Tests"
}
},
"bin": ["bin/doctrine"],
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"bin": [
"bin/doctrine"
],
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
},
"sort-packages": true
},
"scripts": {
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
}
}

3
docs/.gitignore vendored Normal file
View File

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

View File

@@ -1,18 +1,18 @@
# 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 project root, run
It will generate the documentation into the build directory of the checkout.
composer docs
This will generate the documentation into the `docs/output` subdirectory.
## Theme issues
To browse the documentation, you need to run a webserver:
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
in which case you will need to run:
cd docs/output
php -S localhost:8000
1. git submodule init
2. git submodule update
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

9
docs/composer.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "doctrine/orm-docs",
"description": "Documentation for the Object-Relational Mapper\"",
"type": "library",
"license": "MIT",
"require-dev": {
"doctrine/docs-builder": "^1.0"
}
}

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

@@ -101,8 +101,16 @@ The ``Paginate::count(Query $query)`` looks like:
{
static public function count(Query $query)
{
/** @var Query $countQuery */
$countQuery = clone $query;
/*
To avoid changing the $query passed into the method and to make sure a possibly existing
ResultSetMapping is discarded, we create a new query object any copy relevant data over.
*/
$countQuery = new Query($query->getEntityManager());
$countQuery->setDQL($query->getDQL());
$countQuery->setParameters(clone $query->getParameters());
foreach ($query->getHints() as $name => $value) {
$countQuery->setHint($name, $value);
}
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
@@ -111,7 +119,7 @@ The ``Paginate::count(Query $query)`` looks like:
}
}
It clones the query, resets the limit clause first and max results
This resets the limit clause first and max results
and registers the ``CountSqlWalker`` custom tree walker which
will modify the AST to execute a count query. The walkers
implementation is:

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

@@ -13,7 +13,6 @@ If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
@@ -103,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>`
@@ -123,4 +123,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

@@ -279,7 +279,7 @@ requests.
Connection
----------
The ``$connection`` passed as the first argument to he constructor of
The ``$connection`` passed as the first argument to the constructor of
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
to create such a connection. The DBAL configuration is explained in the

View File

@@ -168,7 +168,7 @@ recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an EntityManager.
By default, serializing proxy objects does not initialize them. On
unserialization, resulting objects are detached from the entity
manager and cannot be initialiazed anymore. You can implement the
manager and cannot be initialized anymore. You can implement the
``__serialize()`` method if you want to change that behavior, but
then you need to ensure that you won't generate large serialized
object graphs and take care of circular associations.

View File

@@ -213,12 +213,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
@@ -261,10 +264,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>`.
@@ -678,8 +682,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

View File

@@ -254,6 +254,21 @@ Here is a complete list of ``Column``s attributes (all optional):
- ``options``: Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
Specifying default values
~~~~~~~~~~~~~~~~~~~~~~~~~
While it is possible to specify default values for properties in your
PHP class, Doctrine also allows you to specify default values for
database columns using the ``default`` key in the ``options`` array of
the ``Column`` attribute.
.. configuration-block::
.. literalinclude:: basic-mapping/DefaultValues.php
:language: attribute
.. literalinclude:: basic-mapping/default-values.xml
:language: xml
.. _reference-php-mapping-types:
PHP Types Mapping

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[Entity]
class Message
{
#[Column(options: ['default' => 'Hello World!'])]
private string $text;
}

View File

@@ -0,0 +1,9 @@
<doctrine-mapping>
<entity name="Message">
<field name="text">
<options>
<option name="default">Hello World!</option>
</options>
</field>
</entity>
</doctrine-mapping>

View File

@@ -18,30 +18,6 @@ In your mapping configuration, the column definition (for example, the
the ``charset`` and ``collation``. The default values are ``utf8`` and
``utf8_unicode_ci``, respectively.
Entity Classes
--------------
How can I add default values to a column?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL.
This is not necessary however, you can just use your class properties as default values. These are then used
upon insert:
.. code-block:: php
class User
{
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self::STATUS_DISABLED;
}
.
Mapping
-------

View File

@@ -30,7 +30,7 @@ table alias of the SQL table of the entity.
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
2. The filter must be deterministic. Don't change the values base on external inputs.
2. The filter must be deterministic. Don't change the values based on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.

View File

@@ -232,6 +232,23 @@ Example:
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Person" inheritance-type="SINGLE_TABLE">
<discriminator-column name="discr" type="string" />
<discriminator-map>
<discriminator-mapping value="person" class="MyProject\Model\Person"/>
<discriminator-mapping value="employee" class="MyProject\Model\Employee"/>
</discriminator-map>
</entity>
</doctrine-mapping>
<doctrine-mapping>
<entity name="MyProject\Model\Employee">
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Person:

View File

@@ -187,6 +187,14 @@ internally by the ORM currently refers to fields by their name only, without tak
class containing the field into consideration. This makes it impossible to keep separate
mapping configuration for both fields.
Apart from that, in the case of having multiple ``private`` fields of the same name within
the class hierarchy an entity or mapped superclass, the Collection filtering API cannot determine
the right field to look at. Even if only one of these fields is actually mapped, the ``ArrayCollection``
will not be able to tell, since it does not have access to any metadata.
Thus, to avoid problems in this regard, it is best to avoid having multiple ``private`` fields of the
same name in class hierarchies containing entity and mapped superclasses.
Known Issues
------------

View File

@@ -44,7 +44,7 @@ Something like below for an entity region:
If the entity holds a collection that also needs to be cached.
An collection region could look something like:
A collection region could look something like:
.. code-block:: php
@@ -133,7 +133,7 @@ Caching mode
* Read Write cache employs locks before update/delete.
* Use if data needs to be updated.
* Slowest strategy.
* To use it a the cache region implementation must support locking.
* To use it the cache region implementation must support locking.
Built-in cached persisters
@@ -630,7 +630,7 @@ DELETE / UPDATE queries
DQL UPDATE / DELETE statements are ported directly into a database and bypass
the second-level cache.
Entities that are already cached will NOT be invalidated.
However the cached data could be evicted using the cache API or an special query hint.
However the cached data could be evicted using the cache API or a special query hint.
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``

View File

@@ -118,7 +118,7 @@ entity might look like this:
}
}
Now the possiblity of mass-assignment exists on this entity and can
Now the possibility of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:

View File

@@ -22,7 +22,7 @@ have to register them yourself.
All the commands of the Doctrine Console require access to the
``EntityManager``. You have to inject it into the console application.
Here is an example of a the project-specific ``bin/doctrine`` binary.
Here is an example of a project-specific ``bin/doctrine`` binary.
.. code-block:: php

View File

@@ -65,6 +65,7 @@
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/generated-columns
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/resolve-target-entity-listener

View File

@@ -234,6 +234,7 @@
<!-- extending a class from another package -->
<exclude-pattern>tests/Tests/Mocks/DatabasePlatformMock.php</exclude-pattern>
<exclude-pattern>tests/Tests/Mocks/SchemaManagerMock.php</exclude-pattern>
<exclude-pattern>tests/Tests/ORM/AbstractQueryTest.php</exclude-pattern>
<exclude-pattern>tests/Tests/ORM/Functional/Ticket/DDC3634Test.php</exclude-pattern>
</rule>

View File

@@ -18,18 +18,6 @@ parameters:
count: 1
path: src/AbstractQuery.php
-
message: '#^Expression "\$setCacheEntry\(\$data\)" on a separate line does not do anything\.$#'
identifier: expr.resultUnused
count: 1
path: src/AbstractQuery.php
-
message: '#^Expression "\$setCacheEntry\(\$stmt\)" on a separate line does not do anything\.$#'
identifier: expr.resultUnused
count: 1
path: src/AbstractQuery.php
-
message: '#^Method Doctrine\\ORM\\AbstractQuery\:\:getParameter\(\) should return Doctrine\\ORM\\Query\\Parameter\|null but returns Doctrine\\ORM\\Query\\Parameter\|false\|null\.$#'
identifier: return.type
@@ -102,6 +90,12 @@ parameters:
count: 1
path: src/Cache/CollectionHydrator.php
-
message: '#^If condition is always false\.$#'
identifier: if.alwaysFalse
count: 1
path: src/Cache/DefaultCache.php
-
message: '#^Method Doctrine\\ORM\\Cache\\DefaultCache\:\:buildCollectionCacheKey\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
@@ -264,12 +258,6 @@ parameters:
count: 1
path: src/Cache/DefaultQueryCache.php
-
message: '#^Parameter \#1 \$result of class Doctrine\\ORM\\Cache\\QueryCacheEntry constructor expects array\<string, mixed\>, array\<int\|string, array\<string, array\<mixed\>\>\> given\.$#'
identifier: argument.type
count: 1
path: src/Cache/DefaultQueryCache.php
-
message: '#^Parameter \#2 \$key of method Doctrine\\ORM\\Cache\\Logging\\CacheLogger\:\:entityCacheHit\(\) expects Doctrine\\ORM\\Cache\\EntityCacheKey, Doctrine\\ORM\\Cache\\CacheKey given\.$#'
identifier: argument.type
@@ -546,12 +534,6 @@ parameters:
count: 5
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Entity\\AbstractEntityPersister\:\:loadAll\(\) has no return type specified\.$#'
identifier: missingType.return
count: 1
path: src/Cache/Persister/Entity/AbstractEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Cache\\Persister\\Entity\\AbstractEntityPersister\:\:loadManyToManyCollection\(\) has parameter \$assoc with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
@@ -630,6 +612,18 @@ parameters:
count: 1
path: src/Cache/TimestampQueryCacheValidator.php
-
message: '#^Call to function is_a\(\) with arguments class\-string\<Doctrine\\ORM\\EntityRepository\>, ''Doctrine\\\\ORM\\\\EntityRepository'' and true will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Configuration.php
-
message: '#^Call to function is_a\(\) with arguments class\-string\<Doctrine\\ORM\\EntityRepository\>, ''Doctrine\\\\Persistence\\\\ObjectRepository'' and true will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Configuration.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Configuration'' and ''getResultCache'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
@@ -852,6 +846,12 @@ parameters:
count: 1
path: src/EntityManager.php
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:findBy\(\) should return list\<T of object\> but returns array\<mixed\>\.$#'
identifier: return.type
count: 1
path: src/EntityRepository.php
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:findOneBy\(\) should return \(T of object\)\|null but returns object\|null\.$#'
identifier: return.type
@@ -859,7 +859,7 @@ parameters:
path: src/EntityRepository.php
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\>&Doctrine\\Common\\Collections\\Selectable\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
identifier: return.type
count: 1
path: src/EntityRepository.php
@@ -973,13 +973,13 @@ parameters:
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Parameter &\$id by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, string\>, array\<int\|string, string\> given\.$#'
message: '#^Parameter &\$id by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, string\>, array\<string\> given\.$#'
identifier: parameterByRef.type
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
-
message: '#^Parameter &\$nonemptyComponents by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, bool\>, array\<int\|string, bool\> given\.$#'
message: '#^Parameter &\$nonemptyComponents by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, bool\>, array\<bool\> given\.$#'
identifier: parameterByRef.type
count: 1
path: src/Internal/Hydration/AbstractHydrator.php
@@ -1056,6 +1056,12 @@ parameters:
count: 1
path: src/Internal/HydrationCompleteHandler.php
-
message: '#^Offset int\|null might not exist on array\<int, object\>\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Internal/StronglyConnectedComponents.php
-
message: '#^Property Doctrine\\ORM\\Internal\\StronglyConnectedComponents\:\:\$representingNodes \(array\<int, object\>\) does not accept array\<int\|string, object\>\.$#'
identifier: assign.propertyType
@@ -1188,6 +1194,12 @@ parameters:
count: 5
path: src/Mapping/ClassMetadata.php
-
message: '#^Call to function is_a\(\) with Doctrine\\DBAL\\Platforms\\OraclePlatform and ''Doctrine\\\\DBAL…'' will always evaluate to false\.$#'
identifier: function.impossibleType
count: 1
path: src/Mapping/ClassMetadataFactory.php
-
message: '#^If condition is always true\.$#'
identifier: if.alwaysTrue
@@ -1669,7 +1681,7 @@ parameters:
path: src/Mapping/ClassMetadataInfo.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<T of object\>\:\:\$embeddedClasses \(array\<string, array\{class\: class\-string, columnPrefix\: string\|null, declaredField\: string\|null, originalField\: string\|null, inherited\?\: class\-string, declared\?\: class\-string\}\>\) does not accept non\-empty\-array\<int\|string, array\{class\: string, columnPrefix\: mixed, declaredField\: mixed, originalField\: mixed, inherited\?\: class\-string, declared\?\: class\-string\}\>\.$#'
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<T of object\>\:\:\$embeddedClasses \(array\<string, array\{class\: class\-string, columnPrefix\: string\|null, declaredField\: string\|null, originalField\: string\|null, inherited\?\: class\-string, declared\?\: class\-string\}\>\) does not accept non\-empty\-array\<array\{class\: string, columnPrefix\: mixed, declaredField\: mixed, originalField\: mixed, inherited\?\: class\-string, declared\?\: class\-string\}\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadataInfo.php
@@ -1681,19 +1693,7 @@ parameters:
path: src/Mapping/ClassMetadataInfo.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<T of object\>\:\:\$namedNativeQueries \(array\<string, array\<string, mixed\>\>\) does not accept array\<int\|string, array\<string, mixed\>\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadataInfo.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<T of object\>\:\:\$namedQueries \(array\<string, array\<string, mixed\>\>\) does not accept array\<int\|string, array\<string, mixed\>\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadataInfo.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<T of object\>\:\:\$sqlResultSetMappings \(array\<string, array\{name\: string, entities\: array\<mixed\>, columns\: array\<mixed\>\}\>\) does not accept non\-empty\-array\<int\|string, array\<string, mixed\>\>\.$#'
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<T of object\>\:\:\$sqlResultSetMappings \(array\<string, array\{name\: string, entities\: array\<mixed\>, columns\: array\<mixed\>\}\>\) does not accept non\-empty\-array\<non\-empty\-array\<string, mixed\>\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/ClassMetadataInfo.php
@@ -1722,6 +1722,12 @@ parameters:
count: 1
path: src/Mapping/ClassMetadataInfo.php
-
message: '#^Template type T is declared as covariant, but occurs in invariant position in return type of method Doctrine\\ORM\\Mapping\\ClassMetadataInfo\:\:getReflectionClass\(\)\.$#'
identifier: generics.variance
count: 1
path: src/Mapping/ClassMetadataInfo.php
-
message: '#^@readonly property cannot have a default value\.$#'
identifier: property.readOnlyByPhpDocDefaultValue
@@ -2742,18 +2748,6 @@ parameters:
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandCriteriaParameters\(\) should return array\{list\<mixed\>, list\<int\|string\|null\>\} but returns array\{array\<mixed\>, list\<int\|string\|null\>\}\.$#'
identifier: return.type
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandParameters\(\) should return array\{list\<mixed\>, list\<int\|string\|null\>\} but returns array\{array\<mixed\>, list\<int\|string\|null\>\}\.$#'
identifier: return.type
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandToManyParameters\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
@@ -2790,12 +2784,6 @@ parameters:
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getIndividualValue\(\) should return list\<mixed\> but returns array\<mixed\>\.$#'
identifier: return.type
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getManyToManyCollection\(\) has parameter \$assoc with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
@@ -2856,24 +2844,12 @@ parameters:
count: 5
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:load\(\) has parameter \$assoc with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 5
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:loadAll\(\) has no return type specified\.$#'
identifier: missingType.return
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:loadCollectionFromStatement\(\) has parameter \$coll with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
@@ -2922,18 +2898,6 @@ parameters:
count: 8
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Offset ''relationToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Offset ''sourceToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Offset ''targetToSourceKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
identifier: offsetAccess.notFound
@@ -2988,12 +2952,6 @@ parameters:
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Strict comparison using \=\=\= between string and null will always evaluate to false\.$#'
identifier: identical.alwaysFalse
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\CachedPersisterContext\:\:__construct\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
@@ -3048,12 +3006,6 @@ parameters:
count: 5
path: src/Persisters/Entity/EntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:loadAll\(\) has no return type specified\.$#'
identifier: missingType.return
count: 1
path: src/Persisters/Entity/EntityPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\EntityPersister\:\:loadManyToManyCollection\(\) has parameter \$assoc with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
@@ -3195,33 +3147,6 @@ parameters:
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '''
#^Call to method __construct\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
'''
identifier: staticMethod.deprecatedClass
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '''
#^Call to method generateProxyClasses\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
'''
identifier: staticMethod.deprecatedClass
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '''
#^Call to method getProxy\(\) of deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
The AbstractProxyFactory class is deprecated since doctrine/common 3\.5\.$#
'''
identifier: staticMethod.deprecatedClass
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '''
#^Class Doctrine\\ORM\\Proxy\\ProxyFactory extends deprecated class Doctrine\\Common\\Proxy\\AbstractProxyFactory\:
@@ -3243,15 +3168,6 @@ parameters:
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '''
#^Instantiation of deprecated class Doctrine\\Common\\Proxy\\ProxyGenerator\:
The ProxyGenerator class is deprecated since doctrine/common 3\.5\.$#
'''
identifier: new.deprecated
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createCloner\(\) has Doctrine\\ORM\\EntityNotFoundException in PHPDoc @throws tag but it''s not thrown\.$#'
identifier: throws.unusedType
@@ -3684,7 +3600,7 @@ parameters:
-
message: '#^Property Doctrine\\ORM\\Query\\Filter\\SQLFilter\:\:\$parameters \(array\<string, array\{type\: string, value\: mixed, is_list\: bool\}\>\) does not accept non\-empty\-array\<string, array\{value\: mixed, type\: int\|string, is_list\: bool\}\>\.$#'
identifier: assign.propertyType
count: 1
count: 2
path: src/Query/Filter/SQLFilter.php
-
@@ -3987,12 +3903,6 @@ parameters:
count: 5
path: src/Query/SqlWalker.php
-
message: '#^Property Doctrine\\ORM\\Query\\SqlWalker\:\:\$selectedClasses \(array\<string, array\{class\: Doctrine\\ORM\\Mapping\\ClassMetadata, dqlAlias\: string, resultAlias\: string\|null\}\>\) does not accept non\-empty\-array\<int\|string, array\{class\: Doctrine\\ORM\\Mapping\\ClassMetadata, dqlAlias\: mixed, resultAlias\: string\|null\}\>\.$#'
identifier: assign.propertyType
count: 1
path: src/Query/SqlWalker.php
-
message: '#^Property Doctrine\\ORM\\Query\\SqlWalker\:\:\$selectedClasses with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
@@ -4269,12 +4179,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/ConvertMappingCommand.php
-
message: '#^Parameter \#2 \$destPath of method Doctrine\\ORM\\Tools\\Console\\Command\\ConvertMappingCommand\:\:getExporter\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/ConvertMappingCommand.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
@@ -4299,12 +4203,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/GenerateEntitiesCommand.php
-
message: '#^Parameter \#2 \$outputDirectory of method Doctrine\\ORM\\Tools\\EntityGenerator\:\:generate\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateEntitiesCommand.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
@@ -4323,12 +4221,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#2 \$proxyDir of method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClasses\(\) expects string\|null, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$customRepositoryClassName\.$#'
identifier: property.notFound
@@ -4347,12 +4239,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/GenerateRepositoriesCommand.php
-
message: '#^Parameter \#2 \$outputDirectory of method Doctrine\\ORM\\Tools\\EntityRepositoryGenerator\:\:writeEntityRepositoryClass\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateRepositoriesCommand.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:formatMappings\(\) has parameter \$propertyMappings with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
@@ -5067,6 +4953,12 @@ parameters:
count: 1
path: src/Tools/Pagination/LimitSubqueryOutputWalker.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Pagination\\Paginator\:\:setUseOutputWalkers\(\) should return static\(Doctrine\\ORM\\Tools\\Pagination\\Paginator\<T\>\) but returns \$this\(Doctrine\\ORM\\Tools\\Pagination\\Paginator\<T\>\)\.$#'
identifier: return.type
count: 1
path: src/Tools/Pagination/Paginator.php
-
message: '#^PHPDoc tag @var for variable \$parameters contains generic interface Doctrine\\Common\\Collections\\Collection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
@@ -5091,12 +4983,6 @@ parameters:
count: 5
path: src/Tools/ResolveTargetEntityListener.php
-
message: '#^Parameter \#1 \$className of method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory\<Doctrine\\ORM\\Mapping\\ClassMetadata\>\:\:setMetadataFor\(\) expects class\-string, \(int\|string\) given\.$#'
identifier: argument.type
count: 1
path: src/Tools/ResolveTargetEntityListener.php
-
message: '#^Call to function is_numeric\(\) with int\<0, max\> will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
@@ -5589,8 +5475,26 @@ parameters:
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Offset ''joinTable'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Offset ''relationToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Offset ''sourceToTargetKeyColumns'' might not exist on array\{cache\?\: array, cascade\: array\<string\>, declared\?\: class\-string, fetch\: mixed, fieldName\: string, id\?\: bool, inherited\?\: class\-string, indexBy\?\: string, \.\.\.\}\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Utility/PersisterHelper.php

View File

@@ -57,6 +57,17 @@ parameters:
count: 2
path: src/Tools/SchemaTool.php
-
message: '#introspectSchema#'
count: 2
identifier: 'method.notFound'
path: src/Tools/SchemaTool.php
-
message: '#getMaxIdentifierLength#'
identifier: 'method.deprecatedClass'
path: src/Mapping/ClassMetadataFactory.php
-
message: "#^Parameter \\#2 \\$start of method Doctrine\\\\DBAL\\\\Platforms\\\\AbstractPlatform\\:\\:getSubstringExpression\\(\\) expects int, string given\\.$#"
count: 1

View File

@@ -11,7 +11,7 @@ parameters:
# Fallback logic for DBAL 2
-
message: '/Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command/'
message: '/Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner\:\:addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command/'
path: src/Tools/Console/ConsoleRunner.php
- '/^Class Doctrine\\DBAL\\Platforms\\SQLAnywherePlatform not found\.$/'
@@ -33,6 +33,10 @@ parameters:
message: '/^Instanceof between Doctrine\\DBAL\\Platforms\\AbstractPlatform and Doctrine\\DBAL\\Platforms\\MySQLPlatform will always evaluate to false\.$/'
path: src/Utility/LockSqlHelper.php
# Compatibility with Collections 1
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\>&Doctrine\\Common\\Collections\\Selectable\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
# Forward compatibility with Collections 3
-
message: '#^Parameter \$order of anonymous function has invalid type Doctrine\\Common\\Collections\\Order\.$#'
@@ -100,3 +104,19 @@ parameters:
-
message: '#^Parameter \#1 \$classNames of method Doctrine\\ORM\\Mapping\\ClassMetadataInfo\<object\>\:\:setParentClasses\(\) expects list\<class\-string\>, array\<string\> given\.$#'
path: src/Mapping/ClassMetadataFactory.php
-
message: '#loadMappingFile#'
identifier: 'return.type'
path: src/Mapping/Driver/XmlDriver.php
-
message: '#injectObjectManager#'
identifier: 'method.deprecatedInterface'
path: src/UnitOfWork.php
-
message: '#^Static method Doctrine\\Common\\Collections\\Criteria\:\:create\(\) invoked with 1 parameter, 0 required\.$#'
identifier: arguments.count
count: 3
path: src/Persisters/Collection/OneToManyPersister.php

View File

@@ -9,7 +9,7 @@ parameters:
# Fallback logic for DBAL 2
-
message: '/Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command/'
message: '/Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner\:\:addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command/'
path: src/Tools/Console/ConsoleRunner.php
- '/^Class Doctrine\\DBAL\\Platforms\\SQLAnywherePlatform not found\.$/'

View File

@@ -1,21 +0,0 @@
#!/bin/bash
# This script is a small convenience wrapper for running the doctrine testsuite against a large bunch of databases.
# Just create the phpunit.xmls as described in the array below and configure the specific files <php /> section
# to connect to that database. Just omit a file if you don't have that database and the tests will be skipped.
configs[1]="mysql.phpunit.xml"
configs[2]='postgres.phpunit.xml'
configs[3]='sqlite.phpunit.xml'
configs[4]='oracle.phpunit.xml'
configs[5]='db2.phpunit.xml'
configs[6]='pdo-ibm.phpunit.xml'
configs[7]='sqlsrv.phpunit.xml'
for i in "${configs[@]}"; do
if [ -f "$i" ];
then
echo "RUNNING TESTS WITH CONFIG $i"
phpunit -c "$i" "$@"
fi;
done

View File

@@ -21,7 +21,6 @@ use Doctrine\ORM\Internal\Hydration\IterableResult;
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\Mapping\MappingException;
use LogicException;
@@ -1144,10 +1143,6 @@ abstract class AbstractQuery
throw new LogicException('Uninitialized result set mapping.');
}
if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
throw QueryException::iterateWithMixedResultNotAllowed();
}
$stmt = $this->_doExecute();
return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints);
@@ -1380,7 +1375,7 @@ abstract class AbstractQuery
}
/**
* Executes the query and returns a the resulting Statement object.
* Executes the query and returns the resulting Statement object.
*
* @return Result|int The executed database statement that holds
* the results, or an integer indicating how

View File

@@ -59,7 +59,10 @@ class DefaultCollectionHydrator implements CollectionHydrator
$targetRegion = $targetPersister->getCacheRegion();
$list = [];
/** @var EntityCacheEntry[]|null $entityEntries */
/**
* @var EntityCacheEntry[]|null $entityEntries
* @phpstan-ignore method.deprecatedInterface
*/
$entityEntries = $targetRegion->getMultiple($entry);
if ($entityEntries === null) {

View File

@@ -103,7 +103,8 @@ class DefaultQueryCache implements QueryCache
};
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
$entries = $region->getMultiple($cacheKeys) ?? [];
/** @phpstan-ignore method.deprecatedInterface */
$entries = $region->getMultiple($cacheKeys) ?? [];
// @TODO - move to cache hydration component
foreach ($cacheEntry->result as $index => $entry) {
@@ -167,8 +168,9 @@ class DefaultQueryCache implements QueryCache
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
};
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
/** @phpstan-ignore method.deprecatedInterface */
$assocEntries = $assocRegion->getMultiple($assocKeys);
foreach ($assoc['list'] as $assocIndex => $assocId) {

View File

@@ -156,6 +156,7 @@ class FileLockRegion implements ConcurrentRegion
return null;
}
/** @phpstan-ignore method.deprecatedInterface */
return $this->region->getMultiple($collection);
}

View File

@@ -1109,10 +1109,11 @@ class Configuration extends \Doctrine\DBAL\Configuration
public function setLazyGhostObjectEnabled(bool $flag): void
{
// @phpstan-ignore classConstant.deprecatedTrait (Because we support Symfony < 7.3)
if ($flag && ! trait_exists(LazyGhostTrait::class)) {
throw new LogicException(
'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library'
. ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".'
. ' version 6.2 or 7 is not installed. Please run "composer require symfony/var-exporter:^6.4".'
);
}

View File

@@ -28,7 +28,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
*/
public function __construct($entity, EntityManagerInterface $em, array &$changeSet)
{
// @phpstan-ignore staticMethod.deprecatedClass
// @phpstan-ignore method.deprecatedClass
parent::__construct($entity, $em);
$this->entityChangeSet = &$changeSet;

View File

@@ -25,10 +25,12 @@ use TypeError;
use function array_map;
use function array_merge;
use function count;
use function current;
use function end;
use function get_debug_type;
use function in_array;
use function is_array;
use function is_object;
use function sprintf;
/**
@@ -201,8 +203,10 @@ abstract class AbstractHydrator
} else {
yield from $result;
}
} else {
} elseif (is_object(current($result))) {
yield $result;
} else {
yield array_merge(...$result);
}
}
} finally {

View File

@@ -722,7 +722,7 @@ DEPRECATION
'Mapping for %s: the "UUID" id generator strategy is deprecated with no replacement',
$class->name
);
// @phpstan-ignore new.deprecated
// @phpstan-ignore method.deprecatedClass, new.deprecatedClass
$class->setIdGenerator(new UuidGenerator());
break;

View File

@@ -32,13 +32,8 @@ trait ReflectionBasedDriver
|| $metadata->isInheritedEmbeddedClass($property->name);
}
/** @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

@@ -10,8 +10,6 @@ use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
* YamlDriver that additionally looks for mapping information in a global file.
*
* @deprecated This class is being removed from the ORM and won't have any replacement
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class SimplifiedYamlDriver extends YamlDriver
{

View File

@@ -206,7 +206,6 @@ class XmlDriver extends FileDriver
];
if (isset($discrColumn['options'])) {
assert($discrColumn['options'] instanceof SimpleXMLElement);
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
}

View File

@@ -280,7 +280,7 @@ class MappingException extends ORMException
*/
public static function joinTableRequired($fieldName)
{
return new self(sprintf("The mapping of field '%s' requires an the 'joinTable' attribute.", $fieldName));
return new self(sprintf("The mapping of field '%s' requires the 'joinTable' attribute.", $fieldName));
}
/**

View File

@@ -16,6 +16,7 @@ use Doctrine\ORM\Query;
use Doctrine\ORM\Utility\PersisterHelper;
use function array_fill;
use function array_merge;
use function array_pop;
use function count;
use function get_class;
@@ -256,10 +257,17 @@ class ManyToManyPersister extends AbstractCollectionPersister
if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
} elseif ($operator === Comparison::IN || $operator === Comparison::NIN) {
$whereClauses[] = sprintf('te.%s %s (%s)', $field, $operator === Comparison::IN ? 'IN' : 'NOT IN', implode(', ', array_fill(0, count($value), '?')));
foreach ($value as $item) {
$params = array_merge($params, PersisterHelper::convertToParameterValue($item, $this->em));
$paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $item, $targetClass, $this->em));
}
} else {
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
$params[] = $value;
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
$params = array_merge($params, PersisterHelper::convertToParameterValue($value, $this->em));
$paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $value, $targetClass, $this->em));
}
}

View File

@@ -103,7 +103,7 @@ class OneToManyPersister extends AbstractCollectionPersister
// only works with single id identifier entities. Will throw an
// exception in Entity Persisters if that is not the case for the
// 'mappedBy' field.
$criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
$criteria = Criteria::create(true)->where(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
return $persister->count($criteria);
}
@@ -135,7 +135,7 @@ class OneToManyPersister extends AbstractCollectionPersister
// only works with single id identifier entities. Will throw an
// exception in Entity Persisters if that is not the case for the
// 'mappedBy' field.
$criteria = new Criteria();
$criteria = Criteria::create(true);
$criteria->andWhere(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
$criteria->andWhere(Criteria::expr()->eq($mapping['indexBy'], $key));
@@ -158,7 +158,7 @@ class OneToManyPersister extends AbstractCollectionPersister
// only works with single id identifier entities. Will throw an
// exception in Entity Persisters if that is not the case for the
// 'mappedBy' field.
$criteria = new Criteria(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
$criteria = Criteria::create(true)->where(Criteria::expr()->eq($mapping['mappedBy'], $collection->getOwner()));
return $persister->exists($element, $criteria);
}

View File

@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Persisters\Entity;
use BackedEnum;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Connection;
@@ -26,9 +25,7 @@ use Doctrine\ORM\Persisters\Exception\InvalidOrientation;
use Doctrine\ORM\Persisters\Exception\UnrecognizedField;
use Doctrine\ORM\Persisters\SqlExpressionVisitor;
use Doctrine\ORM\Persisters\SqlValueVisitor;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Repository\Exception\InvalidFindByCall;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Utility\IdentifierFlattener;
@@ -37,17 +34,18 @@ use Doctrine\ORM\Utility\PersisterHelper;
use LengthException;
use function array_combine;
use function array_diff_key;
use function array_fill;
use function array_flip;
use function array_keys;
use function array_map;
use function array_merge;
use function array_search;
use function array_unique;
use function array_values;
use function assert;
use function count;
use function implode;
use function is_array;
use function is_object;
use function reset;
use function spl_object_id;
use function sprintf;
@@ -168,14 +166,6 @@ class BasicEntityPersister implements EntityPersister
*/
protected $quotedColumns = [];
/**
* The INSERT SQL statement used for entities handled by this persister.
* This SQL is only generated once per request, if at all.
*
* @var string|null
*/
private $insertSql;
/**
* The quote strategy.
*
@@ -226,6 +216,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();
}
/**
* {@inheritDoc}
*/
@@ -300,8 +300,8 @@ class BasicEntityPersister implements EntityPersister
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
}
// Unset this queued insert, so that the prepareUpdateData() method knows right away
// (for the next entity already) that the current entity has been written to the database
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
// knows right away (for the next entity already) that the current entity has been written to the database
// and no extra updates need to be scheduled to refer to it.
//
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
@@ -392,7 +392,7 @@ class BasicEntityPersister implements EntityPersister
$types = [];
foreach ($id as $field => $value) {
$types = array_merge($types, $this->getTypes($field, $value, $versionedClass));
$types = array_merge($types, PersisterHelper::inferParameterTypes($field, $value, $versionedClass, $this->em));
}
return $types;
@@ -953,8 +953,31 @@ class BasicEntityPersister implements EntityPersister
continue;
}
$sqlParams = array_merge($sqlParams, $this->getValues($value));
$sqlTypes = array_merge($sqlTypes, $this->getTypes($field, $value, $this->class));
if ($operator === Comparison::IN || $operator === Comparison::NIN) {
if (! is_array($value)) {
$value = [$value];
}
foreach ($value as $item) {
if ($item === null) {
/*
* Compare this to how \Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getSelectConditionStatementSQL
* creates the "[NOT] IN (...)" expression - for NULL values, it does _not_ insert a placeholder in the
* SQL and instead adds an extra ... OR ... IS NULL condition. So we need to skip NULL values here as
* well to create a parameters list that matches the SQL.
*/
continue;
}
$sqlParams = array_merge($sqlParams, PersisterHelper::convertToParameterValue($item, $this->em));
$sqlTypes = array_merge($sqlTypes, PersisterHelper::inferParameterTypes($field, $item, $this->class, $this->em));
}
continue;
}
$sqlParams = array_merge($sqlParams, PersisterHelper::convertToParameterValue($value, $this->em));
$sqlTypes = array_merge($sqlTypes, PersisterHelper::inferParameterTypes($field, $value, $this->class, $this->em));
}
return [$sqlParams, $sqlTypes];
@@ -1274,7 +1297,7 @@ class BasicEntityPersister implements EntityPersister
*/
protected function getSelectColumnsSQL()
{
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->filterHash === $this->em->getFilters()->getHash()) {
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->isFilterHashUpToDate()) {
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -1387,7 +1410,7 @@ class BasicEntityPersister implements EntityPersister
}
$this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
$this->filterHash = $this->em->getFilters()->getHash();
$this->updateFilterHash();
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -1463,22 +1486,17 @@ class BasicEntityPersister implements EntityPersister
*/
public function getInsertSQL()
{
if ($this->insertSql !== null) {
return $this->insertSql;
}
$columns = $this->getInsertColumnList();
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
if (empty($columns)) {
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
if ($columns === []) {
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
return $this->insertSql;
return $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
}
$values = [];
$columns = array_unique($columns);
$placeholders = [];
$columns = array_unique($columns);
foreach ($columns as $column) {
$placeholder = '?';
@@ -1492,15 +1510,13 @@ class BasicEntityPersister implements EntityPersister
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
}
$values[] = $placeholder;
$placeholders[] = $placeholder;
}
$columns = implode(', ', $columns);
$values = implode(', ', $values);
$columns = implode(', ', $columns);
$placeholders = implode(', ', $placeholders);
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
return $this->insertSql;
return sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $placeholders);
}
/**
@@ -1698,6 +1714,8 @@ class BasicEntityPersister implements EntityPersister
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
$comparison = $comparison ?? (is_array($value) ? Comparison::IN : Comparison::EQ);
$selectedColumns = [];
$columns = $this->getSelectConditionStatementColumnSQL($field, $assoc);
@@ -1717,46 +1735,50 @@ class BasicEntityPersister implements EntityPersister
$placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->platform);
}
if ($comparison !== null) {
// special case null value handling
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) {
$selectedColumns[] = $column . ' IS NULL';
continue;
}
if ($comparison === Comparison::NEQ && $value === null) {
$selectedColumns[] = $column . ' IS NOT NULL';
continue;
}
$selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
// special case null value handling
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) {
$selectedColumns[] = $column . ' IS NULL';
continue;
}
if (is_array($value)) {
$in = sprintf('%s IN (%s)', $column, $placeholder);
if ($comparison === Comparison::NEQ && $value === null) {
$selectedColumns[] = $column . ' IS NOT NULL';
if (array_search(null, $value, true) !== false) {
$selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column);
continue;
}
if ($comparison === Comparison::IN || $comparison === Comparison::NIN) {
if (! is_array($value)) {
$value = [$value];
}
if ($value === []) {
$selectedColumns[] = '1=0';
continue;
}
$selectedColumns[] = $in;
$nullKeys = array_keys($value, null, true);
$nonNullValues = array_diff_key($value, array_flip($nullKeys));
$placeholders = implode(', ', array_fill(0, count($nonNullValues), $placeholder));
$in = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholders);
if ($nullKeys) {
if ($nonNullValues) {
$selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column);
} else {
$selectedColumns[] = $column . ' IS NULL';
}
} else {
$selectedColumns[] = $in;
}
continue;
}
if ($value === null) {
$selectedColumns[] = sprintf('%s IS NULL', $column);
continue;
}
$selectedColumns[] = sprintf('%s = %s', $column, $placeholder);
$selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
}
return implode(' AND ', $selectedColumns);
@@ -1947,8 +1969,18 @@ class BasicEntityPersister implements EntityPersister
continue; // skip null values.
}
$types = array_merge($types, $this->getTypes($field, $value, $this->class));
$params = array_merge($params, $this->getValues($value));
if (is_array($value)) {
$nonNullValues = array_diff_key($value, array_flip(array_keys($value, null, true)));
foreach ($nonNullValues as $item) {
$types = array_merge($types, PersisterHelper::inferParameterTypes($field, $item, $this->class, $this->em));
$params = array_merge($params, PersisterHelper::convertToParameterValue($item, $this->em));
}
continue;
}
$types = array_merge($types, PersisterHelper::inferParameterTypes($field, $value, $this->class, $this->em));
$params = array_merge($params, PersisterHelper::convertToParameterValue($value, $this->em));
}
return [$params, $types];
@@ -1976,127 +2008,13 @@ class BasicEntityPersister implements EntityPersister
continue; // skip null values.
}
$types = array_merge($types, $this->getTypes($criterion['field'], $criterion['value'], $criterion['class']));
$params = array_merge($params, $this->getValues($criterion['value']));
$types = array_merge($types, PersisterHelper::inferParameterTypes($criterion['field'], $criterion['value'], $criterion['class'], $this->em));
$params = array_merge($params, PersisterHelper::convertToParameterValue($criterion['value'], $this->em));
}
return [$params, $types];
}
/**
* Infers field types to be used by parameter type casting.
*
* @param mixed $value
*
* @return int[]|null[]|string[]
* @phpstan-return list<int|string|null>
*
* @throws QueryException
*/
private function getTypes(string $field, $value, ClassMetadata $class): array
{
$types = [];
switch (true) {
case isset($class->fieldMappings[$field]):
$types = array_merge($types, [$class->fieldMappings[$field]['type']]);
break;
case isset($class->associationMappings[$field]):
$assoc = $class->associationMappings[$field];
$class = $this->em->getClassMetadata($assoc['targetEntity']);
if (! $assoc['isOwningSide']) {
$assoc = $class->associationMappings[$assoc['mappedBy']];
$class = $this->em->getClassMetadata($assoc['targetEntity']);
}
$columns = $assoc['type'] === ClassMetadata::MANY_TO_MANY
? $assoc['relationToTargetKeyColumns']
: $assoc['sourceToTargetKeyColumns'];
foreach ($columns as $column) {
$types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em);
}
break;
default:
$types[] = null;
break;
}
if (is_array($value)) {
return array_map(static function ($type) {
$type = Type::getType($type);
return $type->getBindingType() + Connection::ARRAY_PARAM_OFFSET;
}, $types);
}
return $types;
}
/**
* Retrieves the parameters that identifies a value.
*
* @param mixed $value
*
* @return mixed[]
*/
private function getValues($value): array
{
if (is_array($value)) {
$newValue = [];
foreach ($value as $itemValue) {
$newValue = array_merge($newValue, $this->getValues($itemValue));
}
return [$newValue];
}
return $this->getIndividualValue($value);
}
/**
* Retrieves an individual parameter value.
*
* @param mixed $value
*
* @phpstan-return list<mixed>
*/
private function getIndividualValue($value): array
{
if (! is_object($value)) {
return [$value];
}
if ($value instanceof BackedEnum) {
return [$value->value];
}
$valueClass = DefaultProxyClassNameResolver::getClass($value);
if ($this->em->getMetadataFactory()->isTransient($valueClass)) {
return [$value];
}
$class = $this->em->getClassMetadata($valueClass);
if ($class->isIdentifierComposite) {
$newValue = [];
foreach ($class->getIdentifierValues($value) as $innerValue) {
$newValue = array_merge($newValue, $this->getValues($innerValue));
}
return $newValue;
}
return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)];
}
/**
* {@inheritDoc}
*/

View File

@@ -73,7 +73,7 @@ interface EntityPersister
/**
* Expands the parameters from the given criteria and use the correct binding types if found.
*
* @param string[] $criteria
* @param array<string, mixed> $criteria
*
* @phpstan-return array{list<mixed>, list<int|string|null>}
*/
@@ -258,6 +258,8 @@ interface EntityPersister
* @param int|null $offset
* @phpstan-param array<string, string>|null $orderBy
* @phpstan-param array<string, mixed> $criteria
*
* @return mixed[]
*/
public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null);

View File

@@ -63,7 +63,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
*/
private function getVersionedClassMetadata(): ClassMetadata
{
if (isset($this->class->fieldMappings[$this->class->versionField]['inherited'])) {
if ($this->class->versionField !== null && isset($this->class->fieldMappings[$this->class->versionField]['inherited'])) {
$definingClassName = $this->class->fieldMappings[$this->class->versionField]['inherited'];
return $this->em->getClassMetadata($definingClassName);
@@ -149,7 +149,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
// Execute all inserts. For each entity:
// 1) Insert on root table
// 2) Insert on sub tables
foreach ($this->queuedInserts as $entity) {
foreach ($this->queuedInserts as $key => $entity) {
$insertData = $this->prepareInsertData($entity);
// Execute insert on root table
@@ -194,9 +194,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
if ($this->class->requiresFetchAfterChange) {
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
}
}
$this->queuedInserts = [];
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
// knows right away (for the next entity already) that the current entity has been written to the database
// and no extra updates need to be scheduled to refer to it.
//
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
// from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they
// were given to our addInsert() method.
unset($this->queuedInserts[$key]);
}
}
/**
@@ -404,7 +411,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
protected function getSelectColumnsSQL()
{
// Create the column list fragment only once
if ($this->currentPersisterContext->selectColumnListSql !== null) {
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->isFilterHashUpToDate()) {
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -495,6 +502,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
}
$this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
$this->updateFilterHash();
return $this->currentPersisterContext->selectColumnListSql;
}

View File

@@ -38,7 +38,7 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
*/
protected function getSelectColumnsSQL()
{
if ($this->currentPersisterContext->selectColumnListSql !== null) {
if ($this->currentPersisterContext->selectColumnListSql !== null && $this->isFilterHashUpToDate()) {
return $this->currentPersisterContext->selectColumnListSql;
}
@@ -92,6 +92,7 @@ class SingleTablePersister extends AbstractEntityInheritancePersister
}
$this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
$this->updateFilterHash();
return $this->currentPersisterContext->selectColumnListSql;
}

View File

@@ -172,10 +172,12 @@ EOPHP;
$this->isLazyGhostObjectEnabled = false;
// @phpstan-ignore new.deprecatedClass, method.deprecatedClass
$proxyGenerator = new ProxyGenerator($proxyDir, $proxyNs);
// @phpstan-ignore classConstant.deprecatedInterface
// @phpstan-ignore classConstant.deprecatedInterface, method.deprecatedClass
$proxyGenerator->setPlaceholder('baseProxyInterface', LegacyProxy::class);
// @phpstan-ignore method.deprecatedClass
parent::__construct($proxyGenerator, $em->getMetadataFactory(), $autoGenerate);
}
@@ -205,6 +207,7 @@ EOPHP;
public function getProxy($className, array $identifier)
{
if (! $this->isLazyGhostObjectEnabled) {
// @phpstan-ignore method.deprecatedClass
return parent::getProxy($className, $identifier);
}
@@ -226,6 +229,7 @@ EOPHP;
public function generateProxyClasses(array $classes, $proxyDir = null)
{
if (! $this->isLazyGhostObjectEnabled) {
// @phpstan-ignore method.deprecatedClass
return parent::generateProxyClasses($classes, $proxyDir);
}
@@ -422,7 +426,10 @@ EOPHP;
continue;
}
$property->setAccessible(true);
if (PHP_VERSION_ID < 80100) {
$property->setAccessible(true);
}
$property->setValue($proxy, $property->getValue($original));
}
};
@@ -568,6 +575,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

@@ -14,9 +14,10 @@ class InListExpression extends InExpression
public function __construct(ArithmeticExpression $expression, array $literals, bool $not = false)
{
$this->literals = $literals;
$this->not = $not;
// @phpstan-ignore property.deprecatedClass
$this->not = $not;
// @phpstan-ignore staticMethod.deprecatedClass
// @phpstan-ignore method.deprecatedClass
parent::__construct($expression);
}
}

View File

@@ -13,9 +13,10 @@ class InSubselectExpression extends InExpression
public function __construct(ArithmeticExpression $expression, Subselect $subselect, bool $not = false)
{
$this->subselect = $subselect;
$this->not = $not;
// @phpstan-ignore property.deprecatedClass
$this->not = $not;
// @phpstan-ignore staticMethod.deprecatedClass
// @phpstan-ignore method.deprecatedClass
parent::__construct($expression);
}
}

View File

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

View File

@@ -411,7 +411,7 @@ class ResultSetMappingBuilder extends ResultSetMapping
[$relation, $fieldName] = explode('.', $fieldName);
}
if (isset($classMetadata->associationMappings[$relation])) {
if ($relation !== null && isset($classMetadata->associationMappings[$relation])) {
if ($relation) {
$associationMapping = $classMetadata->associationMappings[$relation];
$joinAlias = $alias . $relation;

View File

@@ -1587,7 +1587,9 @@ class SqlWalker implements TreeWalker
$sqlParts[] = $col . ' AS ' . $columnAlias;
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
if ($resultAlias !== null) {
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
}
$this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
@@ -1622,7 +1624,9 @@ class SqlWalker implements TreeWalker
$sqlParts[] = $col . ' AS ' . $columnAlias;
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
if ($resultAlias !== null) {
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
}
$this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
}
@@ -2356,7 +2360,9 @@ class SqlWalker implements TreeWalker
*/
public function walkInListExpression(AST\InListExpression $inExpr): string
{
/** @phpstan-ignore property.deprecatedClass */
return $this->walkArithmeticExpression($inExpr->expression)
/** @phpstan-ignore property.deprecatedClass */
. ($inExpr->not ? ' NOT' : '') . ' IN ('
. implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals))
. ')';
@@ -2367,7 +2373,9 @@ class SqlWalker implements TreeWalker
*/
public function walkInSubselectExpression(AST\InSubselectExpression $inExpr): string
{
/** @phpstan-ignore property.deprecatedClass */
return $this->walkArithmeticExpression($inExpr->expression)
/** @phpstan-ignore property.deprecatedClass */
. ($inExpr->not ? ' NOT' : '') . ' IN ('
. $this->walkSubselect($inExpr->subselect)
. ')';

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use function method_exists;
/**
* Forward compatibility with Symfony Console 7.4
*
* @internal
*/
trait ApplicationCompatibility
{
private static function addCommandToApplication(Application $application, Command $command): ?Command
{
// @phpstan-ignore function.alreadyNarrowedType (This method did not exist before Symfony 7.4)
if (method_exists(Application::class, 'addCommand')) {
return $application->addCommand($command);
}
// @phpstan-ignore method.notFound
return $application->add($command);
}
}

View File

@@ -39,6 +39,7 @@ abstract class AbstractEntityManagerCommand extends Command
$helper = $this->getHelper('em');
assert($helper instanceof EntityManagerHelper);
/** @phpstan-ignore method.deprecatedClass */
return $helper->getEntityManager();
}

View File

@@ -23,8 +23,7 @@ class CollectionRegionCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:region:collection')
->setDescription('Clear a second-level cache collection region')

View File

@@ -23,8 +23,7 @@ class EntityRegionCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:region:entity')
->setDescription('Clear a second-level cache entity region')

View File

@@ -21,8 +21,7 @@ class MetadataCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:metadata')
->setDescription('Clear all metadata cache of the various cache drivers')

View File

@@ -30,8 +30,7 @@ class QueryCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:query')
->setDescription('Clear all query cache of the various cache drivers')

View File

@@ -23,8 +23,7 @@ class QueryRegionCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:region:query')
->setDescription('Clear a second-level cache query region')

View File

@@ -32,8 +32,7 @@ class ResultCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:result')
->setDescription('Clear all result cache of the various cache drivers')

View File

@@ -75,8 +75,7 @@ class ConvertDoctrine1SchemaCommand extends Command
$this->metadataExporter = $metadataExporter;
}
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:convert-d1-schema')
->setAliases(['orm:convert:d1-schema'])

View File

@@ -40,8 +40,7 @@ class ConvertMappingCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:convert-mapping')
->setAliases(['orm:convert:mapping'])

View File

@@ -22,8 +22,7 @@ class EnsureProductionSettingsCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:ensure-production-settings')
->setDescription('Verify that Doctrine is properly configured for a production environment')

View File

@@ -31,8 +31,7 @@ class GenerateEntitiesCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:generate-entities')
->setAliases(['orm:generate:entities'])

View File

@@ -29,8 +29,7 @@ class GenerateProxiesCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:generate-proxies')
->setAliases(['orm:generate:proxies'])

View File

@@ -30,8 +30,7 @@ class GenerateRepositoriesCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:generate-repositories')
->setAliases(['orm:generate:repositories'])

View File

@@ -23,8 +23,7 @@ class InfoCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:info')
->setDescription('Show basic information about all mapped entities')
@@ -39,7 +38,7 @@ EOT
private function doExecute(InputInterface $input, OutputInterface $output): int
{
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();
$ui = new SymfonyStyle($input, $output);
$entityManager = $this->getEntityManager($input);
@@ -48,7 +47,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

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

View File

@@ -30,8 +30,7 @@ class RunDqlCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:run-dql')
->setDescription('Executes arbitrary DQL directly from the command line')

View File

@@ -27,6 +27,10 @@ abstract class AbstractCommand extends AbstractEntityManagerCommand
*/
abstract protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui);
private function doConfigure(): void
{
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
$ui = new SymfonyStyle($input, $output);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command\SchemaTool;
use Doctrine\ORM\Tools\Console\CommandCompatibility;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -19,8 +20,9 @@ use function sprintf;
*/
class CreateCommand extends AbstractCommand
{
/** @return void */
protected function configure()
use CommandCompatibility;
private function doConfigure(): void
{
$this->setName('orm:schema-tool:create')
->setDescription('Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output')
@@ -44,6 +46,11 @@ EOT
);
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
return parent::execute($input, $output);
}
/**
* {@inheritDoc}
*/

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command\SchemaTool;
use Doctrine\ORM\Tools\Console\CommandCompatibility;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -20,8 +21,9 @@ use function sprintf;
*/
class DropCommand extends AbstractCommand
{
/** @return void */
protected function configure()
use CommandCompatibility;
private function doConfigure(): void
{
$this->setName('orm:schema-tool:drop')
->setDescription('Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output')
@@ -48,6 +50,11 @@ EOT
);
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
return parent::execute($input, $output);
}
/**
* {@inheritDoc}
*/

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command\SchemaTool;
use Doctrine\ORM\Tools\Console\CommandCompatibility;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -21,11 +22,12 @@ use function sprintf;
*/
class UpdateCommand extends AbstractCommand
{
use CommandCompatibility;
/** @var string */
protected $name = 'orm:schema-tool:update';
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName($this->name)
->setDescription('Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata')
@@ -72,6 +74,11 @@ EOT
);
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
return parent::execute($input, $output);
}
/**
* {@inheritDoc}
*/

View File

@@ -23,8 +23,7 @@ class ValidateSchemaCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:validate-schema')
->setDescription('Validate the mapping files')

View File

@@ -9,10 +9,32 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
if ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) {
// Symfony 8
if ((new ReflectionMethod(Command::class, 'configure'))->hasReturnType()) {
/** @internal */
trait CommandCompatibility
{
protected function configure(): void
{
$this->doConfigure();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
return $this->doExecute($input, $output);
}
}
// Symfony 7
} elseif ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) {
/** @internal */
trait CommandCompatibility
{
/** @return void */
protected function configure()
{
$this->doConfigure();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
return $this->doExecute($input, $output);
@@ -22,6 +44,12 @@ if ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) {
/** @internal */
trait CommandCompatibility
{
/** @return void */
protected function configure()
{
$this->doConfigure();
}
/**
* {@inheritDoc}
*

View File

@@ -23,6 +23,8 @@ use function class_exists;
*/
final class ConsoleRunner
{
use ApplicationCompatibility;
/**
* Create a Symfony Console HelperSet
*
@@ -71,7 +73,7 @@ final class ConsoleRunner
if ($helperSetOrProvider instanceof HelperSet) {
$cli->setHelperSet($helperSetOrProvider);
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass, method.deprecatedClass
$helperSetOrProvider = new HelperSetManagerProvider($helperSetOrProvider);
}
@@ -84,20 +86,20 @@ final class ConsoleRunner
public static function addCommands(Application $cli, ?EntityManagerProvider $entityManagerProvider = null): void
{
if ($entityManagerProvider === null) {
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass, method.deprecatedClass
$entityManagerProvider = new HelperSetManagerProvider($cli->getHelperSet());
}
$connectionProvider = new ConnectionFromManagerProvider($entityManagerProvider);
if (class_exists(DBALConsole\Command\ImportCommand::class)) {
$cli->add(new DBALConsole\Command\ImportCommand());
self::addCommandToApplication($cli, new DBALConsole\Command\ImportCommand());
}
$cli->addCommands(
[
// DBAL Commands
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass, method.deprecatedClass
new DBALConsole\Command\ReservedWordsCommand($connectionProvider),
new DBALConsole\Command\RunSqlCommand($connectionProvider),
@@ -111,16 +113,16 @@ final class ConsoleRunner
new Command\SchemaTool\CreateCommand($entityManagerProvider),
new Command\SchemaTool\UpdateCommand($entityManagerProvider),
new Command\SchemaTool\DropCommand($entityManagerProvider),
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass
new Command\EnsureProductionSettingsCommand($entityManagerProvider),
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass
new Command\ConvertDoctrine1SchemaCommand(),
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass
new Command\GenerateRepositoriesCommand($entityManagerProvider),
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass
new Command\GenerateEntitiesCommand($entityManagerProvider),
new Command\GenerateProxiesCommand($entityManagerProvider),
// @phpstan-ignore new.deprecated
// @phpstan-ignore new.deprecatedClass
new Command\ConvertMappingCommand($entityManagerProvider),
new Command\RunDqlCommand($entityManagerProvider),
new Command\ValidateSchemaCommand($entityManagerProvider),

View File

@@ -16,8 +16,6 @@ use function str_replace;
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
*
* @link www.doctrine-project.org
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class AnnotationExporter extends AbstractExporter
{

View File

@@ -23,8 +23,6 @@ use const PHP_EOL;
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
*
* @link www.doctrine-project.org
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class PhpExporter extends AbstractExporter
{

View File

@@ -21,8 +21,6 @@ use function uasort;
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
*
* @link www.doctrine-project.org
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class XmlExporter extends AbstractExporter
{

View File

@@ -16,8 +16,6 @@ use function count;
* @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
*
* @link www.doctrine-project.org
*
* @phpstan-ignore class.extendsDeprecatedClass
*/
class YamlExporter extends AbstractExporter
{

View File

@@ -149,7 +149,9 @@ class LimitSubqueryOutputWalker extends SqlOutputWalker
$selectAliasToExpressionMap = [];
// Get any aliases that are available for select expressions.
foreach ($AST->selectClause->selectExpressions as $selectExpression) {
$selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
if ($selectExpression->fieldIdentificationVariable !== null) {
$selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
}
}
// Rebuild string orderby expressions to use the select expression they're referencing

View File

@@ -228,7 +228,22 @@ class Paginator implements Countable, IteratorAggregate
*/
private function getCountQuery(): Query
{
$countQuery = $this->cloneQuery($this->query);
/*
As opposed to using self::cloneQuery, the following code does not transfer
a potentially existing result set mapping (either set directly by the user,
or taken from the parser result from a previous invocation of Query::parse())
to the new query object. This is fine, since we are going to completely change the
select clause, so a previously existing result set mapping (RSM) is probably wrong anyway.
In the case of using output walkers, we are even creating a new RSM down below.
In the case of using a tree walker, we want to have a new RSM created by the parser.
*/
$countQuery = new Query($this->query->getEntityManager());
$countQuery->setDQL($this->query->getDQL());
$countQuery->setParameters(clone $this->query->getParameters());
$countQuery->setCacheable(false);
foreach ($this->query->getHints() as $name => $value) {
$countQuery->setHint($name, $value);
}
if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
$countQuery->setHint(CountWalker::HINT_DISTINCT, true);

View File

@@ -86,12 +86,6 @@ class ResolveTargetEntityListener implements EventSubscriber
}
}
foreach ($this->resolveTargetEntities as $interface => $data) {
if ($data['targetEntity'] === $cm->getName()) {
$args->getEntityManager()->getMetadataFactory()->setMetadataFor($interface, $cm);
}
}
foreach ($cm->discriminatorMap as $value => $class) {
if (isset($this->resolveTargetEntities[$class])) {
$cm->addDiscriminatorMapClass($value, $this->resolveTargetEntities[$class]['targetEntity']);

View File

@@ -413,7 +413,7 @@ class SchemaTool
// @phpstan-ignore method.deprecated
if (array_filter($schema->getSequences() + $schema->getTables(), $filter) && ! $this->platform->canEmulateSchemas()) {
// @phpstan-ignore method.deprecated, new.deprecated
// @phpstan-ignore method.deprecated, method.deprecatedClass, new.deprecatedClass
$schema->visit(new RemoveNamespacedAssets());
}
}

View File

@@ -62,6 +62,7 @@ use function array_merge;
use function array_sum;
use function array_values;
use function assert;
use function count;
use function current;
use function func_get_arg;
use function func_num_args;
@@ -77,6 +78,8 @@ use function spl_object_id;
use function sprintf;
use function strtolower;
use const PHP_VERSION_ID;
/**
* The UnitOfWork is responsible for tracking changes to objects during an
* "object-level" transaction and for writing out changes to the database
@@ -1060,7 +1063,9 @@ class UnitOfWork implements PropertyChangedListener
$this->entityStates[$oid] = self::STATE_MANAGED;
$this->scheduleForInsert($entity);
if (! isset($this->entityInsertions[$oid])) {
$this->scheduleForInsert($entity);
}
}
/** @param mixed[] $idValue */
@@ -3168,8 +3173,14 @@ EXCEPTION
$reflField->setValue($entity, $pColl);
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && ! $isIteration && ! $targetClass->isIdentifierComposite && ! isset($assoc['indexBy'])) {
if (
$assoc['type'] === ClassMetadata::ONE_TO_MANY
// is iteration
&& ! (isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION])
// is foreign key composite
&& ! ($targetClass->hasAssociation($assoc['mappedBy']) && count($targetClass->getAssociationMapping($assoc['mappedBy'])['joinColumns'] ?? []) > 1)
&& ! isset($assoc['indexBy'])
) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} else {
$this->loadCollection($pColl);
@@ -3884,7 +3895,9 @@ EXCEPTION
foreach ($this->reflectionPropertiesGetter->getProperties($class->name) as $prop) {
$name = $prop->name;
$prop->setAccessible(true);
if (PHP_VERSION_ID < 80100) {
$prop->setAccessible(true);
}
if (! isset($class->associationMappings[$name])) {
if (! $class->isIdentifier($name)) {

View File

@@ -4,11 +4,19 @@ declare(strict_types=1);
namespace Doctrine\ORM\Utility;
use BackedEnum;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\QueryException;
use RuntimeException;
use function array_map;
use function array_merge;
use function is_array;
use function is_object;
use function sprintf;
/**
@@ -113,4 +121,116 @@ class PersisterHelper
$class->getName()
));
}
/**
* Infers field types to be used by parameter type casting.
*
* @param mixed $value
*
* @return int[]|null[]|string[]
* @phpstan-return list<int|string|null>
*
* @throws QueryException
*/
public static function inferParameterTypes(string $field, $value, ClassMetadata $class, EntityManagerInterface $em): array
{
$types = [];
switch (true) {
case isset($class->fieldMappings[$field]):
$types = array_merge($types, [$class->fieldMappings[$field]['type']]);
break;
case isset($class->associationMappings[$field]):
$assoc = $class->associationMappings[$field];
$class = $em->getClassMetadata($assoc['targetEntity']);
if (! $assoc['isOwningSide']) {
$assoc = $class->associationMappings[$assoc['mappedBy']];
$class = $em->getClassMetadata($assoc['targetEntity']);
}
$columns = $assoc['type'] === ClassMetadata::MANY_TO_MANY
? $assoc['relationToTargetKeyColumns']
: $assoc['sourceToTargetKeyColumns'];
foreach ($columns as $column) {
$types[] = self::getTypeOfColumn($column, $class, $em);
}
break;
default:
$types[] = null;
break;
}
if (is_array($value)) {
return array_map(static function ($type) {
$type = Type::getType($type);
return $type->getBindingType() + Connection::ARRAY_PARAM_OFFSET;
}, $types);
}
return $types;
}
/**
* Converts a value to the type and value required to bind it as a parameter.
*
* @param mixed $value
*
* @return list<mixed>
*/
public static function convertToParameterValue($value, EntityManagerInterface $em): array
{
if (is_array($value)) {
$newValue = [];
foreach ($value as $itemValue) {
$newValue = array_merge($newValue, self::convertToParameterValue($itemValue, $em));
}
return [$newValue];
}
return self::convertIndividualValue($value, $em);
}
/**
* @param mixed $value
*
* @phpstan-return list<mixed>
*/
private static function convertIndividualValue($value, EntityManagerInterface $em): array
{
if (! is_object($value)) {
return [$value];
}
if ($value instanceof BackedEnum) {
return [$value->value];
}
$valueClass = DefaultProxyClassNameResolver::getClass($value);
if ($em->getMetadataFactory()->isTransient($valueClass)) {
return [$value];
}
$class = $em->getClassMetadata($valueClass);
if ($class->isIdentifierComposite) {
$newValue = [];
foreach ($class->getIdentifierValues($value) as $innerValue) {
$newValue = array_merge($newValue, self::convertToParameterValue($innerValue, $em));
}
return $newValue;
}
return [$em->getUnitOfWork()->getSingleIdentifierValue($value)];
}
}

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