Compare commits

...

264 Commits

Author SHA1 Message Date
Alexander M. Turek 78ed4cddeb Merge branch '3.7.x' into 4.0.x
* 3.7.x:
  Merge pull request #12373 from tomme87/12225-fix-hydration-issue
  Add ORDER BY clause to SELECT query
  Bump codecov/codecov-action from 5 to 6
  Mention PostgreSQL jsonb as a reason to favor json over simple_array
  Update docs/en/reference/basic-mapping.rst
  Add nullable enums, default values, query usage and XML mapping to enum docs
  Add documentation for enumType mapping with PHP backed enums
  Group Doctrine workflow updates together
  Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml
  Bump doctrine/.github/.github/workflows/coding-standards.yml
  Bump doctrine/.github/.github/workflows/documentation.yml
  Bump doctrine/.github/.github/workflows/composer-lint.yml
  Bump ramsey/composer-install from 3 to 4
  Add cursor pagination
2026-04-09 10:20:54 +02:00
Alexander M. Turek 8ceb0b0d04 Merge pull request #12424 from doctrine/3.6.x-merge-up-into-3.7.x_xA84VK5A
Merge release 3.6.3 into 3.7.x
2026-04-02 13:53:21 +02:00
Grégoire Paris e88cd591f0 Merge pull request #12423 from greg0ire/3.6.x
Merge 2.20.x up into 3.6.x
2026-04-02 08:53:27 +02:00
Grégoire Paris 88a8f75f62 Merge remote-tracking branch 'origin/2.20.x' into 3.6.x 2026-04-02 08:27:51 +02:00
Tom Roar Furunes 9fe8ce4bf7 Merge pull request #12373 from tomme87/12225-fix-hydration-issue
12225 Fix hydration issue when using indexBy, SQL filter, and inheritance mapping
2026-04-02 08:18:54 +02:00
Grégoire Paris a46ff16339 Merge pull request #12414 from ahmed-bhs/docs/enum-type-mapping
Add documentation for enumType mapping with PHP backed enums
2026-04-01 00:35:46 +02:00
Grégoire Paris 94e60e4318 Merge pull request #12419 from greg0ire/backport-12222
Backport #12222
2026-04-01 00:35:01 +02:00
Grégoire Paris f59cd4019a Add ORDER BY clause to SELECT query
The order of results is not guaranteed unless we do so, and the test can
fail in some cases:

	There was 1 failure:

	1) Doctrine\Tests\ORM\Functional\QueryTest::testToIterableWithMixedResultArbitraryJoinsScalars
	Failed asserting that two strings are equal.
	--- Expected
	+++ Actual
	@@ @@
	-'Doctrine 2'
	+'lala 2'

	/home/runner/work/orm/orm/tests/Tests/ORM/Functional/QueryTest.php:481
2026-03-29 10:33:06 +02:00
Grégoire Paris 81558a8b2a Merge pull request #12418 from doctrine/dependabot/github_actions/2.20.x/codecov/codecov-action-6
Bump codecov/codecov-action from 5 to 6
2026-03-29 10:10:19 +02:00
dependabot[bot] e431ee113d Bump codecov/codecov-action from 5 to 6
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-29 06:12:22 +00:00
Ahmed EBEN HASSINE de4ec208fd Mention PostgreSQL jsonb as a reason to favor json over simple_array 2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE 脳の流れ df014a74c9 Update docs/en/reference/basic-mapping.rst
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE 2f46d95028 Add nullable enums, default values, query usage and XML mapping to enum docs
Cover additional enumType behaviors: nullable columns, database default
values with enum cases, usage in DQL/QueryBuilder/findBy, and XML
mapping syntax with enum-type attribute.
2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE 3fda5629f6 Add documentation for enumType mapping with PHP backed enums
The enumType option on #[Column] was barely mentioned in the docs and
had no dedicated section. This adds a complete reference covering
single-value columns, collection types (json, simple_array), automatic
type inference, validation behavior, and platform compatibility.
2026-03-26 08:52:00 +01:00
Grégoire Paris 6b273234d6 Merge pull request #12407 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/composer-lint.yml-14.0.0
Bump doctrine/.github/.github/workflows/composer-lint.yml from 13.1.0 to 14.0.0
2026-03-26 08:08:34 +01:00
Grégoire Paris 580a95ce3f Merge pull request #12413 from greg0ire/group-doctrine-updates
Group Doctrine workflow updates together
2026-03-26 06:53:01 +01:00
Grégoire Paris 8b91e248eb Group Doctrine workflow updates together
We do very small releases, often touching only one workflow, which means
it is unlikely to have several issues when attempting to bump several
references to this repository.
2026-03-25 20:48:20 +01:00
Grégoire Paris d2266c7d0c Merge pull request #12408 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/documentation.yml-14.0.0
Bump doctrine/.github/.github/workflows/documentation.yml from 13.1.0 to 14.0.0
2026-03-22 13:53:32 +00:00
Grégoire Paris eb0485869a Merge pull request #12406 from doctrine/dependabot/github_actions/2.20.x/ramsey/composer-install-4
Bump ramsey/composer-install from 3 to 4
2026-03-22 13:52:17 +00:00
Grégoire Paris 8372d600c6 Merge pull request #12409 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/coding-standards.yml-14.0.0
Bump doctrine/.github/.github/workflows/coding-standards.yml from 13.1.0 to 14.0.0
2026-03-22 08:41:51 +00:00
Grégoire Paris dde1d71b34 Merge pull request #12410 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/release-on-milestone-closed.yml-14.0.0
Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml from 13.1.0 to 14.0.0
2026-03-22 08:38:30 +00:00
dependabot[bot] 0d03255061 Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:28 +00:00
dependabot[bot] 75a18090d9 Bump doctrine/.github/.github/workflows/coding-standards.yml
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:27 +00:00
dependabot[bot] 77ffd2ab68 Bump doctrine/.github/.github/workflows/documentation.yml
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:25 +00:00
dependabot[bot] 401cc06d71 Bump doctrine/.github/.github/workflows/composer-lint.yml
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:22 +00:00
dependabot[bot] 5029b193ee Bump ramsey/composer-install from 3 to 4
Bumps [ramsey/composer-install](https://github.com/ramsey/composer-install) from 3 to 4.
- [Release notes](https://github.com/ramsey/composer-install/releases)
- [Commits](https://github.com/ramsey/composer-install/compare/v3...v4)

---
updated-dependencies:
- dependency-name: ramsey/composer-install
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:19 +00:00
Grégoire Paris b87781f65e Merge pull request #12364 from seb-jean/cursor-pagination
Add cursor-based pagination
2026-03-15 14:20:20 +00:00
seb-jean c0ff86ef69 Add cursor pagination 2026-03-13 12:04:50 +01:00
Alexander M. Turek 79bff0f072 Merge branch '3.7.x' into 4.0.x
* 3.7.x:
  Make the data provider static
  Raise proper exception for invalid arguments in Base::add() (#12394)
2026-03-12 13:27:37 +01:00
Alexander M. Turek 77b579287c Merge branch '3.6.x' into 3.7.x
* 3.6.x:
  Make the data provider static
  Raise proper exception for invalid arguments in Base::add() (#12394)
2026-03-12 13:26:47 +01:00
Alexander M. Turek 27c33cf88d Merge branch '2.20.x' into 3.6.x
* 2.20.x:
  Make the data provider static
  Raise proper exception for invalid arguments in Base::add() (#12394)
2026-03-12 09:31:56 +01:00
Alexander M. Turek 6068b61a0d Make the data provider static 2026-03-12 09:24:03 +01:00
Alexander M. Turek 00024f7d88 Raise proper exception for invalid arguments in Base::add() (#12394) 2026-03-12 09:05:27 +01:00
Alexander M. Turek 44c92377a0 Merge branch '3.7.x' into 4.0.x
* 3.7.x:
  Fix code style (#12395)
  Bump actions/upload-artifact from 6 to 7 (#12387)
  Bump actions/download-artifact from 7 to 8 (#12386)
2026-03-11 16:55:56 +01:00
Alexander M. Turek 255612a1ff Merge branch '3.6.x' into 3.7.x
* 3.6.x:
  Fix code style (#12395)
  Bump actions/upload-artifact from 6 to 7 (#12387)
  Bump actions/download-artifact from 7 to 8 (#12386)
2026-03-11 16:55:34 +01:00
Alexander M. Turek 331f8b52cb Merge branch '2.20.x' into 3.6.x
* 2.20.x:
  Fix code style (#12395)
  Bump actions/upload-artifact from 6 to 7 (#12387)
  Bump actions/download-artifact from 7 to 8 (#12386)
2026-03-11 16:55:13 +01:00
Alexander M. Turek b2faba62b7 Fix code style (#12395) 2026-03-11 16:51:15 +01:00
dependabot[bot] da426a0036 Bump actions/upload-artifact from 6 to 7 (#12387)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  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>
2026-03-05 00:34:23 +01:00
dependabot[bot] 1891a76f13 Bump actions/download-artifact from 7 to 8 (#12386)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  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>
2026-03-05 00:33:50 +01:00
Grégoire Paris 6bb30e1cee Merge pull request #12371 from greg0ire/drop-coll-2
Drop support for Collections 2
2026-02-04 07:54:07 +01:00
Grégoire Paris 6336eb5d5d Drop support for Collections 2 2026-02-03 08:27:22 +01:00
Grégoire Paris b8f404abcf Merge pull request #12369 from greg0ire/4.0.x
Merge 3.7.x up into 4.0.x
2026-02-03 07:58:01 +01:00
Grégoire Paris 3e8490a792 Merge remote-tracking branch 'origin/3.7.x' into 4.0.x 2026-02-02 20:34:35 +01:00
Grégoire Paris 14bb034fe4 Merge pull request #12341 from greg0ire/compat-coll3
Implement compatibility with collections 3
2026-02-02 18:29:56 +01:00
Grégoire Paris afc0aab61a Implement compatiblity with collections 3 2026-02-01 23:19:23 +01:00
Grégoire Paris 3e784c1d9a Merge pull request #12361 from greg0ire/breaking-evmi
Leverage EventManagerInterface
2026-01-31 11:00:10 +01:00
Grégoire Paris e1d7a13a5e Merge pull request #12368 from doctrine/3.6.x-merge-up-into-3.7.x_4EGww1uJ
Merge release 3.6.2 into 3.7.x
2026-01-30 22:55:20 +01:00
vvaswani 4262eb495b fix: update index to be serialized in __sleep() (#12366)
Signed-off-by: Vikram Vaswani <2571660+vvaswani@users.noreply.github.com`>
2026-01-30 22:41:41 +01:00
Grégoire Paris ca49533d3e Leverage EventManagerInterface
Not having this constraint means consumers can use composition instead of
inheritance.
2026-01-30 08:34:51 +01:00
Grégoire Paris 8275471110 Merge pull request #12367 from greg0ire/4.0.x
Merge 3.7.x up into 4.0.x
2026-01-30 08:33:48 +01:00
Grégoire Paris afd683bdb8 Merge remote-tracking branch 'origin/3.7.x' into 4.0.x 2026-01-30 07:53:25 +01:00
Grégoire Paris fe6e5a67f8 Merge pull request #12362 from greg0ire/leverage-evm-interface
Leverage event manager interfaces
2026-01-30 07:51:38 +01:00
Grégoire Paris b20a66dcdd Leverage event manager interfaces
Note that this involves dropping support for doctrine/event-manager 1.x,
and given that v2 only requires PHP 8.1, I think that is fine.
2026-01-29 22:37:51 +01:00
Grégoire Paris 0db02df3aa Merge pull request #12360 from doctrine/3.7.x
Merge 3.7.x up into 4.0.x
2026-01-27 21:20:51 +01:00
Grégoire Paris dc46af27ed Merge pull request #12358 from doctrine/3.6.x
Merge 3.6.x up into 3.7.x
2026-01-26 22:40:24 +01:00
Grégoire Paris 05ab22710b Merge pull request #12349 from greg0ire/remove-has-listeners-call
Remove unnecessary check
2026-01-26 09:03:10 +01:00
Grégoire Paris d3b47d2cbb Merge pull request #12355 from doctrine/2.20.x
Merge 2.20.x up into 3.6.x
2026-01-25 12:48:28 +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 6af7de38e1 Remove unnecessary check
EventManager::dispatchEvent() already performs a similar check. So does
EventManager::getListeners()
2026-01-17 13:57:15 +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 0780b5a6b1 Merge pull request #12348 from greg0ire/4.0.x
Merge 3.7.x up into 4.0.x
2026-01-16 18:51:07 +01:00
Grégoire Paris 71b1831351 Merge remote-tracking branch 'origin/3.7.x' into 4.0.x 2026-01-16 18:30:15 +01:00
Grégoire Paris 63d9a898ec Merge pull request #12347 from doctrine/3.6.x
Merge 3.6.x up into 3.7.x
2026-01-16 18:28:50 +01:00
Grégoire Paris 0bd839a720 Merge pull request #12345 from greg0ire/3.6.x
Merge 2.20.x up into 3.6.x
2026-01-16 18:27:03 +01:00
Grégoire Paris b65004fc26 Merge remote-tracking branch 'origin/2.20.x' into 3.6.x 2026-01-16 18:24:38 +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 7376adede8 Merge pull request #12342 from greg0ire/override
Add Override attribute where applicable
2026-01-13 11:54:57 +01:00
sasezaki ab156a551c Update phpstan-dbal2 to phpstan-dbal3 in .gitattributes (#12343) 2026-01-12 12:53:04 +01:00
Grégoire Paris 59f7f6793d Add Override attribute where applicable 2026-01-11 21:30:13 +01:00
Grégoire Paris 0fc9208d71 Merge pull request #12340 from greg0ire/add-missing-td
Add missing return type declaration
2026-01-10 23:18:33 +01:00
Grégoire Paris fd9e572424 Add missing return type declaration
The class is final, so this is backward-compatible.
2026-01-10 13:01:03 +01:00
Grégoire Paris 76490f2c99 Merge pull request #12338 from doctrine/3.6.x-merge-up-into-3.7.x_8wGpvJ0m
Merge release 3.6.1 into 3.7.x
2026-01-09 10:09:17 +01:00
Grégoire Paris 2148940290 Merge pull request #12335 from greg0ire/gh-12166
Avoid lazy object initialization when initializing read-only property
2026-01-09 06:28:15 +01:00
Grégoire Paris d3538095fd Merge pull request #12337 from greg0ire/3.6.x
Merge 2.20.x up into 3.6.x
2026-01-09 06:26:06 +01:00
Grégoire Paris 0c1bf14729 Merge remote-tracking branch 'origin/2.20.x' into 3.6.x 2026-01-08 08:55:21 +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 e923bbc932 Avoid lazy object initialization when initializing read-only property
Initializing e.g. a readonly ID does not require loading any data from
the database. However, calling isInitialized() on the reflection of a
readonly property triggers the native lazy object initialization.
If we have a lazy property at hand, then the property cannot be initialized
already, so it is safe to skip the call.
2026-01-07 13:04:14 +01:00
n0099 8cbd34c666 Merge pull request #12060 from n0099/patch-1
Update doc `dql-custom-walkers.rst` with an output walker to interpolate parameters into SQL
2026-01-01 18:27:48 +01:00
Carlos Fernandes f8bbdc40b0 Add dispatchPreFlushEvent() method and avoid calling getConnection() twice 2025-12-30 18:35:45 +01:00
Vladislav Sultanov 8bdefef6d1 Handle int-backed enums for values stored as string values in MySQL ENUM columns (#12275)
* FIX: Handle int-backed enums for values stored as string values in MySQL ENUM columns

Related issue: doctrine#12274

* FIX: Apply coding standard changes

Related issue: doctrine#12274

* FIX: Add unit test cases

Related issue: doctrine#12274
2025-12-30 18:21:10 +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 62477b5d42 Update branch metadata (#12327)
* Update branch metadata

3.6.0 has been released. As a consequence:

- 3.7.x is the next minor branch;
- 3.6.x is the current branch;
- 3.5.x is no longer maintained.

* doc: drop old releases

We should reduce the number of versions we have, so let's remove docs
for versions that have less than 2k downloads per day.
2025-12-21 00:29:57 +01:00
HypeMC 12116aa3c2 Fix docs regarding query hints (#12328) 2025-12-21 00:29:08 +01:00
Grégoire Paris 0aeddd0592 Fix grammatical errors 2025-12-20 15:34:16 +01:00
Grégoire Paris 2491c4b20d Merge pull request #12329 from HypeMC/fix-result-cache-examples
Fix result cache examples in docs
2025-12-20 09:14:39 +01:00
HypeMC 08d6167243 Fix result cache examples in docs 2025-12-20 01:21:11 +01:00
Grégoire Paris 765a9c43e6 Merge pull request #12326 from doctrine/3.6.x-merge-up-into-4.0.x_pKt05XGT
Merge release 3.6.0 into 4.0.x
2025-12-19 22:03:19 +01:00
Grégoire Paris d4e9276e79 Merge pull request #12325 from doctrine/3.5.x
Merge 3.5.x up into 3.6.x
2025-12-19 21:36:14 +01:00
Grégoire Paris cee74faa97 Merge remote-tracking branch 'origin/2.20.x' into 3.5.x 2025-12-19 20:59:05 +01:00
Grégoire Paris 9ae2181185 Merge pull request #12165 from HypeMC/debug-events-commands
Add commands for inspecting configured listeners
2025-12-19 08:31:44 +01:00
HypeMC 3e25efd72b One table 2025-12-17 23:17:36 +01:00
HypeMC 47496ed882 Fixes 2025-12-17 18:52:17 +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
Grégoire Paris 609e616f2d Merge pull request #12279 from greg0ire/deprecate-conversion
Deprecate string default expressions
2025-12-10 15:05:54 +01:00
Grégoire Paris 4016d6ba4b Deprecate string default expressions
Right now, the ORM handles the conversion of strings that happen to be
default expressions for date, time and datetime columns into the
corresponding value objects.

Let us allow users to specify these value objects directly, and
deprecate relying on the aforementioned conversion.
2025-12-10 12:08:30 +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 fcd9dede2e Merge branch '3.6.x' into 4.0.x
* 3.6.x:
  Remove obsolete PHPStan ignore rules
  Remove obsolete VarExporter feature detection (#12309)
  Allow Symfony 8 (#12308)
  Explicitly set a cache in testDisablingXmlValidationIsPossible (#12307)
  Removes Guides from our dependencies (#12303)
  Fix PHPStan and test errors after DBAL 4.4 and Symfony 7.4 releases (#12301)
  Support Symfony Console 8 (#12300)
  Bump doctrine/.github/.github/workflows/composer-lint.yml (#12288)
  Bump doctrine/.github/.github/workflows/documentation.yml (#12289)
  Bump doctrine/.github/.github/workflows/coding-standards.yml (#12290)
  Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12291)
  Bump actions/checkout from 5 to 6 (#12292)
  Add ORDER BY clause to more test cases
  Fix check for composite foreign key
  Fix documentation about default values
  Fix eager fetch composite foreign key (#11397)
2025-11-30 01:12:51 +01:00
Alexis Lefebvre 7c347b85c1 doc: do not mention InverseJoinColumn since it’s only with PHP 8.0 (#12313) 2025-11-30 00:49:09 +01:00
Alexander M. Turek 458b040d93 Remove obsolete PHPStan ignore rules 2025-11-30 00:34:10 +01:00
Alexis Lefebvre 262c396639 doc: update minimal version of PHP (#12312) 2025-11-30 00:31:21 +01:00
Alexander M. Turek 396636a2c2 Merge branch '3.5.x' into 3.6.x
* 3.5.x:
  Remove obsolete VarExporter feature detection (#12309)
  Allow Symfony 8 (#12308)
  Explicitly set a cache in testDisablingXmlValidationIsPossible (#12307)
  Removes Guides from our dependencies (#12303)
  Fix PHPStan and test errors after DBAL 4.4 and Symfony 7.4 releases (#12301)
  Support Symfony Console 8 (#12300)
  Bump doctrine/.github/.github/workflows/composer-lint.yml (#12288)
  Bump doctrine/.github/.github/workflows/documentation.yml (#12289)
  Bump doctrine/.github/.github/workflows/coding-standards.yml (#12290)
  Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12291)
  Bump actions/checkout from 5 to 6 (#12292)
2025-11-30 00:29:59 +01:00
Alexander M. Turek 78dd074266 Remove obsolete VarExporter feature detection (#12309) 2025-11-30 00:11:02 +01:00
Alexander M. Turek ff22a00fcf Allow Symfony 8 (#12308) 2025-11-30 00:10:09 +01:00
Alexander M. Turek 02e8ff9663 Explicitly set a cache in testDisablingXmlValidationIsPossible (#12307) 2025-11-29 23:16:54 +01:00
Alexis Lefebvre 01fd55e9ea chore: show parameters in name of CI jobs 2025-11-29 23:12:27 +01:00
Alexander M. Turek 2e75a7f1c1 Merge branch '2.20.x' into 3.5.x
* 2.20.x:
  Support Symfony Console 8 (#12300)
  Bump doctrine/.github/.github/workflows/composer-lint.yml (#12288)
  Bump doctrine/.github/.github/workflows/documentation.yml (#12289)
  Bump doctrine/.github/.github/workflows/coding-standards.yml (#12290)
  Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12291)
  Bump actions/checkout from 5 to 6 (#12292)
2025-11-29 22:07:13 +01:00
Alexander M. Turek 152b0e3d65 Removes Guides from our dependencies (#12303) 2025-11-29 21:55:58 +01:00
Alexander M. Turek 9d11fdd3da Fix PHPStan and test errors after DBAL 4.4 and Symfony 7.4 releases (#12301)
* Fix PHPStan errors after DBAL 4.4 and Symfony 7.4 releases

* Fix PHPStan and test errors after DBAL 4.4 and Symfony 7.4 releases
2025-11-29 18:04:54 +01:00
Alexander M. Turek 87f1ba74e0 Support Symfony Console 8 (#12300) 2025-11-29 15:03:56 +01:00
Grégoire Paris f357a33d23 Merge pull request #12293 from doctrine/3.5.x
Merge 3.5.x. up into 3.6.x
2025-11-24 20:25:07 +01:00
Grégoire Paris ee70178314 Merge pull request #12287 from greg0ire/more-order-by
Add ORDER BY clause to more test cases
2025-11-24 09:40:48 +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 01c178b297 Add ORDER BY clause to more test cases
In https://github.com/doctrine/orm/pull/12222, I thought wrongly thought
the issue I was fixing only affected one test.
2025-11-23 19:18:20 +01:00
Grégoire Paris ffa50a777f Merge pull request #12286 from greg0ire/3.5.x
Merge 2.20.x up into 3.5.x
2025-11-21 20:24:51 +01:00
Grégoire Paris 649048f745 Merge remote-tracking branch 'origin/2.20.x' into 3.5.x 2025-11-21 19:46:45 +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 6982c8ab9d Merge pull request #12284 from doctrine/3.5.x
Merge 3.5.x up into 3.6.x
2025-11-20 22:49:20 +01:00
Grégoire Paris e767d2dbeb Merge pull request #12280 from greg0ire/remove-default
Remove FieldMapping::$default
2025-11-20 21:33:15 +01:00
Grégoire Paris 3df11d518c Merge pull request #12283 from doctrine/2.20.x
Merge 2.20.x up into 3.5.x
2025-11-20 21:31:19 +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
Grégoire Paris d65b6a6a10 Remove FieldMapping::$default
It has been deprecated in favor of $options['default'].
2025-11-19 08:08:54 +01:00
Grégoire Paris 3d8afbb7b2 Merge pull request #12278 from greg0ire/4.0.x
Merge 3.6.x up into 4.0.x
2025-11-19 08:05:56 +01:00
Grégoire Paris 736f6bc97f Merge remote-tracking branch 'origin/3.6.x' into 4.0.x 2025-11-19 07:57:42 +01:00
Grégoire Paris 608705427e Merge pull request #12277 from greg0ire/3.5.x
Merge 2.20.x up into 3.5.x
2025-11-19 07:13:23 +01:00
Grégoire Paris f0562f4120 Merge pull request #12273 from greg0ire/deprecate-default
Deprecate FieldMapping::$default
2025-11-19 06:50:43 +01:00
Grégoire Paris 9f19310f27 Merge remote-tracking branch 'origin/2.20.x' into 3.5.x 2025-11-19 00:01:30 +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 62f2cff218 Merge pull request #12268 from pmaasz/querybuilder-hints
Add hints to QueryBuilder
2025-11-15 09:32:24 +01:00
pmaasz cdd774906b add member variable hints to the querybuilder for hints to be added to the query
This adds the membervariable hints to the QueryBuilder to enable setting hints
that will be applied to the query when it is created. This can help trigger
custom walker classes when the query is not adressable driectly e.g. in
Symfony Form Extensions where the quer_builder normalizer is handed the querybuilder
directly. Also see #11849
The feature mirrors the hint feature from the Query class.
This also adds tests for the hints in the QueryBuilder to ensure that those are added
correctly and applied to the query itself on creation
2025-11-14 09:11:48 +01:00
Grégoire Paris 96776e091d Deprecate FieldMapping::$default
Its purpose is unclear since there is FieldMapping::$options['default']
already.
2025-11-14 08:46:08 +01:00
Grégoire Paris f7470d8a3f Merge pull request #12271 from greg0ire/3.6.x
Merge 3.5.x up into 3.6.x
2025-11-11 19:31:00 +01:00
Grégoire Paris 2c41cc7f1c Merge remote-tracking branch 'origin/3.5.x' into 3.6.x 2025-11-11 19:28:47 +01:00
Grégoire Paris a6c1e63a60 Merge pull request #12266 from doctrine/3.5.x-merge-up-into-3.6.x_hSSiOXm0
Merge release 3.5.6 into 3.6.x
2025-11-10 22:27:19 +01:00
Grégoire Paris 6881cdff4c Merge pull request #12264 from doctrine/3.5.x-merge-up-into-3.6.x_9GolPzTd
Merge release 3.5.5 into 3.6.x
2025-11-10 21:27:36 +01:00
Grégoire Paris 0ed46303cc Merge pull request #12253 from greg0ire/4.0.x
Merge 3.6.x up into 4.0.x
2025-10-29 21:07:20 +01:00
Grégoire Paris 6e351b699c Merge remote-tracking branch 'origin/3.6.x' into 4.0.x 2025-10-29 20:51:40 +01:00
Grégoire Paris 9e5442a892 Merge pull request #12251 from doctrine/3.5.x
Merge 3.5.x up into 3.6.x
2025-10-29 20:41:29 +01:00
Grégoire Paris 01774c035c Merge pull request #12065 from whataboutpereira/fix-enum-discriminator-column
Use enum values from enumType in DiscriminatorColumn and check DiscriminatorMap values against it
2025-10-29 07:28:16 +01:00
Reio Remma 6f83166266 Extract enum cases from enumType in DiscriminatorColumn
Check DiscriminatorMap keys match enum cases.
Test values are populated from enum cases and mismatched values throw an exception.
Fixes #11794
2025-10-28 20:36:11 +02:00
Alexander M. Turek 758de75b45 Fix docs build 2025-10-08 11:18:04 +02:00
Alexander M. Turek 1ff63be779 Fix low-deps tests 2025-10-08 11:11:20 +02:00
Alexander M. Turek 2d5d129ee1 Merge branch '3.6.x' into 4.0.x
* 3.6.x:
  Fix missing import
  Remove calls to getMockForAbstractClass() (#12003)
  Add a CI job that fails on deprecations (#12188)
  use the empty string instead of null as an array offset (#12181)
  Upgrade to doctrine/coding-standard 14
  Bump doctrine/.github from 7.3.0 to 8.0.0
  do not call setAccessible() on PHP >= 8.1 (#12182)
  Fix docs on final entities (#12176)
  Remove Database and Model First chapters that said little of value.
  Switch to IgnoreDeprecations
  Fix php doc for getPropertyAccessors method (#12159)
  docs: consistent PostgreSQL's name case
  docs: generation strategies differences between DBAL 3 and 4
  Check extra condition to decide if a test was skipped
  Use PHPUnit 11 when possible
  Migrate away from annotations in tests
  Migrate away from assertStringNotMatchesFormat()
  Migrate to willReturn()
  Migrate away from getMockForAbstractClass()
  Fix `IN`/`NOT IN` expression handling and support enums when matching on to-many-collections
2025-10-08 10:50:52 +02:00
HypeMC cb8a76ba3a Add commands for inspecting configured listeners 2025-09-15 15:03:52 +02:00
Grégoire Paris cdc52b2a9e Merge pull request #12154 from greg0ire/4.0.x
Merge 3.6.x up into 4.0.x
2025-08-29 00:45:24 +02:00
Grégoire Paris c5e2f3fb23 Merge remote-tracking branch 'origin/3.6.x' into 4.0.x 2025-08-28 08:48:00 +02:00
Grégoire Paris c91c3a3b1d Merge pull request #12145 from greg0ire/4.0.x
Merge 3.6.x up into 4.0.x
2025-08-19 22:32:39 +02:00
Grégoire Paris 42a693c0d3 Merge remote-tracking branch 'origin/3.6.x' into 4.0.x 2025-08-19 22:18:28 +02:00
Grégoire Paris 03cc07e4d7 Merge pull request #12137 from greg0ire/forbid-nullable-on-primary-key
Forbid nullable on columns that are part of a primary key
2025-08-19 07:36:41 +02:00
Grégoire Paris 78fee8ea5c Forbid nullable on columns that are part of a primary key
This behavior has been deprecated.
2025-08-17 21:33:40 +02:00
Grégoire Paris 0c73cf93fa Merge pull request #12136 from doctrine/3.6.x
Merge 3.6.x up into 4.0.x
2025-08-17 21:24:30 +02:00
Grégoire Paris c03ed691d4 Merge pull request #12132 from doctrine/3.6.x
Merge 3.6.x up into 4.0.x
2025-08-17 19:10:21 +02:00
Alexander M. Turek b521e89b20 Merge branch '3.6.x' into 4.0.x
* 3.6.x:
  Don't partially mock the AbstractPlatform class (#12114)
2025-08-06 19:01:53 +02:00
Alexander M. Turek a6a94cdefd Merge branch '3.6.x' into 4.0.x
* 3.6.x:
  Include stability in coverage file key (#12112)
  Allow Symfony 8 (#12110)
  Run tests with Symfony 8 (#12102)
  Improve comment
  Convert test into 2 unit tests
  Quote parts of the table name
2025-08-06 15:09:04 +02:00
Alexander M. Turek c5e5742744 Merge branch '3.6.x' into 4.0.x
* 3.6.x:
  Move LazyGhost deprecation to ProxyFactory (#12101)
  Address deprecations from doctrine/dbal (#12098)
  Remove if statement
2025-08-05 00:25:15 +02:00
Grégoire Paris 8b3a6dfbbb Merge pull request #12089 from doctrine/drop-useless-bool
Drop useless parameter
2025-07-30 17:17:08 +02:00
Gregoire PARIS 5634c3ccee Drop useless parameter
It was here to improve DX on 3.x
2025-07-30 16:56:05 +02:00
Grégoire Paris a93d7d22d8 Merge pull request #12092 from greg0ire/4.0.x
Merge 3.6.x up into 4.0.x
2025-07-30 16:55:10 +02:00
Gregoire PARIS 677e0a4ed3 Merge remote-tracking branch 'origin/3.6.x' into 4.0.x 2025-07-30 16:40:28 +02:00
Grégoire Paris 224ff9710d Merge pull request #12050 from greg0ire/no-legacy-refl-fields
Remove ClassMetadata::$reflFields
2025-07-04 07:51:21 +02:00
Grégoire Paris 68c71521e7 Remove ClassMetadata::$reflFields
It has been deprecated in favor of property accessors.
2025-07-01 20:36:27 +02:00
Grégoire Paris a2ea72c763 Merge pull request #12049 from greg0ire/4.0.x
Merge 3.5.x up into 4.0.x
2025-07-01 20:06:20 +02:00
Grégoire Paris 1492b01093 Merge remote-tracking branch 'origin/3.5.x' into 4.0.x 2025-07-01 19:53:49 +02:00
Grégoire Paris 68b797161e Merge pull request #12029 from greg0ire/remove-userland-proxies
Remove the possibility to use userland proxies
2025-06-28 15:26:06 +02:00
Grégoire Paris e17b31ee68 Remove the possibility to use userland proxies
Since we bumped the minimum requirement to PHP 8.4, there no longer is a
reason to support userland proxies when we have native lazy objects at our
disposal.
2025-06-28 11:34:39 +02:00
Grégoire Paris ac87e6e7dc Merge pull request #12038 from doctrine/3.5.x
Merge 3.5.x up into 4.0.x
2025-06-28 11:22:57 +02:00
Grégoire Paris 016d9d2074 Merge pull request #12035 from doctrine/3.5.x
Merge 3.5.x up into 4.0.x
2025-06-27 18:46:41 +02:00
Grégoire Paris a96697e436 Merge pull request #12028 from doctrine/3.5.x
Merge 3.5.x up into 4.0.x
2025-06-27 08:43:31 +02:00
Grégoire Paris b1ec1da8e6 Merge pull request #12027 from greg0ire/4.0.x
Merge 3.4.x up into 4.0.x
2025-06-26 21:25:54 +02:00
Grégoire Paris 6cfe0883f0 Merge remote-tracking branch 'origin/3.5.x' into 4.0.x 2025-06-26 21:12:00 +02:00
Grégoire Paris cd56fe54bb Merge pull request #12016 from greg0ire/4.0.x
Merge 3.5.x up into 4.0.x
2025-06-24 08:15:07 +02:00
Grégoire Paris 982ef7ac56 Merge remote-tracking branch 'origin/3.5.x' into 4.0.x 2025-06-22 23:55:47 +02:00
Grégoire Paris bc904f6a0c Merge pull request #11999 from greg0ire/drop-checks
Drop checks on PHP_VERSION_ID
2025-06-17 23:08:16 +02:00
Grégoire Paris cddf985a7d Drop checks on PHP_VERSION_ID
We are requiring PHP 8.4, so all these checks are useless now.
2025-06-17 22:41:01 +02:00
Grégoire Paris 2e9ca50007 Merge pull request #11998 from greg0ire/require-php-84
Require PHP 8.4
2025-06-17 08:21:02 +02:00
Grégoire Paris 275975a444 Require PHP 8.4
It will allow us to use only native lazy objects and drop other
implementations.

I'm switching the codebase to php 8.4 style, which results in constants
being typed.
2025-06-16 23:49:26 +02:00
Grégoire Paris 2088a469cb Merge pull request #11996 from greg0ire/remove-depr-methods
Remove deprecated configuration methods
2025-06-16 23:48:18 +02:00
Gregoire PARIS 3f13f119e2 Remove deprecated configuration methods
They are no-ops or throw exceptions.
2025-06-16 09:53:02 +02:00
Grégoire Paris d444a79bb0 Merge pull request #11995 from greg0ire/4.0.x
Merge 3.5.x up into 4.0.x
2025-06-16 09:30:14 +02:00
Grégoire Paris 01dae555bf Merge remote-tracking branch 'origin/3.5.x' into 4.0.x 2025-06-16 09:21:03 +02:00
Grégoire Paris d9049d8ef5 Merge pull request #11994 from greg0ire/4.0.x
Merge 3.4.x up into 4.0.x
2025-06-16 09:19:31 +02:00
Grégoire Paris 1ad46527b6 Merge remote-tracking branch 'origin/3.4.x' into 4.0.x 2025-06-16 09:06:01 +02:00
Grégoire Paris a4b794db4f Merge pull request #11986 from doctrine/3.4.x-merge-up-into-4.0.x_b6XaoLOe
Merge release 3.4.0 into 4.0.x
2025-06-14 14:09:44 +02:00
Grégoire Paris 7eeea7d66c Merge pull request #11967 from eltharin/add_arg_on_function
remove func_get_args for have "normals" arguments in function
2025-06-09 08:03:22 +02:00
Grégoire Paris f96e812438 remove trailing whitespace 2025-06-08 11:21:45 +02:00
eltharin d1b4aa013a remove func_get_args for have "normals" arguments in function
BC break for next major version
2025-06-08 11:21:40 +02:00
Grégoire Paris dda313871d Merge pull request #11974 from doctrine/3.4.x
Merge 3.4.x up into 4.0.x
2025-06-07 21:17:45 +02:00
Grégoire Paris f7584df83c Merge pull request #11972 from doctrine/3.4.x
Merge 3.4.x up into 4.0.x
2025-06-06 13:29:22 +02:00
Grégoire Paris 28b2591694 Merge pull request #11947 from doctrine/3.4.x
Merge 3.4.x up into 4.0.x
2025-05-25 18:52:09 +02:00
Grégoire Paris 615566507c Merge pull request #11928 from greg0ire/4.0.x
Merge 3.4.x up into 4.0.x
2025-05-02 21:12:06 +02:00
Grégoire Paris 9b1cb60cbc Merge remote-tracking branch 'origin/3.4.x' into 4.0.x 2025-05-02 21:02:18 +02:00
Grégoire Paris 1d0e365401 Merge pull request #11841 from greg0ire/4.0.x
Merge 3.4.x up into 4.0.x
2025-02-18 23:33:10 +01:00
Grégoire Paris c12daf5206 Merge remote-tracking branch 'origin/3.4.x' into 4.0.x 2025-02-18 23:14:53 +01:00
Grégoire Paris f7030d1844 Merge pull request #11762 from greg0ire/4.0.x
Merge 3.4.x up into 4.0.x
2024-12-08 14:05:23 +01:00
Grégoire Paris 8d7b4d8d17 Merge remote-tracking branch 'origin/3.4.x' into 4.0.x 2024-12-08 13:48:20 +01:00
Grégoire Paris f53d35b74a Merge pull request #11730 from greg0ire/output-walker
Remove legacy implementation of the DQL -> SQL transformation
2024-12-08 11:08:01 +01:00
Grégoire Paris 350597beb0 Drop support for Persistence 3 (#11729) 2024-11-24 17:24:03 +01:00
Grégoire Paris 8dc17b2851 Remove legacy implementation of the DQL -> SQL transformation 2024-11-24 15:57:52 +01:00
Grégoire Paris ee9dd474de Merge pull request #11728 from greg0ire/4.0.x
Merge 3.4.x up into 4.0.x
2024-11-23 23:36:08 +01:00
Grégoire Paris 0a4a11b6e4 Merge remote-tracking branch 'origin/3.4.x' into 4.0.x 2024-11-23 22:38:29 +01:00
Grégoire Paris a7406cc26d Merge remote-tracking branch 'origin/3.4.x' into 4.0.x 2024-11-23 20:54:35 +01:00
Grégoire Paris f11adcb622 Merge pull request #11689 from doctrine/3.4.x
Merge 3.4.x up into 4.0.x
2024-10-17 07:41:50 +02:00
Alexander M. Turek 1e4b88d456 Merge branch '3.4.x' into 4.0.x
* 3.4.x:
  Tell dependabot to target 2.20.x (#11681)
  Remove annotations example
  Bump doctrine/.github from 5.1.0 to 5.2.0 (#11680)
  Psalm 5.26.1 (#11677)
  CI: Close stale pull requests
  Update README (#11673)
  Fix PHPUnit deprecations
  Bump doctrine/.github from 5.1.0 to 5.2.0 (#11671)
  Experiment with literalinclude
2024-10-14 09:53:03 +02:00
Alexander M. Turek eb5dd3f34f Remove more DBAL 3 compat code (#11675) 2024-10-13 21:19:45 +02:00
Alexander M. Turek 25066f801c Merge branch '3.3.x' into 4.0.x
* 3.3.x:
  Drop DBAL 2 compat code (#11674)
  Update branch metadata (#11672)
2024-10-13 20:56:35 +02:00
Alexander M. Turek a0f5b4c9a3 Drop support for DBAL 4.1 (#11670) 2024-10-13 00:07:30 +02:00
Grégoire Paris 8b5148a7a8 Merge remote-tracking branch 'origin/3.3.x' into 4.0.x 2024-10-12 22:40:54 +02:00
Alexander M. Turek 22198f7c19 Merge branch '3.3.x' into 4.0.x
* 3.3.x:
  Remove vendor prefix of PHPDoc referencing class-string (#11643)
  Deprecate the `\Doctrine\ORM\Query\Parser::setCustomOutputTreeWalker()` method (#11641)
2024-10-09 22:54:08 +02:00
Alexander M. Turek 9cf5593759 Merge branch '3.3.x' into 4.0.x
* 3.3.x:
  Stop recommending vendor-prefixed PHPDoc (#11640)
  Let PHPStan detect deprecated usages (#11639)
  PHPStan 1.12.6 (#11635)
  Add upgrade note about property hooks (#11636)
  Prepare PHP 8.4 support: Prevent property hooks from being used (#11628)
  Use E_ALL instead of E_ALL | E_STRICT
  Add CI job for PHP 8.4
  fix generating duplicate method stubs
2024-10-09 21:54:51 +02:00
Grégoire Paris b5c0e0154f Remove int from union type
This case is no longer necessary now that we have a ParameterType enum.
2024-10-09 10:29:38 +02:00
Grégoire Paris dbf26dbf90 Merge remote-tracking branch 'origin/3.3.x' into 4.0.x 2024-10-09 10:14:02 +02:00
Grégoire Paris 9b09cd03c0 Merge pull request #11519 from greg0ire/remove-db-driver
Remove DatabaseDriver
2024-06-22 15:46:04 +02:00
Grégoire Paris 2c489fb8b3 Remove DatabaseDriver 2024-06-22 11:39:08 +02:00
Alexander M. Turek 42a36e19a5 Merge branch '3.3.x' into 4.0.x
* 3.3.x:
  Fix deprecated array access usage (#11517)
  Address doctrine/persistence 3.3.3 release
  Add the propoer void return type on the __load method of proxies
  Deprecate DatabaseDriver
  Remove unneeded CS rule
  Fix OneToManyPersister::deleteEntityCollection missing discriminator column/value. (GH-11500)
  Skip joined entity creation for empty relation (#10889)
  ci: maintained and stable mariadb version (11.4 current lts) (#11490)
  fix(docs): use string value in `addAttribute`
  Replace assertion with exception (#11489)
  Use ramsey/composer-install in PHPBench workflow
  update EntityManager#transactional to EntityManager#wrapInTransaction
  Fix cloning entities
  Consider usage of setFetchMode when checking for simultaneous usage of fetch-mode EAGER and WITH condition.
  Update branch metadata (#11474)
2024-06-21 14:01:17 +02:00
Grégoire Paris f46d8a1742 Merge pull request #11510 from greg0ire/missing-type-hint
Add missing type declaration
2024-06-20 14:35:40 +02:00
Grégoire Paris 1eb67b7ca1 Add missing type declaration 2024-06-19 22:02:03 +02:00
Grégoire Paris 00b29a13f2 Merge pull request #11473 from doctrine/3.2.x-merge-up-into-4.0.x_1iG5LEAr
Merge release 3.2.0 into 4.0.x
2024-05-23 16:42:47 +02:00
Alexander M. Turek a22e0fda8e Remove NotSupported (#11471) 2024-05-23 07:16:41 +02:00
Alexander M. Turek b58946a11f Merge branch '3.2.x' into 4.0.x
* 3.2.x:
  Deprecate the NotSupported exception (#11470)
2024-05-22 21:54:30 +02:00
Alexander M. Turek d973867dc0 Drop support for DBAL 3 (#11216) 2024-05-22 14:10:25 +02:00
Alexander M. Turek ac5a17da2e Remove Serializable implementation (#11469) 2024-05-22 11:50:28 +02:00
Alexander M. Turek f4bbf8e1d0 Merge branch '3.2.x' into 4.0.x
* 3.2.x:
  Deprecate SequenceGenerator implementing Serializable (#11468)
2024-05-22 10:51:35 +02:00
Alexander M. Turek 95a0000446 Merge branch '3.2.x' into 4.0.x
* 3.2.x:
  Psalm 5.24.0 (#11467)
  PHPStan 1.11.1 (#11466)
2024-05-21 14:25:44 +02:00
Alexander M. Turek b2a09b9c4e Merge branch '3.2.x' into 4.0.x
* 3.2.x:
  Fix failed merge (#11464)
  Test with actual lock modes (#11465)
  Backport test for Query::setLockMode() (#11463)
  Fix return type of Query::getLockMode() (#11462)
2024-05-21 14:05:02 +02:00
Alexander M. Turek 3695d5793d Merge branch '3.2.x' into 4.0.x
* 3.2.x:
  Using an integer as discriminator value with ORM v3
  Using an integer as discriminator value with ORM v3
  Bump ramsey/composer-install from 2 to 3 (#11442)
  Use ramsey/composer-install in PHPBench workflow (#11444)
  Bump doctrine/.github from 3.0.0 to 5.0.1
  Upgrade codecov/codecov-action
  Setup Dependabot
  Make test compatible with PHP 7.1
  Fix deprecation layer
  Remove unused test group
  Keep entities in identity map until the scheduled deletions are executed.
  Update association-mapping.rst
  fix: always cleanup in `AbstractHydrator::toIterable()` (#11101)
  Respect orderBy for EAGER fetch mode
  fix(docs): typo
2024-05-21 08:43:08 +02:00
Alexander M. Turek 9674168572 Merge branch '3.2.x' into 4.0.x
* 3.2.x:
  Revert "Merge pull request #11399 from ThomasLandauer/issue-11377" (#11415)
  Fix BIGINT validation (#11414)
  docs: update PHP version in doc
  Fix fromMappingArray definition
  Fix templated phpdoc return type (#11407)
  [Documentation] Merging "Query Result Formats" with "Hydration Modes"
  SchemaValidator: Changing mapping of BIGINT to string|int
  Fix psalm errors: remove override of template type
  Update dql-doctrine-query-language.rst
  Adding `NonUniqueResultException`
  [Documentation] Query Result Formats
2024-04-15 16:31:30 +02:00
Alexander M. Turek 80aec02fd7 Merge branch '3.2.x' into 4.0.x
* 3.2.x:
  Adjust PHPBench mocks
  Set column length explicitly (#11393)
  Add missing import
  Remove unused variable (#11391)
  Fixed proxy initialization for EnumReflectionProperty
  Remove older versions from the docs (#11383)
  [Documentation] Removing "Doctrine Mapping Types" ... (#11384)
  [GH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (#11380)
  Improve lazy ghost performance by avoiding self-referencing closure. (#11376)
  Remove outdated git metadata files (#11362)
  Switch join columns around, otherwise index doesnt match
  Key on fk
  Fix entities and mapping.
  Minor code style fix in AbstractRemoteControl
  Do not schedule batch loading for target classes with composite identifier.
  Cleanup tests not to use model sets.
  provides a test case for github issue 11154
2024-03-21 15:16:26 +01:00
Grégoire Paris 637cd6e405 Merge pull request #11353 from DaDeather/11351-remove-obsolete-indexes-and-unique-constraint-properties-from-table-attribute
Remove obsolete and unnecessary properties from `Table` attribute (#11351)
2024-03-18 09:12:14 +01:00
Ismail Özgün Turan 9430d56d78 Remove obsolete and unnecessary properties from Table attribute (#11351)
The properties `indexes` and `uniqueConstraints` were present before and
were used for the `AnnotationDriver` but were never implemented
for the `AttributeDriver`.
Since the `AnnotationDriver` doesn't exist anymore these can be safely
removed reducing possible confusion when defining indices and
uniqueConstraints in the `Table` attribute which never worked anyway.
2024-03-18 08:31:32 +01:00
Grégoire Paris a4e71b8df7 Merge pull request #11367 from doctrine/3.2.x
Merge 3.2.x up into 4.0.x
2024-03-18 08:13:06 +01:00
Grégoire Paris f20955901a Merge pull request #11356 from greg0ire/remove--complete
Remove --complete option of orm:schema-tool:update
2024-03-18 08:12:47 +01:00
Grégoire Paris 54c36d2a55 Remove --complete option of orm:schema-tool:update
That option achieved nothing anymore.
2024-03-15 08:47:52 +01:00
Grégoire Paris ea7cc64911 Merge pull request #11355 from greg0ire/4.0.x
Merge 3.2.x up into 4.0.x
2024-03-15 08:45:32 +01:00
Grégoire Paris 22947da46b Merge remote-tracking branch 'origin/3.2.x' into 4.0.x 2024-03-15 08:35:33 +01:00
Alexander M. Turek 2928b9331a Remove ReflectionEnumProperty (#11334) 2024-03-03 22:13:36 +01:00
Alexander M. Turek fdd84a2290 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Prepare releases 2.19 and 3.1 (#11335)
2024-03-03 18:46:03 +01:00
Alexander M. Turek 8c1ad6f0c5 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Fix annotation
  Bump CI workflows (#11336)
  Fix SchemaTool::getSchemaFromMetadata() uniqueConstraint without a predefined name (#11314)
2024-03-03 18:04:09 +01:00
Alexander M. Turek a5cb7c26c5 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Use class from persistence package  (#11330)
  Refator array_map into simple loop for performance. (#11332)
2024-03-03 14:33:43 +01:00
Alexander M. Turek f8b5ef03d5 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Psalm 5.22.2 (#11326)
2024-03-01 11:01:25 +01:00
Alexander M. Turek e80f03c5d8 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Bump Doctrine Collections to 2.2 (#11325)
  Use enum_exists() for enums
  Remove PHP 7 workarounds (#11324)
2024-03-01 09:28:26 +01:00
Alexander M. Turek c1d61802cd Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  PHPStan 1.10.59 (#11320)
  Address deprecations from Collection 2.2 (#11315)
  Deprecate invalid method call
  Throw a full-fledged exception on invalid call
  Remove extra word
  Fix sql walker phpdoc
2024-03-01 08:23:55 +01:00
Grégoire Paris ca8631c172 Merge pull request #11306 from greg0ire/cleanup-deprecations
Disallow passing null to ClassMetadata::fullyQualifiedClassName()
2024-02-28 20:43:58 +01:00
Grégoire Paris 518d16b9cb Disallow passing null to ClassMetadata::fullyQualifiedClassName() 2024-02-25 11:11:52 +01:00
Grégoire Paris 7679fc0f4f Merge pull request #11304 from doctrine/3.1.x
Merge 3.1.x up into 4.0.x
2024-02-25 10:56:49 +01:00
Alexander M. Turek c5ef72d060 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Backport QueryParameterTest (#11288)
  Test different ways of settings query parameters
  Be less restrictive in DiscriminatorColumnMapping phpdoc (#11226)
  Allow (Array)ParameterType in QueryBuilder
  Do not implicitly cast glob's return type
  Do not cast file_put_contents's return type
  Do not implicitly cast getLockTime()'s return type
  Do not implicitly cast getLockContent()'s return value
2024-02-22 13:53:02 +01:00
Alexander M. Turek cbab4d6a14 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Fix Static Analysis folder reference (#11281)
  Remove broken assertion from DateAddFunction and DateSubFunction (#11243)
  Account for inversedBy being a non-falsy-string or null
  Improve static analysis on AttachEntityListenersListener
  docs: recommend safer way to disable logging (#11269)
  Remove unused baseline entries
  Treat '0' as a legitimate trim char
  Add type field mapper documentation to the sidebar
  Mark document as orphan
  Use correction sectionauthor syntax
  Remove unused trait
  [Documentation] Adding link to Postgres upgrade article (#11257)
  Validate more variadic parameters (#11261)
  Throw if a variadic parameter contains unexpected named arguments (#11260)
  Make docs valid according to guides 0.3.3 (#11252)
  fix: support array-type arg in QB variadic calls (#11242)
2024-02-21 22:20:20 +01:00
Alexander M. Turek 5733eced42 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Remove references to deprecated constants from Lexer (#11234)
2024-02-07 15:43:44 +01:00
Alexander M. Turek 0917109c50 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Add TokenType class (#11228)
  Revert "Merge pull request #11229 from greg0ire/add-columns"
  Add columns for 3.1.x and 4.0x
  Update version ORM from 2 to 3 in docs (#11221)
  Clean up outdated sentence (#11224)
  Update README.md
  Point link to correct upgrade guide (#11220)
  Ignore subclasses without discriminatorValue when generating discriminator column condition SQL (#11200)
  Update branches in README
2024-02-07 14:21:45 +01:00
Alexander M. Turek 615eb926f4 Merge branch '3.1.x' into 4.0.x
* 3.1.x:
  Bump dependencies in the "getting started" docs page (#11219)
  DoctrineSetup was renamed to ORMSetup (#11218)
2024-02-04 17:45:54 +01:00
Grégoire Paris d734ab38fa Merge pull request #11215 from greg0ire/remove-array-access
Remove array access
2024-02-04 13:52:52 +01:00
Grégoire Paris 0446f5b4b5 Remove array access 2024-02-04 11:27:46 +01:00
477 changed files with 6663 additions and 5959 deletions
+6 -51
View File
@@ -12,42 +12,17 @@
"upcoming": true
},
{
"name": "3.6",
"branchName": "3.6.x",
"slug": "3.6",
"name": "3.7",
"branchName": "3.7.x",
"slug": "3.7",
"upcoming": true
},
{
"name": "3.5",
"branchName": "3.5.x",
"slug": "3.5",
"name": "3.6",
"branchName": "3.6.x",
"slug": "3.6",
"current": true
},
{
"name": "3.4",
"slug": "3.4",
"maintained": false
},
{
"name": "3.3",
"slug": "3.3",
"maintained": false
},
{
"name": "3.2",
"slug": "3.2",
"maintained": false
},
{
"name": "3.1",
"slug": "3.1",
"maintained": false
},
{
"name": "3.0",
"slug": "3.0",
"maintained": false
},
{
"name": "2.21",
"branchName": "2.21.x",
@@ -89,26 +64,6 @@
"name": "2.14",
"slug": "2.14",
"maintained": false
},
{
"name": "2.13",
"slug": "2.13",
"maintained": false
},
{
"name": "2.12",
"slug": "2.12",
"maintained": false
},
{
"name": "2.11",
"slug": "2.11",
"maintained": false
},
{
"name": "2.10",
"slug": "2.10",
"maintained": false
}
]
}
+1 -1
View File
@@ -15,6 +15,6 @@ phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
phpstan-dbal2.neon export-ignore
phpstan-dbal3.neon export-ignore
phpstan-params.neon export-ignore
phpstan-persistence2.neon export-ignore
+4
View File
@@ -7,3 +7,7 @@ updates:
labels:
- "CI"
target-branch: "2.20.x"
groups:
doctrine:
patterns:
- "doctrine/*"
+1 -1
View File
@@ -24,4 +24,4 @@ on:
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@v12.2.0"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@14.0.0"
+1 -1
View File
@@ -17,4 +17,4 @@ on:
jobs:
composer-lint:
name: "Composer Lint"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@v12.2.0"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@14.0.0"
+74 -51
View File
@@ -1,4 +1,4 @@
name: "CI"
name: "CI: PHPUnit"
on:
pull_request:
@@ -25,50 +25,58 @@ 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:
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
extension:
- "sqlite3"
- "pdo_sqlite"
deps:
- "highest"
native_lazy:
- "0"
stability:
- "stable"
include:
- php-version: "8.2"
- php-version: "8.4"
dbal-version: "4@dev"
extension: "pdo_sqlite"
native_lazy: "0"
- php-version: "8.2"
stability: "stable"
- php-version: "8.4"
dbal-version: "4@dev"
extension: "sqlite3"
native_lazy: "0"
- php-version: "8.1"
stability: "stable"
- php-version: "8.4"
dbal-version: "default"
deps: "lowest"
extension: "pdo_sqlite"
native_lazy: "0"
stability: "stable"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "pdo_sqlite"
native_lazy: "1"
stability: "stable"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "sqlite3"
stability: "dev"
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -80,12 +88,20 @@ jobs:
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Allow dev dependencies"
run: |
composer config minimum-stability dev
composer remove --no-update --dev phpbench/phpbench phpdocumentor/guides-cli
composer require --no-update symfony/console:^8 symfony/var-exporter:^8 doctrine/dbal:^4.4
composer require --dev --no-update symfony/cache:^8
if: "${{ matrix.stability == 'dev' }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "${{ matrix.deps }}"
@@ -94,7 +110,6 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
- name: "Run PHPUnit with Second Level Cache and PHPUnit 10"
run: |
@@ -116,12 +131,11 @@ jobs:
if: "${{ matrix.php-version != '8.1' }}"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
- name: "Upload coverage file"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.native_lazy }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-coverage"
path: "coverage*.xml"
@@ -160,33 +174,36 @@ jobs:
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"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
postgres-version:
- "17"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.2"
- php-version: "8.4"
dbal-version: "4@dev"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.2"
dbal-version: "3.7"
postgres-version: "9.6"
- php-version: "8.4"
dbal-version: "default"
postgres-version: "10"
extension: pdo_pgsql
services:
@@ -203,7 +220,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -220,7 +237,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -228,27 +245,30 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
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"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
- "4@dev"
mariadb-version:
- "11.4"
@@ -271,7 +291,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -288,7 +308,7 @@ jobs:
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -296,27 +316,30 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
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"
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
mysql-version:
- "5.7"
- "8.0"
@@ -324,11 +347,11 @@ jobs:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.2"
- php-version: "8.4"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
- php-version: "8.4"
dbal-version: "4@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
@@ -347,7 +370,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -364,7 +387,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -393,7 +416,7 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
@@ -411,17 +434,17 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v6"
uses: "actions/download-artifact@v8"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v5"
uses: "codecov/codecov-action@v6"
with:
directory: reports
env:
+1 -1
View File
@@ -17,4 +17,4 @@ on:
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@v12.2.0"
uses: "doctrine/.github/.github/workflows/documentation.yml@14.0.0"
+3 -3
View File
@@ -32,11 +32,11 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.4"
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -48,7 +48,7 @@ jobs:
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"
@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@v12.2.0"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@14.0.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
+3 -16
View File
@@ -25,17 +25,9 @@ jobs:
name: Static Analysis with PHPStan
runs-on: ubuntu-22.04
strategy:
matrix:
include:
- dbal-version: default
config: phpstan.neon
- dbal-version: 3.8.2
config: phpstan-dbal3.neon
steps:
- name: "Checkout code"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
- name: Install PHP
uses: shivammathur/setup-php@v2
@@ -44,13 +36,8 @@ jobs:
php-version: "8.4"
tools: cs2pr
- name: Require specific DBAL version
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: Install dependencies with Composer
uses: ramsey/composer-install@v2
uses: ramsey/composer-install@v4
- name: Run static analysis with phpstan/phpstan
run: "vendor/bin/phpstan analyse -c ${{ matrix.config }} --error-format=checkstyle | cs2pr"
run: "vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr"
+9 -9
View File
@@ -1,9 +1,9 @@
| [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] |
| [4.0.x][4.0] | [3.7.x][3.7] | [3.6.x][3.6] | [2.21.x][2.21] | [2.20.x][2.20] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![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] |
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.7 image]][3.7 workflow] | [![Build status][3.6 image]][3.6 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.7 coverage image]][3.7 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
Doctrine ORM is an object-relational mapper for PHP 8.4+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
@@ -21,16 +21,16 @@ without requiring unnecessary code duplication.
[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.7 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.7.x
[3.7]: https://github.com/doctrine/orm/tree/3.7.x
[3.7 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.7.x
[3.7 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.7.x/graph/badge.svg
[3.7 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.7.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
+320 -1
View File
@@ -6,6 +6,145 @@ awareness about deprecated code.
- Use of our low-overhead runtime deprecation API, details:
https://github.com/doctrine/deprecations/
# Upgrade to 4.0
## BC BREAK: EventManager to EventManagerInterface migration
The following methods used to return an instance of `Doctrine\Common\EventManager`,
and now return an instance of `Doctrine\Common\EventManagerInterface`:
- `Doctrine\ORM\EntityManager::getEventManager()`
- `Doctrine\ORM\EntityManagerDecorator::getEventManager()`
- `Doctrine\ORM\EntityManagerInterface::getEventManager()`
## BC BREAK: Remove `FieldMapping::$default`
The `default` property of `Doctrine\ORM\Mapping\FieldMapping` has been removed.
Use `FieldMapping::$options['default']` instead.
## BC BREAK: throw on `nullable` on columns that end up being used in a primary key
Specifying `nullable` on join columns that are part of a primary key is
an error and will cause an exception to be thrown.
## BC BREAK: `Doctrine\ORM\Mapping\LegacyReflectionFields` is removed
The `Doctrine\ORM\Mapping\LegacyReflectionFields` class has been removed.
Also, `Doctrine\ORM\Mapping\ClassMetadata::$reflFields` has been removed, as
well as methods depending on it.
## BC BREAK: Userland lazy objects are no longer supported
Userland lazy objects are no longer supported.
[Native lazy objects](https://www.php.net/manual/en/language.oop5.lazy-objects.php)
are used instead.
## BC BREAK: type declarations on constants
All constants in the ORM now have type declarations and are final. You may no
longer override them in extending types.
## Remove methods for configuring no longer configurable features
Since 3.0, lazy ghosts are enabled unconditionally, and so is rejecting ID
collisions in the identity map.
As a consequence, the following methods are removed:
* `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()`
* `Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()`
* `Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()`
## BC BREAK: New argument to `NewObjectExpression::dispatch()`
```diff
<?php
class NewObjectExpression extends Node
{
// …
- public function dispatch(SqlWalker $walker): string
+ public function dispatch(SqlWalker $walker, string|null $parentAlias = null): string
{
// …
}
}
```
## BC BREAK: New argument to `AbstractEntityPersister::buildCollectionCacheKey()`
```diff
<?php
abstract class AbstractEntityPersister implements CachedEntityPersister
{
// …
protected function buildCollectionCacheKey(
AssociationMapping $association,
array $ownerId,
+ string $filterHash
): CollectionCacheKey {
// …
}
}
```
## Require implementation of `OutputWalker`, remove `SqlWalker::getExecutor()`
The `SqlWalker::getExecutor()` method is removed. Output walkers should
implement the `\Doctrine\ORM\Query\OutputWalker` interface and create
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances.
## Remove `DatabaseDriver`
The class `Doctrine\ORM\Mapping\Driver\DatabaseDriver` is removed.
## Remove the `NotSupported` exception
The class `Doctrine\ORM\Exception\NotSupported` has been removed without replacement.
## Remove remaining `Serializable` implementation
`SequenceGenerator` does not implement the `Serializable` interface anymore.
The following methods have been removed:
* `SequenceGenerator::serialize()`
* `SequenceGenerator::unserialize()`
## Remove `orm:schema-tool:update` option `--complete`
That option was a no-op.
## Remove `Doctrine\ORM\Mapping\ReflectionEnumProperty`
This class has been removed.
Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from
`doctrine/persistence`.
## Forbid passing null to `ClassMetadata::fullyQualifiedClassName()`
Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is
no longer possible.
## Remove array access
Using array access on instances of the following classes is no longer possible:
- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping`
- `Doctrine\ORM\Mapping\EmbedClassMapping`
- `Doctrine\ORM\Mapping\FieldMapping`
- `Doctrine\ORM\Mapping\JoinColumnMapping`
- `Doctrine\ORM\Mapping\JoinTableMapping`
## Remove properties `$indexes` and `$uniqueConstraints` from `Doctrine\ORM\Mapping\Table`
The properties `$indexes` and `$uniqueConstraints` have been removed since they had no effect at all.
The preferred way of defining indices and unique constraints is by
using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\Index` attributes.
# Upgrade to 3.x General Notes
We recommend you upgrade to DBAL 3 first before upgrading to ORM 3. See
@@ -26,6 +165,186 @@ to run with ORM 3.
At this point, we recommend upgrading to PHP 8.4 first and then directly from
ORM 2.19 to 3.5 and up so that you can skip the lazy ghost proxy generation
and directly start using native lazy objects.
>>>>>>> origin/3.6.x
# Upgrade to 3.7
## Conditional breaking changes
3.7 adds support for `doctrine/collections` 3. If you upgrade to that version
of `doctrine/collections`, there are breaking changes in `doctrine/orm` as well,
because of cross-package inheritance and type declarations.
Most notably, `Doctrine\ORM\PersistentCollection::add` no longer returns a boolean:
```diff
- public function add(mixed $value): bool
+ public function add(mixed $value): void
```
That method always returned `true`, so you can safely stop using the return
value before upgrading.
Also, if you extend `Doctrine\ORM\Persisters\SqlValueVisitor`, you need to
ensure the following methods have a return type in your subclasses:
- `walkComparison()`
- `walkCompositeExpression()`
- `walkValue()`
## Deprecate `EventManager` return type in `EntityManager` methods
The return type of the following methods has been changed from
`Doctrine\Common\EventManager` to `Doctrine\Common\EventManagerInterface`:
- `Doctrine\ORM\Decorator\EntityManagerDecorator::getEventManager()`
- `Doctrine\ORM\EntityManager::getEventManager()`
- `Doctrine\ORM\EntityManagerInterface::getEventManager()`
All three methods continue to return an instance of `EventManager`, however
relying on that is deprecated and will no longer be the guaranteed in 4.0.
# Upgrade to 3.6
## Deprecate using string expression for default values in mappings
Using a string expression for default values in field mappings is deprecated.
Use `Doctrine\DBAL\Schema\DefaultExpression` instances instead.
Here is how to address this deprecation when mapping entities using PHP attributes:
```diff
use DateTime;
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentDate;
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTime;
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
final class TimeEntity
{
#[ORM\Id]
#[ORM\Column]
public int $id;
- #[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'], insertable: false, updatable: false)]
+ #[ORM\Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
public DateTime $createdAt;
- #[ORM\Column(options: ['default' => 'CURRENT_TIME'], insertable: false, updatable: false)]
+ #[ORM\Column(options: ['default' => new CurrentTime()], insertable: false, updatable: false)]
public DateTime $createdTime;
- #[ORM\Column(options: ['default' => 'CURRENT_DATE'], insertable: false, updatable: false)]
+ #[ORM\Column(options: ['default' => new CurrentDate()], insertable: false, updatable: false)]
public DateTime $createdDate;
}
```
Here is how to do the same when mapping entities using XML:
```diff
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\ORM\Functional\XmlTimeEntity">
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="createdAt" type="datetime" insertable="false" updatable="false">
<options>
- <option name="default">CURRENT_TIMESTAMP</option>
+ <option name="default">
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
+ </option>
</options>
</field>
<field name="createdAtImmutable" type="datetime_immutable" insertable="false" updatable="false">
<options>
- <option name="default">CURRENT_TIMESTAMP</option>
+ <option name="default">
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
+ </option>
</options>
</field>
<field name="createdTime" type="time" insertable="false" updatable="false">
<options>
- <option name="default">CURRENT_TIME</option>
+ <option name="default">
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTime"/>
+ </option>
</options>
</field>
<field name="createdDate" type="date" insertable="false" updatable="false">
<options>
- <option name="default">CURRENT_DATE</option>
+ <option name="default">
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentDate"/>
+ </option>
</options>
</field>
</entity>
</doctrine-mapping>
```
## Deprecate `FieldMapping::$default`
The `default` property of `Doctrine\ORM\Mapping\FieldMapping` is deprecated and
will be removed in 4.0. Instead, use `FieldMapping::$options['default']`.
## Deprecate specifying `nullable` on columns that end up being used in a primary key
Specifying `nullable` on join columns that are part of a primary key is
deprecated and will be an error in 4.0.
This can happen when using a join column mapping together with an id mapping,
or when using a join column mapping or an inverse join column mapping on a
many-to-many relationship.
```diff
class User
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Id]
#[ORM\ManyToOne(targetEntity: Family::class, inversedBy: 'users')]
- #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id', nullable: true)]
+ #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id')]
private ?Family $family;
#[ORM\ManyToMany(targetEntity: Group::class)]
#[ORM\JoinTable(name: 'user_group')]
- #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true)]
- #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id', nullable: true)]
+ #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id')]
+ #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
private Collection $groups;
}
```
## Deprecate `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
Using `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
is deprecated in favor of using an associative array of join parts with the
root alias as key.
## Deprecate using the `WITH` keyword for arbitrary DQL joins
Using the `WITH` keyword to specify the condition for an arbitrary DQL join is
deprecated in favor of using the `ON` keyword (similar to the SQL syntax for
joins).
The `WITH` keyword is now meant to be used only for filtering conditions in
association joins.
# Upgrade to 3.5
@@ -2159,7 +2478,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
+10 -11
View File
@@ -31,31 +31,30 @@
],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"require": {
"php": "^8.1",
"php": "^8.4",
"ext-ctype": "*",
"composer-runtime-api": "^2",
"doctrine/collections": "^2.2",
"doctrine/dbal": "^3.8.2 || ^4",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/collections": "^3",
"doctrine/dbal": "^4.2.1",
"doctrine/deprecations": "^1.1",
"doctrine/event-manager": "^2.1.1",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^3",
"doctrine/persistence": "^3.3.1 || ^4",
"doctrine/persistence": "^4",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "^6.3.9 || ^7.0"
"symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^14.0",
"phpbench/phpbench": "^1.0",
"phpdocumentor/guides-cli": "^1.4",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "2.1.23",
"phpstan/phpstan-deprecation-rules": "^2",
"phpunit/phpunit": "^10.5.0 || ^11.5",
"phpunit/phpunit": "^11.5.42",
"psr/log": "^1 || ^2 || ^3",
"symfony/cache": "^5.4 || ^6.2 || ^7.0"
"symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0",
"symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",
+39 -2
View File
@@ -40,7 +40,8 @@ Now this is all awfully technical, so let me come to some use-cases
fast to keep you motivated. Using walker implementation you can for
example:
- Modify the Output walker to get the raw SQL via ``Query->getSQL()``
with interpolated parameters.
- Modify the AST to generate a Count Query to be used with a
paginator for any given DQL query.
- Modify the Output Walker to generate vendor-specific SQL
@@ -50,7 +51,7 @@ example:
- Modify the Output walker to pretty print the SQL for debugging
purposes.
In this cookbook-entry I will show examples of the first two
In this cookbook-entry I will show examples of the first three
points. There are probably much more use-cases.
Generic count query for pagination
@@ -223,3 +224,39 @@ huge benefits with using vendor specific features. This would still
allow you write DQL queries instead of NativeQueries to make use of
vendor specific features.
Modifying the Output Walker to get the raw SQL with interpolated parameters
---------------------------------------------------------------------------
Sometimes we may want to log or trace the raw SQL being generated from its DQL
for profiling slow queries afterwards or audit queries that changed many rows
``$query->getSQL()`` will give us the prepared statement being passed to database
with all values of SQL parameters being replaced by positional ``?`` or named ``:name``
as parameters are interpolated into prepared statements by the database while executing the SQL.
``$query->getParameters()`` will give us details about SQL parameters that we've provided.
So we can create an output walker to interpolate all SQL parameters that will be
passed into prepared statement in PHP before database handle them internally:
.. literalinclude:: dql-custom-walkers/InterpolateParametersSQLOutputWalker.php
:language: php
Then you may get the raw SQL with this output walker:
.. code-block:: php
<?php
$query
->where('t.int IN (:ints)')->setParameter(':ints', [1, 2])
->orWhere('t.string IN (?0)')->setParameter(0, ['3', '4'])
->orWhere("t.bool = ?1")->setParameter('?1', true)
->orWhere("t.string = :string")->setParameter(':string', 'ABC')
->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, InterpolateParametersSQLOutputWalker::class)
->getSQL();
The where clause of the returned SQL should be like:
.. code-block:: sql
WHERE t0_.int IN (1, 2)
OR t0_.string IN ('3', '4')
OR t0_.bool = 1
OR t0_.string = 'ABC'
@@ -0,0 +1,47 @@
<?php
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\BooleanType;
use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Query\AST;
use Doctrine\ORM\Query\SqlOutputWalker;
class InterpolateParametersSQLOutputWalker extends SqlOutputWalker
{
/** {@inheritdoc} */
public function walkInputParameter(AST\InputParameter $inputParam): string
{
$parameter = $this->getQuery()->getParameter($inputParam->name);
if ($parameter === null) {
return '?';
}
$value = $parameter->getValue();
/** @var ParameterType|ArrayParameterType|int|string $typeName */
/** @see \Doctrine\ORM\Query\ParameterTypeInferer::inferType() */
$typeName = $parameter->getType();
$platform = $this->getConnection()->getDatabasePlatform();
$processParameterType = static fn(ParameterType $type) => static fn($value): string =>
(match ($type) { /** @see Type::getBindingType() */
ParameterType::NULL => 'NULL',
ParameterType::INTEGER => $value,
ParameterType::BOOLEAN => (new BooleanType())->convertToDatabaseValue($value, $platform),
ParameterType::STRING, ParameterType::ASCII => $platform->quoteStringLiteral($value),
default => throw new ValueNotConvertible($value, $type->name)
});
if (is_string($typeName) && Type::hasType($typeName)) {
return Type::getType($typeName)->convertToDatabaseValue($value, $platform);
}
if ($typeName instanceof ParameterType) {
return $processParameterType($typeName)($value);
}
if ($typeName instanceof ArrayParameterType && is_array($value)) {
$type = ArrayParameterType::toElementParameterType($typeName);
return implode(', ', array_map($processParameterType($type), $value));
}
throw new ValueNotConvertible($value, $typeName);
}
}
+51 -173
View File
@@ -29,22 +29,11 @@ steps of configuration.
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
if (PHP_VERSION_ID > 80400) {
$config->enableNativeLazyObjects(true);
} else {
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
$config->setProxyNamespace('MyProject\Proxies');
if ($applicationMode === "development") {
$config->setAutoGenerateProxyClasses(true);
} else {
$config->setAutoGenerateProxyClasses(false);
}
}
$config->enableNativeLazyObjects(true);
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
@@ -76,55 +65,6 @@ Configuration Options
The following sections describe all the configuration options
available on a ``Doctrine\ORM\Configuration`` instance.
.. _reference-native-lazy-objects:
Native Lazy Objects (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With PHP 8.4 we recommend that you use native lazy objects instead of
the code generation approach using the ``symfony/var-exporter`` Ghost trait.
With Doctrine 4, the minimal requirement will become PHP 8.4 and native lazy objects
will become the only approach to lazy loading.
.. code-block:: php
<?php
$config->enableNativeLazyObjects(true);
Proxy Directory
~~~~~~~~~~~~~~~
Required except if you use native lazy objects with PHP 8.4.
This setting will be removed in the future.
.. code-block:: php
<?php
$config->setProxyDir($dir);
$config->getProxyDir();
Gets or sets the directory where Doctrine generates any proxy
classes. For a detailed explanation on proxy classes and how they
are used in Doctrine, refer to the "Proxy Objects" section further
down.
Proxy Namespace
~~~~~~~~~~~~~~~
Required except if you use native lazy objects with PHP 8.4.
This setting will be removed in the future.
.. code-block:: php
<?php
$config->setProxyNamespace($namespace);
$config->getProxyNamespace();
Gets or sets the namespace to use for generated proxy classes. For
a detailed explanation on proxy classes and how they are used in
Doctrine, refer to the "Proxy Objects" section further down.
Metadata Driver (**REQUIRED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -156,15 +96,59 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the attribute
driver, because otherwise mass-operations on all entities through
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
directories. With this feature a single driver can support multiple
directories of Entities.
the console could not work correctly. Metadata drivers can accept either
a single directory as a string or an array of directories.
AttributeDriver also accepts ``Doctrine\Persistence\Mapping\Driver\ClassLocator``,
allowing one to customize file discovery logic. You may choose to use Symfony Finder, or
utilize directory scan with ``FileClassLocator::createFromDirectories()``:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
$paths = ['/path/to/lib/MyProject/Entities'];
$classLocator = FileClassLocator::createFromDirectories($paths);
$driverImpl = new AttributeDriver($classLocator);
$config->setMetadataDriverImpl($driverImpl);
With this feature, you're empowered to provide a fine-grained iterator of only necessary
files to the Driver. For example, if you are using Vertical Slice architecture, you can
exclude ``*Test.php``, ``*Controller.php``, ``*Service.php``, etc.:
.. code-block:: php
<?php
use Symfony\Component\Finder\Finder;
$finder = new Finder()->files()->in($paths)
->name('*.php')
->notName(['*Test.php', '*Controller.php', '*Service.php']);
$classLocator = new FileClassLocator($finder);
If you know the list of class names you want to track, use
``Doctrine\Persistence\Mapping\Driver\ClassNames``:
.. code-block:: php
<?php
use Doctrine\Persistence\Mapping\Driver\ClassNames;
use App\Entity\{Article, Book};
$entityClasses = [Article::class, Book::class];
$classLocator = new ClassNames($entityClasses);
$driverImpl = new AttributeDriver($classLocator);
$config->setMetadataDriverImpl($driverImpl);
Metadata Cache (**RECOMMENDED**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -224,63 +208,6 @@ Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
Auto-generating Proxy Classes (**OPTIONAL**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This setting is not required if you use native lazy objects with PHP 8.4
and will be removed in the future.
Proxy classes can either be generated manually through the Doctrine
Console or automatically at runtime by Doctrine. The configuration
option that controls this behavior is:
.. code-block:: php
<?php
$config->setAutoGenerateProxyClasses($mode);
Possible values for ``$mode`` are:
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
.. code-block:: php
$ ./doctrine orm:generate-proxies
When you do this in a development environment,
be aware that you may get class/file not found errors if certain proxies
are not yet generated. You may also get failing lazy-loads if new
methods were added to the entity class that are not yet in the proxy class.
In such a case, simply use the Doctrine Console to (re)generate the
proxy classes.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
This strategy is only sane for development.
In a production environment, it is highly recommended to use
AUTOGENERATE_NEVER to allow for optimal performances. The other
options are interesting in development environment.
``setAutoGenerateProxyClasses`` can accept a boolean
value. This is still possible, ``FALSE`` being equivalent to
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
Development vs Production Configuration
---------------------------------------
@@ -396,55 +323,6 @@ transparently initialize itself on first access.
This will override the 'fetch' option specified in the mapping for
that association, but only for that query.
Generating Proxy classes
~~~~~~~~~~~~~~~~~~~~~~~~
In a production environment, it is highly recommended to use
``AUTOGENERATE_NEVER`` to allow for optimal performances.
However you will be required to generate the proxies manually
using the Doctrine Console:
.. code-block:: php
$ ./doctrine orm:generate-proxies
The other options are interesting in development environment:
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
a proxy directory. Proxies will be generated and written to file
on each request, so any modification to your code will be acknowledged.
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
proxy file. If your code changes, you will need to regenerate the
proxies manually.
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
but without writing them to disk.
Autoloading Proxies
-------------------
When you deserialize proxy objects from the session or any other storage
it is necessary to have an autoloading mechanism in place for these classes.
For implementation reasons Proxy class names are not PSR-0 compliant. This
means that you have to register a special autoloader for these classes:
.. code-block:: php
<?php
use Doctrine\ORM\Proxy\Autoloader;
$proxyDir = "/path/to/proxies";
$proxyNamespace = "MyProxies";
Autoloader::register($proxyDir, $proxyNamespace);
If you want to execute additional logic to intercept the proxy file not found
state you can pass a closure as the third argument. It will be called with
the arguments proxydir, namespace and className when the proxy file could not
be found.
Multiple Metadata Sources
-------------------------
+3 -4
View File
@@ -18,7 +18,7 @@ well.
Requirements
------------
Doctrine ORM requires a minimum of PHP 8.1. For greatly improved
Doctrine ORM requires a minimum of PHP 8.4. For greatly improved
performance it is also recommended that you use APC with PHP.
Doctrine ORM Packages
@@ -79,9 +79,8 @@ Entities
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class can be final or read-only when
you use :ref:`native lazy objects <reference-native-lazy-objects>`.
It may contain final methods or read-only properties too.
- An entity class can be final or read-only. It may contain final
methods or read-only properties too.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
@@ -668,11 +668,6 @@ and in the Context of a :ref:`#[ManyToMany] <attrref_manytomany>`. If this attri
are missing they will be computed considering the field's name and the current
:doc:`naming strategy <namingstrategy>`.
The ``#[InverseJoinColumn]`` is the same as ``#[JoinColumn]`` and is used in the context
of a ``#[ManyToMany]`` attribute declaration to specifiy the details of the join table's
column information used for the join to the inverse entity. This is only required
on PHP 8.0, where nested attributes are not yet supported.
Optional parameters:
- **name**: Column name that holds the foreign key identifier for
+186 -1
View File
@@ -168,7 +168,7 @@ Here is a complete list of ``Column``s attributes (all optional):
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into. See :ref:`reference-enum-mapping`.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
which is the maximum number of digits that are stored for the values.
@@ -182,6 +182,37 @@ 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.
When using XML, you can specify object instances using the ``<object>``
element:
.. code-block:: xml
<field name="createdAt" type="datetime" insertable="false" updatable="false">
<options>
<option name="default">
<object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
</option>
</options>
</field>
The ``<object>`` element requires a ``class`` attribute specifying the
fully qualified class name to instantiate.
.. configuration-block::
.. literalinclude:: basic-mapping/DefaultValues.php
:language: attribute
.. literalinclude:: basic-mapping/default-values.xml
:language: xml
.. _reference-php-mapping-types:
PHP Types Mapping
@@ -290,6 +321,160 @@ that value and raw value are different, you have to use the raw value representa
$messageRepository = $entityManager->getRepository(Message::class);
$deMessages = $messageRepository->findBy(['language' => 'de']); // Use lower case here for raw value representation
.. _reference-enum-mapping:
Mapping PHP Enums
-----------------
.. versionadded:: 2.11
Doctrine natively supports mapping PHP backed enums to database columns.
A backed enum is a PHP enum that the same scalar type (``string`` or ``int``)
assigned to each case. Doctrine stores the scalar value in the database and
converts it back to the enum instance when hydrating the entity.
Using ``enumType`` provides three main benefits:
- **Automatic conversion**: Doctrine handles the conversion in both directions
transparently. When loading an entity, scalar values from the database are
converted into enum instances. When persisting, enum instances are reduced
to their scalar ``->value`` before being sent to the database.
- **Type-safety**: Entity properties contain enum instances directly. Your
getters return ``Suit`` instead of ``string``, removing the need to call
``Suit::from()`` manually.
- **Validation**: When a database value does not match any enum case, Doctrine
throws a ``MappingException`` during hydration instead of silently returning
an invalid value.
This feature works with all database platforms supported by Doctrine (MySQL,
PostgreSQL, SQLite, etc.) as it relies on standard column types (``string``,
``integer``, ``json``, ``simple_array``) rather than any vendor-specific enum
type.
.. note::
This is unrelated to the MySQL-specific ``ENUM`` column type covered in
:doc:`the MySQL Enums cookbook entry </cookbook/mysql-enums>`.
Defining an Enum
~~~~~~~~~~~~~~~~
.. literalinclude:: basic-mapping/Suit.php
:language: php
Only backed enums (``string`` or ``int``) are supported. Unit enums (without
a scalar value) cannot be mapped.
Single-Value Columns
~~~~~~~~~~~~~~~~~~~~
Use the ``enumType`` option on ``#[Column]`` to map a property to a backed enum.
The underlying database column stores the enum's scalar value (``string`` or ``int``).
.. literalinclude:: basic-mapping/EnumMapping.php
:language: php
When the PHP property is typed with the enum class, Doctrine automatically
infers the appropriate column type (``string`` for string-backed enums,
``integer`` for int-backed enums) and sets ``enumType``. You can also specify
the column ``type`` explicitly.
Storing Collections of Enums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can store multiple enum values in a single column by combining ``enumType``
with a collection column type: ``json`` or ``simple_array``.
.. note::
Automatic type inference does not apply to collection columns. When the
PHP property is typed as ``array``, Doctrine cannot detect the enum class.
You must specify both ``type`` and ``enumType`` explicitly.
.. literalinclude:: basic-mapping/EnumCollectionMapping.php
:language: php
With ``json``, the values are stored as a JSON array (e.g. ``["hearts","spades"]``).
With ``simple_array``, the values are stored as a comma-separated string
(e.g. ``hearts,spades``).
In both cases, Doctrine converts each element to and from the enum
automatically during hydration and persistence.
.. tip::
Use ``json`` when enum values may contain commas, when you need to store
int-backed enums (as it preserves value types), when the column also
stores complex/nested data structures, or when you want to query individual
values using database-native JSON operators (e.g. PostgreSQL ``jsonb``).
Prefer ``simple_array`` for a compact, human-readable storage of
string-backed enums whose values do not contain commas.
+-------------------+-----------------------------+-------------------------------+
| Column type | Database storage | PHP type |
+===================+=============================+===============================+
| ``string`` | ``hearts`` | ``Suit`` |
+-------------------+-----------------------------+-------------------------------+
| ``integer`` | ``1`` | ``Priority`` |
+-------------------+-----------------------------+-------------------------------+
| ``json`` | ``["hearts","spades"]`` | ``array<Suit>`` |
+-------------------+-----------------------------+-------------------------------+
| ``simple_array`` | ``hearts,spades`` | ``array<Suit>`` |
+-------------------+-----------------------------+-------------------------------+
Nullable Enums
~~~~~~~~~~~~~~
Enum columns can be nullable. When the database value is ``NULL``, Doctrine
preserves it as ``null`` without triggering any validation error.
.. code-block:: php
<?php
#[ORM\Column(type: 'string', nullable: true, enumType: Suit::class)]
private Suit|null $suit = null;
Default Values
~~~~~~~~~~~~~~
You can specify a database-level default using an enum case directly in the
column options:
.. code-block:: php
<?php
#[ORM\Column(options: ['default' => Suit::Hearts])]
public Suit $suit;
Using Enums in Queries
~~~~~~~~~~~~~~~~~~~~~~
Enum instances can be used directly as parameters in DQL, QueryBuilder, and
repository methods. Doctrine converts them to their scalar value automatically.
.. code-block:: php
<?php
// QueryBuilder
$qb = $em->createQueryBuilder();
$qb->select('c')
->from(Card::class, 'c')
->where('c.suit = :suit')
->setParameter('suit', Suit::Clubs);
// Repository
$cards = $em->getRepository(Card::class)->findBy(['suit' => Suit::Clubs]);
XML Mapping
~~~~~~~~~~~
When using XML mapping, the ``enum-type`` attribute is used on ``<field>``
elements:
.. code-block:: xml
<field name="suit" type="string" enum-type="App\Entity\Suit" />
.. _reference-mapping-types:
Doctrine Mapping Types
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use DateTime;
use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[Entity]
class Message
{
#[Column(options: ['default' => 'Hello World!'])]
private string $text;
#[Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
private DateTime $createdAt;
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Player
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
/** @var list<Suit> */
#[ORM\Column(type: 'json', enumType: Suit::class)]
private array $favouriteSuits = [];
/** @var list<Suit> */
#[ORM\Column(type: 'simple_array', enumType: Suit::class)]
private array $allowedSuits = [];
}
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Card
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
#[ORM\Column(enumType: Suit::class)]
private Suit $suit;
}
+13
View File
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Entity;
enum Suit: string
{
case Hearts = 'hearts';
case Diamonds = 'diamonds';
case Clubs = 'clubs';
case Spades = 'spades';
}
@@ -0,0 +1,16 @@
<doctrine-mapping>
<entity name="Message">
<field name="text">
<options>
<option name="default">Hello World!</option>
</options>
</field>
<field name="createdAt" insertable="false" updatable="false">
<options>
<option name="default">
<object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
</option>
</options>
</field>
</entity>
</doctrine-mapping>
@@ -490,7 +490,7 @@ where you can generate an arbitrary join with the following syntax:
.. code-block:: php
<?php
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b WITH u.email = b.email');
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b ON u.email = b.email');
With an arbitrary join the result differs from the joins using a mapped property.
The result of an arbitrary join is an one dimensional array with a mix of the entity from the ``SELECT``
@@ -513,13 +513,15 @@ it loads all the related ``Banlist`` objects corresponding to this ``User``. Thi
when the DQL is switched to an arbitrary join.
.. note::
The differences between WHERE, WITH and HAVING clauses may be
The differences between WHERE, WITH, ON and HAVING clauses may be
confusing.
- WHERE is applied to the results of an entire query
- WITH is applied to a join as an additional condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
the WITH is required, even if it is 1 = 1
- ON is applied to arbitrary joins as the join condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b ON f.id = b.id)
the ON is required, even if it is 1 = 1. WITH is also
supported as alternative keyword for that case for BC reasons.
- WITH is applied to an association join as an additional condition.
- HAVING is applied to the results of a query after
aggregation (GROUP BY)
@@ -1409,8 +1411,7 @@ Result Cache API:
$query->setResultCacheDriver(new ApcCache());
$query->useResultCache(true)
->setResultCacheLifeTime(3600);
$query->enableResultCache(3600);
$result = $query->getResult(); // cache miss
@@ -1420,8 +1421,8 @@ Result Cache API:
$query->setResultCacheId('my_query_result');
$result = $query->getResult(); // saved in given result cache id.
// or call useResultCache() with all parameters:
$query->useResultCache(true, 3600, 'my_query_result');
// or call enableResultCache() with all parameters:
$query->enableResultCache(3600, 'my_query_result');
$result = $query->getResult(); // cache hit!
// Introspection
@@ -1699,9 +1700,14 @@ From, Join and Index by
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
.. note::
Using the ``WITH`` keyword for the ``ConditionalExpression`` of a
``RangeVariableDeclaration`` is deprecated and will be removed in
ORM 4.0. Use the ``ON`` keyword instead.
Select Expressions
~~~~~~~~~~~~~~~~~~
-24
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
-------
+16
View File
@@ -208,6 +208,22 @@ 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>
In this example, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
+24 -7
View File
@@ -344,10 +344,10 @@ the Query object which can be retrieved from ``EntityManager#createQuery()``.
Executing a Query
^^^^^^^^^^^^^^^^^
The QueryBuilder is a builder object only - it has no means of actually
executing the Query. Additionally a set of parameters such as query hints
cannot be set on the QueryBuilder itself. This is why you always have to convert
a querybuilder instance into a Query object:
The QueryBuilder is only a builder object - it has no means of actually
executing the Query. Additional functionality, such as enabling the result cache,
cannot be set on the QueryBuilder itself. This is why you must always convert
a QueryBuilder instance into a Query object:
.. code-block:: php
@@ -355,9 +355,8 @@ a querybuilder instance into a Query object:
// $qb instanceof QueryBuilder
$query = $qb->getQuery();
// Set additional Query options
$query->setQueryHint('foo', 'bar');
$query->useResultCache('my_cache_id');
// Enable the result cache
$query->enableResultCache(3600, 'my_custom_id');
// Execute Query
$result = $query->getResult();
@@ -555,6 +554,24 @@ using ``addCriteria``:
$qb->addCriteria($criteria);
// then execute your query like normal
Adding hints to a Query
^^^^^^^^^^^^^^^^^^^^^^^
You can also set query hints to a QueryBuilder by using ``setHint``:
.. code-block:: php
<?php
// ...
// $qb instanceof QueryBuilder
$qb->setHint('hintName', 'hintValue');
// then execute your query like normal
The query hint can hold anything the usual query hints can hold
except null. Those hints will be applied to the query when the
query is created.
Low Level API
^^^^^^^^^^^^^
+1 -1
View File
@@ -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
+5 -3
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
@@ -83,8 +83,6 @@ The following Commands are currently available:
cache drivers.
- ``orm:clear-cache:result`` Clear result cache of the various
cache drivers.
- ``orm:generate-proxies`` Generates proxy classes for entity
classes. Deprecated in favor of using native lazy objects.
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
line.
- ``orm:schema-tool:create`` Processes the schema and either
@@ -96,6 +94,10 @@ The following Commands are currently available:
- ``orm:schema-tool:update`` Processes the schema and either
update the database schema of EntityManager Storage Connection or
generate the SQL output.
- ``orm:debug:event-manager`` Lists event listeners for an entity
manager, optionally filtered by event name.
- ``orm:debug:entity-listeners`` Lists entity listeners for a given
entity, optionally filtered by event name.
The following alias is defined:
+1 -3
View File
@@ -49,9 +49,7 @@ An entity contains persistable properties. A persistable property
is an instance variable of the entity that is saved into and retrieved from the database
by Doctrine's data mapping capabilities.
An entity class can be final or read-only when you use
:ref:`native lazy objects <reference-native-lazy-objects>`.
It may contain final methods or read-only properties too.
An entity class can be final or read-only. It may contain final methods or read-only properties too.
An Example Model: Bug Tracker
-----------------------------
+210
View File
@@ -1,6 +1,41 @@
Pagination
==========
Doctrine ORM provides two pagination strategies for DQL queries. Both handle
the low-level SQL plumbing, but they make different trade-offs:
.. list-table::
:header-rows: 1
* - Feature
- Offset ``Paginator``
- ``CursorPaginator``
* - Total count
- Yes
- No
* - Random access to page N
- Yes
- No
* - Stable under concurrent inserts/deletes
- No
- Yes
* - Performance on deep pages
- Degrades (OFFSET scan)
- Constant (index range scan)
* - Requires deterministic ORDER BY
- No
- Yes
Choose the **Offset Paginator** when you need a total page count or want to
let users jump to an arbitrary page number.
Choose the **Cursor Paginator** when you need stable, high-performance
pagination on large datasets and a simple previous/next navigation is
sufficient.
Offset-Based Pagination
-----------------------
Doctrine ORM ships with a Paginator for DQL queries. It
has a very simple API and implements the SPL interfaces ``Countable`` and
``IteratorAggregate``.
@@ -58,3 +93,178 @@ In this way the `DISTINCT` keyword will be omitted and can bring important perfo
->setHint(Paginator::HINT_ENABLE_DISTINCT, false)
->setFirstResult(0)
->setMaxResults(100);
Cursor-Based Pagination
-----------------------
Doctrine ORM ships with a ``CursorPaginator`` for cursor-based pagination of DQL queries.
Unlike offset-based pagination, cursor pagination uses opaque pointers (cursors) derived
from the last seen row to fetch the next or previous page. This makes it stable and
performant on large datasets — no matter how deep you paginate, the database always uses
an index range scan instead of skipping rows.
.. note::
Cursor pagination requires a **deterministic ``ORDER BY`` clause**. Every column
combination used for sorting must uniquely identify a position in the result set.
A common pattern is to sort by a timestamp and then by primary key as a tie-breaker.
Basic Usage
~~~~~~~~~~~
The ``$cursor`` parameter is an opaque string produced by a previous call to
``getNextCursorAsString()`` or ``getPreviousCursorAsString()``. On the first request
it is ``null`` or an empty string ``''`` — both are treated identically as the first
page. It is typically read from the incoming HTTP query string:
.. code-block:: php
$cursor = $_GET['cursor'] ?? null; // null or '' on the first page
.. code-block:: php
<?php
use Doctrine\ORM\Tools\CursorPagination\CursorPaginator;
$dql = 'SELECT p FROM BlogPost p ORDER BY p.createdAt DESC, p.id DESC';
$query = $entityManager->createQuery($dql);
$paginator = (new CursorPaginator($query))
->paginate(cursor: $cursor, limit: 15);
foreach ($paginator as $post) {
echo $post->getTitle() . "\n";
}
echo $paginator->getPreviousCursorAsString(); // previous encoded cursor string
echo $paginator->getNextCursorAsString(); // next encoded cursor string
Navigating Pages
~~~~~~~~~~~~~~~~
Pass the encoded cursor back on subsequent requests to move forward or backward:
.. code-block:: php
<?php
// Next page
$paginator->paginate(15, $nextCursor);
// Previous page
$paginator->paginate(15, $previousCursor);
The cursor is an encoded string containing the location at which the next query should begin fetching results, along with the navigation direction.
API Reference
~~~~~~~~~~~~~
``CursorPaginator::paginate(?string $cursor, int $limit): self``
Executes the query and stores the results. Fetches ``$limit + 1`` rows to
detect whether a further page exists, then trims the extra row. Returns
``$this`` for chaining.
``CursorPaginator::getNextCursor(): Cursor``
Returns the ``Cursor`` object for the next page. Throws a ``LogicException``
if there is no next page — call ``hasNextPage()`` first.
``CursorPaginator::getPreviousCursor(): Cursor``
Returns the ``Cursor`` object for the previous page. Throws a ``LogicException``
if there is no previous page — call ``hasPreviousPage()`` first.
``CursorPaginator::getNextCursorAsString(): string``
Returns the encoded cursor to retrieve the next page. Throws a
``LogicException`` if there is no next page — call ``hasNextPage()`` first.
``CursorPaginator::getPreviousCursorAsString(): string``
Returns the encoded cursor to retrieve the previous page. Throws a
``LogicException`` if there is no previous page — call ``hasPreviousPage()`` first.
``CursorPaginator::hasNextPage(): bool``
Returns whether a next page is available.
``CursorPaginator::hasPreviousPage(): bool``
Returns whether a previous page is available.
``CursorPaginator::hasToPaginate(): bool``
Returns whether either a next or previous page exists (i.e. the result
set spans more than one page).
``CursorPaginator::getValues(): array``
Returns the raw entity array for the current page.
``CursorPaginator::getItems(): array``
Returns an array of ``CursorItem`` objects, each wrapping an entity and its
individual ``Cursor``. Useful when you need per-row cursors.
``CursorPaginator::getCursorForItem(mixed $item, bool $isNext = true): Cursor``
Builds a ``Cursor`` pointing at a specific entity. ``$isNext = true`` means
"start *after* this item"; ``false`` means "start *before* this item".
``CursorPaginator::count(): int``
Returns the number of items on the current page (implements ``Countable``).
**Next page**
.. code-block:: sql
SELECT ...
FROM post p
WHERE (p.created_at < :cursor_val_0)
OR (p.created_at = :cursor_val_0 AND p.id < :cursor_id_1)
ORDER BY p.created_at DESC, p.id DESC
LIMIT 16 -- limit + 1
**Previous page**
.. code-block:: sql
SELECT ...
FROM post p
WHERE (p.created_at > :cursor_val_0)
OR (p.created_at = :cursor_val_0 AND p.id > :cursor_id_1)
ORDER BY p.created_at ASC, p.id ASC -- reversed
LIMIT 16
HTML Template Example
~~~~~~~~~~~~~~~~~~~~~
The following example shows how to render a paginated list with previous/next
navigation links using the ``CursorPaginator`` in a PHP template:
.. literalinclude:: pagination/cursor-pagination.php
:language: php
Cursor Encoding
~~~~~~~~~~~~~~~
A cursor is serialized to a URL-safe string via ``Cursor::encodeToString()`` and
deserialized back via the static ``Cursor::fromEncodedString()``. The format is a
JSON object encoded with URL-safe Base64 (no padding):
.. code-block:: json
{
"p.createdAt": "2024-01-15T10:30:00+00:00",
"p.id": 42,
"_isNext": true
}
The ``_isNext`` flag distinguishes next-page cursors from previous-page cursors.
All other keys are the DQL path expressions (``alias.field``) of the ``ORDER BY``
columns, and their values are the database representations of the pivot row's
field values.
If you need a different serialization format (e.g. encryption), build it on top of
a ``Cursor`` instance: call ``$cursor->toArray()`` to get the raw data, apply your
own encoding, and reconstruct with ``new Cursor($parameters, $isNext)``.
Limitations
~~~~~~~~~~~
- Every ``ORDER BY`` column must map to an entity field. Raw SQL expressions or
computed columns in ``ORDER BY`` are not supported.
- ``COUNT`` queries are not available; cursor pagination does not know the total
number of results by design. If you need a total count, use the
offset-based ``Paginator`` described above.
- The query must have at least one ``ORDER BY`` item; the paginator throws a
``LogicException`` otherwise.
@@ -0,0 +1,31 @@
<?php
use Doctrine\ORM\Tools\CursorPagination\CursorPaginator;
$cursor = $_GET['cursor'] ?? null;
$query = $entityManager->createQuery('SELECT p FROM BlogPost p ORDER BY p.createdAt DESC, p.id DESC');
/** @var CursorPaginator<BlogPost> $paginator */
$paginator = (new CursorPaginator($query))
->paginate(cursor: $cursor, limit: 15);
?>
<p><?= $paginator->count() ?> result(s) on this page.</p>
<ul>
<?php foreach ($paginator as $post): ?>
<li><?= escape($post->getTitle()) ?></li>
<?php endforeach ?>
</ul>
<?php if ($paginator->hasToPaginate()): ?>
<nav>
<?php if ($paginator->hasPreviousPage()): ?>
<a href="?cursor=<?= escape($paginator->getPreviousCursorAsString()) ?>">Previous</a>
<?php endif ?>
<?php if ($paginator->hasNextPage()): ?>
<a href="?cursor=<?= escape($paginator->getNextCursorAsString()) ?>">Next</a>
<?php endif ?>
</nav>
<?php endif ?>
+13 -3
View File
@@ -155,10 +155,20 @@
</xs:restriction>
</xs:simpleType>
<xs:complexType name="object">
<xs:attribute name="class" type="xs:string" use="required"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
<xs:complexType name="option" mixed="true">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="option" type="orm:option"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
<xs:choice minOccurs="0" maxOccurs="1">
<xs:element name="object" type="orm:object"/>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="option" type="orm:option"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
</xs:sequence>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="required"/>
<xs:anyAttribute namespace="##other"/>
+1 -9
View File
@@ -11,7 +11,7 @@
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps"/>
<config name="php_version" value="80100"/>
<config name="php_version" value="80400"/>
<file>src</file>
<file>tests</file>
@@ -50,8 +50,6 @@
</rule>
<rule ref="PSR1.Classes.ClassDeclaration.MultipleClasses">
<exclude-pattern>src/Mapping/Driver/LoadMappingFileImplementation.php</exclude-pattern>
<exclude-pattern>src/Mapping/GetReflectionClassImplementation.php</exclude-pattern>
<exclude-pattern>tests/*</exclude-pattern>
</rule>
@@ -210,12 +208,6 @@
<exclude-pattern>tests/Tests/Models/DDC1590/DDC1590User.php</exclude-pattern>
</rule>
<rule ref="Squiz.Classes.ValidClassName.NotCamelCaps">
<!-- we need to test what happens with an stdClass proxy -->
<exclude-pattern>tests/Tests/Proxy/DefaultProxyClassNameResolverTest.php</exclude-pattern>
</rule>
<rule ref="Squiz.Commenting.FunctionComment.WrongStyle">
<!-- https://github.com/squizlabs/PHP_CodeSniffer/issues/1961 -->
<exclude-pattern>tests/Tests/Mocks/DatabasePlatformMock.php</exclude-pattern>
+30 -366
View File
@@ -619,7 +619,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
@@ -942,12 +942,6 @@ parameters:
count: 4
path: src/Mapping/ClassMetadata.php
-
message: '#^If condition is always true\.$#'
identifier: if.alwaysTrue
count: 1
path: src/Mapping/ClassMetadata.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ClassMetadata\:\:inlineEmbeddable\(\) has parameter \$embeddable with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
@@ -1308,6 +1302,12 @@ parameters:
count: 1
path: src/Mapping/DefaultQuoteStrategy.php
-
message: '#^Expression on left side of \?\? is not nullable\.$#'
identifier: nullCoalesce.expr
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\AttributeDriver\:\:isRepeatedPropertyDeclaration\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
@@ -1326,102 +1326,6 @@ parameters:
count: 1
path: src/Mapping/Driver/AttributeDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedColumn…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedTableN…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencingColum…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Table'' and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Index and ''getType'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\> and Doctrine\\ORM\\Mapping\\ClassMetadata will always evaluate to true\.$#'
identifier: instanceof.alwaysTrue
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:__construct\(\) has parameter \$sm with generic class Doctrine\\DBAL\\Schema\\AbstractSchemaManager but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildFieldMappings\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildIndexes\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:buildToOneAssociationMappings\(\) has parameter \$metadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) has parameter \$asset with generic class Doctrine\\DBAL\\Schema\\AbstractAsset but does not specify its types\: N$#'
identifier: missingType.generics
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getClassNameForTable\(\) should return class\-string but returns string\.$#'
identifier: return.type
count: 2
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#1 \$asset of static method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) expects Doctrine\\DBAL\\Schema\\AbstractAsset, Doctrine\\DBAL\\Schema\\Column\|false given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Parameter \#2 \$columnName of method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getFieldNameForColumn\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 4
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver\:\:__construct\(\) has parameter \$fileExtension with no type specified\.$#'
identifier: missingType.parameter
@@ -1447,7 +1351,7 @@ parameters:
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Parameter \#1 \$columnDef of method Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:setDiscriminatorColumn\(\) expects array\{name\: string\|null, fieldName\?\: string\|null, type\?\: string\|null, length\?\: int\|null, columnDefinition\?\: string\|null, enumType\?\: class\-string\<BackedEnum\>\|null, options\?\: array\<string, mixed\>\|null\}\|Doctrine\\ORM\\Mapping\\DiscriminatorColumnMapping\|null, array\{name\: string\|null, type\: string, length\: int, columnDefinition\: string\|null, enumType\: string\|null, options\?\: array\<int\|string, array\<int\|string, mixed\>\|bool\|string\>\} given\.$#'
message: '#^Parameter \#1 \$columnDef of method Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:setDiscriminatorColumn\(\) expects array\{name\: string\|null, fieldName\?\: string\|null, type\?\: string\|null, length\?\: int\|null, columnDefinition\?\: string\|null, enumType\?\: class\-string\<BackedEnum\>\|null, options\?\: array\<string, mixed\>\|null\}\|Doctrine\\ORM\\Mapping\\DiscriminatorColumnMapping\|null, array\{name\: string\|null, type\: string, length\: int, columnDefinition\: string\|null, enumType\: string\|null, options\?\: array\<int\|string, array\<int\|string, mixed\>\|bool\|object\|string\>\} given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/Driver/XmlDriver.php
@@ -1477,7 +1381,7 @@ parameters:
path: src/Mapping/Driver/XmlDriver.php
-
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:\$table \(array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\?\: array\<string, mixed\>, quoted\?\: bool\}\) does not accept array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\: array\<int\|string, array\<int\|string, mixed\>\|bool\|string\>, quoted\?\: bool\}\.$#'
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:\$table \(array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\?\: array\<string, mixed\>, quoted\?\: bool\}\) does not accept array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\: array\<int\|string, array\<int\|string, mixed\>\|bool\|object\|string\>, quoted\?\: bool\}\.$#'
identifier: assign.propertyType
count: 1
path: src/Mapping/Driver/XmlDriver.php
@@ -1524,18 +1428,6 @@ parameters:
count: 1
path: src/Mapping/JoinTableMapping.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\LegacyReflectionFields\:\:__construct\(\) has parameter \$classMetadata with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Mapping/LegacyReflectionFields.php
-
message: '#^Parameter \#1 \$class of method Doctrine\\Persistence\\Mapping\\ReflectionService\:\:getAccessibleProperty\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Mapping/LegacyReflectionFields.php
-
message: '#^Strict comparison using \!\=\= between array\<string, string\> and null will always evaluate to true\.$#'
identifier: notIdentical.alwaysTrue
@@ -1608,12 +1500,6 @@ parameters:
count: 1
path: src/Mapping/QuoteStrategy.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ReflectionEnumProperty\:\:getValue\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Mapping/ReflectionEnumProperty.php
-
message: '#^Method Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\:\:fromMappingArray\(\) should return static\(Doctrine\\ORM\\Mapping\\ToOneOwningSideMapping\) but returns Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\.$#'
identifier: return.type
@@ -1687,7 +1573,7 @@ parameters:
path: src/PersistentCollection.php
-
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:matching\(\) should return Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\> but returns Doctrine\\Common\\Collections\\ReadableCollection\<TKey of \(int\|string\), T\>&Doctrine\\Common\\Collections\\Selectable\<TKey of \(int\|string\), T\>\.$#'
message: '#^Method Doctrine\\ORM\\PersistentCollection\:\:matching\(\) should return Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\> but returns Doctrine\\Common\\Collections\\ReadableCollection\<TKey of \(int\|string\), T\>\.$#'
identifier: return.type
count: 1
path: src/PersistentCollection.php
@@ -1699,7 +1585,7 @@ parameters:
path: src/PersistentCollection.php
-
message: '#^Parameter \#2 \$callback of function array_walk expects callable\(object, int\)\: mixed, array\{Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\>&Doctrine\\Common\\Collections\\Selectable\<TKey of \(int\|string\), T\>, ''add''\} given\.$#'
message: '#^Parameter \#2 \$callback of function array_walk expects callable\(object, int\)\: mixed, array\{Doctrine\\Common\\Collections\\Collection\<TKey of \(int\|string\), T\>, ''add''\} given\.$#'
identifier: argument.type
count: 1
path: src/PersistentCollection.php
@@ -1824,6 +1710,12 @@ parameters:
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:count\(\) should return int\<0, max\> but returns int\.$#'
identifier: return.type
count: 1
path: src/Persisters/Collection/ManyToManyPersister.php
-
message: '#^Method Doctrine\\ORM\\Persisters\\Collection\\ManyToManyPersister\:\:delete\(\) has parameter \$collection with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
identifier: missingType.generics
@@ -2112,12 +2004,6 @@ parameters:
count: 1
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#2 \$lockMode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:appendLockHint\(\) expects Doctrine\\DBAL\\LockMode, Doctrine\\DBAL\\LockMode\:\:NONE\|Doctrine\\DBAL\\LockMode\:\:OPTIMISTIC\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_READ\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_WRITE\|int given\.$#'
identifier: argument.type
count: 2
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '#^Parameter \#3 \$hints of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:hydrateAll\(\) expects array\<string, string\>, array\<string, Doctrine\\ORM\\PersistentCollection\|true\> given\.$#'
identifier: argument.type
@@ -2190,12 +2076,6 @@ parameters:
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Parameter \#2 \$lockMode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:appendLockHint\(\) expects Doctrine\\DBAL\\LockMode, Doctrine\\DBAL\\LockMode\:\:NONE\|Doctrine\\DBAL\\LockMode\:\:OPTIMISTIC\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_READ\|Doctrine\\DBAL\\LockMode\:\:PESSIMISTIC_WRITE\|int given\.$#'
identifier: argument.type
count: 1
path: src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
@@ -2220,156 +2100,6 @@ parameters:
count: 1
path: src/Persisters/SqlValueVisitor.php
-
message: '#^Parameter \#3 \$className of static method Doctrine\\ORM\\Proxy\\Autoloader\:\:resolveFile\(\) expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/Autoloader.php
-
message: '#^Parameter \#3 of closure expects class\-string, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/Autoloader.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\<T of object\> but returns class\-string\<Doctrine\\Persistence\\Proxy\<T of object\>\>\|class\-string\<T of object\>\.$#'
identifier: return.type
count: 1
path: src/Proxy/DefaultProxyClassNameResolver.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\DefaultProxyClassNameResolver\:\:resolveClassName\(\) should return class\-string\<T of object\> but returns string\.$#'
identifier: return.type
count: 1
path: src/Proxy/DefaultProxyClassNameResolver.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$isEmbeddedClass\.$#'
identifier: property.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$isMappedSuperclass\.$#'
identifier: property.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Call to an undefined static method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyGhost\(\)\.$#'
identifier: staticMethod.notFound
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Call to function is_bool\(\) with bool will always evaluate to true\.$#'
identifier: function.alreadyNarrowedType
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Comparison operation "\<" between 0\|1\|2\|3\|4 and 0 is always false\.$#'
identifier: smaller.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Comparison operation "\>" between 0\|1\|2\|3\|4 and 4 is always false\.$#'
identifier: greater.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) has Doctrine\\ORM\\EntityNotFoundException in PHPDoc @throws tag but it''s not thrown\.$#'
identifier: throws.unusedType
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) has parameter \$classMetadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:createLazyInitializer\(\) return type with generic interface Doctrine\\ORM\\Proxy\\InternalProxy does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClass\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClasses\(\) has parameter \$classes with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateSerializeImpl\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateUseLazyGhostTrait\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:loadProxyClass\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:skipClass\(\) has parameter \$metadata with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#1 \$class of method Doctrine\\ORM\\Utility\\IdentifierFlattener\:\:flattenIdentifier\(\) expects Doctrine\\ORM\\Mapping\\ClassMetadata, Doctrine\\Persistence\\Mapping\\ClassMetadata given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#1 \$filename of function filemtime expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#3 \$length of function substr expects int\|null, int\|false given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Parameter \#3 \$newScope of static method Closure\:\:bind\(\) expects ''static''\|class\-string\|object\|null, string given\.$#'
identifier: argument.type
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Result of \|\| is always false\.$#'
identifier: booleanOr.alwaysFalse
count: 1
path: src/Proxy/ProxyFactory.php
-
message: '#^Method Doctrine\\ORM\\Query\:\:processParameterMappings\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
@@ -2490,12 +2220,6 @@ parameters:
count: 1
path: src/Query/AST/Functions/SizeFunction.php
-
message: '#^Parameter \#2 \$mode of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getTrimExpression\(\) expects Doctrine\\DBAL\\Platforms\\TrimMode, Doctrine\\DBAL\\Platforms\\TrimMode\:\:BOTH\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:LEADING\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:TRAILING\|Doctrine\\DBAL\\Platforms\\TrimMode\:\:UNSPECIFIED\|int given\.$#'
identifier: argument.type
count: 2
path: src/Query/AST/Functions/TrimFunction.php
-
message: '#^Call to an undefined method Doctrine\\ORM\\Query\\SqlWalker\:\:walkJoinPathExpression\(\)\.$#'
identifier: method.notFound
@@ -2562,12 +2286,6 @@ parameters:
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\<int\<0, max\>\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, list\<Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|int\|string\> given\.$#'
identifier: argument.type
count: 1
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '#^Parameter \#1 \$sql of method Doctrine\\DBAL\\Connection\:\:executeQuery\(\) expects string, list\<string\>\|string given\.$#'
identifier: argument.type
@@ -2586,42 +2304,12 @@ parameters:
count: 1
path: src/Query/Exec/SingleTableDeleteUpdateExecutor.php
-
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Andx\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
identifier: property.phpDocType
count: 1
path: src/Query/Expr/Andx.php
-
message: '#^Method Doctrine\\ORM\\Query\\Expr\\Func\:\:getArguments\(\) should return list\<mixed\> but returns array\<mixed\>\.$#'
identifier: return.type
count: 1
path: src/Query/Expr/Func.php
-
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Orx\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
identifier: property.phpDocType
count: 1
path: src/Query/Expr/Orx.php
-
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Select\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
identifier: property.phpDocType
count: 1
path: src/Query/Expr/Select.php
-
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\: Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|int\|string, is_list\: bool\}\>\.$#'
identifier: assign.propertyType
count: 2
path: src/Query/Filter/SQLFilter.php
-
message: '#^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns int so it can be removed from the return type\.$#'
identifier: return.unusedType
count: 1
path: src/Query/ParameterTypeInferer.php
-
message: '#^@readonly property cannot have a default value\.$#'
identifier: property.readOnlyByPhpDocDefaultValue
@@ -2856,6 +2544,12 @@ parameters:
count: 2
path: src/QueryBuilder.php
-
message: '#^Parameter \#3 \$type of method Doctrine\\ORM\\QueryBuilder\:\:setParameter\(\) expects Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|string\|null, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|int\|string\|null given\.$#'
identifier: argument.type
count: 1
path: src/QueryBuilder.php
-
message: '#^Method Doctrine\\ORM\\Repository\\DefaultRepositoryFactory\:\:createRepository\(\) return type with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
identifier: missingType.generics
@@ -2868,24 +2562,6 @@ parameters:
count: 1
path: src/Repository/DefaultRepositoryFactory.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#1 \$filename of function file_exists expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#1 \$filename of function is_writable expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
@@ -3351,6 +3027,12 @@ parameters:
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\AssociationMapping\:\:\$joinColumns\.$#'
identifier: property.notFound
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$inversedBy\.$#'
identifier: property.notFound
@@ -3366,7 +3048,7 @@ parameters:
-
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
identifier: property.notFound
count: 1
count: 3
path: src/UnitOfWork.php
-
@@ -3453,12 +3135,6 @@ parameters:
count: 2
path: src/UnitOfWork.php
-
message: '#^Parameter \#3 \$collection of class Doctrine\\ORM\\PersistentCollection constructor expects Doctrine\\Common\\Collections\\Collection\<\(int\|string\), mixed\>&Doctrine\\Common\\Collections\\Selectable\<\(int\|string\), mixed\>, Doctrine\\Common\\Collections\\Collection given\.$#'
identifier: argument.type
count: 1
path: src/UnitOfWork.php
-
message: '#^Parameter \#5 \$invoke of method Doctrine\\ORM\\Event\\ListenersInvoker\:\:invoke\(\) expects int\<0, 7\>, int\<min, \-1\>\|int\<1, max\> given\.$#'
identifier: argument.type
@@ -3477,12 +3153,6 @@ parameters:
count: 1
path: src/UnitOfWork.php
-
message: '#^Unable to resolve the template type T in call to method static method Symfony\\Component\\VarExporter\\Hydrator\:\:hydrate\(\)$#'
identifier: argument.templateType
count: 1
path: src/UnitOfWork.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
@@ -3572,9 +3242,3 @@ parameters:
identifier: missingType.generics
count: 1
path: src/Utility/PersisterHelper.php
-
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) should return list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\> but returns list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|int\>\.$#'
identifier: return.type
count: 1
path: src/Utility/PersisterHelper.php
-151
View File
@@ -1,151 +0,0 @@
includes:
- phpstan-baseline.neon
- phpstan-params.neon
parameters:
reportUnmatchedIgnoredErrors: false # Some errors in the baseline only apply to DBAL 4
ignoreErrors:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
# We can be certain that those values are not matched.
-
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~.*getTrimExpression.*expects int.*~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Call to static method unquoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Instantiated class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName not found\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to static method quoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
path: src/Tools/SchemaTool.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getObjectName\(\)\.$~'
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint::get.*\.$~'
identifier: method.notFound
-
message: '~createComparator~'
identifier: arguments.count
-
message: '~UnqualifiedName~'
identifier: class.notFound
-
message: '~IndexedColumn~'
identifier: class.notFound
-
message: '~PrimaryKeyConstraint~'
identifier: class.notFound
-
message: '~IndexType~'
identifier: class.notFound
-
message: '~dropForeignKey~'
identifier: method.notFound
-
message: '~getIndexedColumns~'
identifier: method.notFound
-
message: '~getPrimaryKeyConstraint~'
identifier: method.notFound
-
message: '~PrimaryKeyConstraint~'
identifier: class.notFound
path: src/Tools/SchemaTool.php
-
message: '~^Call to method toString.*UnqualifiedName\.$~'
path: src/Tools/SchemaTool.php
- '~^Call to method getObjectName\(\) on an unknown class Doctrine\\DBAL\\Schema\\NamedObject\.$~'
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
- '~^Class Doctrine\\DBAL\\Schema\\NamedObject not found\.$~'
-
message: '~sort~'
identifier: argument.unresolvableType
path: src/Mapping/Driver/DatabaseDriver.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php
-
message: '~^Call to deprecated method getEventManager\(\) of class Doctrine\\DBAL\\Connection\.$~'
path: src/EntityManager.php
-
message: '~deprecated class Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand\:~'
path: src/Tools/Console/ConsoleRunner.php
# Compatibility with Persistence 3
-
message: '#Expression on left side of \?\? is not nullable.#'
path: src/Mapping/Driver/AttributeDriver.php
-
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Utility/PersisterHelper.php
-
message: '~inferParameterTypes.*should return~'
path: src/Utility/PersisterHelper.php
-
message: '~.*appendLockHint.*expects.*LockMode given~'
paths:
- src/Persisters/Entity/BasicEntityPersister.php
- src/Persisters/Entity/JoinedSubclassPersister.php
-
message: '~.*executeStatement.*expects~'
path: src/Query/Exec/MultiTableUpdateExecutor.php
-
message: '~method_exists.*getEventManager~'
path: src/EntityManager.php
-
message: '~method_exists.*getIdentitySequence~'
path: src/Mapping/ClassMetadataFactory.php
-
message: '~expand(Criteria)?Parameters.*should return array~'
path: src/Persisters/Entity/BasicEntityPersister.php
-
message: '~inferType.*never returns~'
path: src/Query/ParameterTypeInferer.php
+2 -39
View File
@@ -3,6 +3,8 @@ includes:
- phpstan-params.neon
parameters:
checkMissingOverrideMethodAttribute: true
ignoreErrors:
# Symfony cache supports passing a key prefix to the clear method.
- '/^Method Psr\\Cache\\CacheItemPoolInterface\:\:clear\(\) invoked with 1 parameter, 0 required\.$/'
@@ -11,42 +13,3 @@ parameters:
-
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# DBAL 4 compatibility
-
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Query/AST/Functions/TrimFunction.php
-
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
path: src/Utility/PersisterHelper.php
# Compatibility with DBAL 3
# See https://github.com/doctrine/dbal/pull/3480
-
message: '~^Result of method Doctrine\\DBAL\\Connection::commit\(\) \(void\) is used\.$~'
path: src/UnitOfWork.php
-
message: '~^Strict comparison using === between null and false will always evaluate to false\.$~'
path: src/UnitOfWork.php
-
message: '~^Variable \$e on left side of \?\? always exists and is not nullable\.$~'
path: src/UnitOfWork.php
-
message: '~^Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner::addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
path: src/Tools/Console/ConsoleRunner.php
-
message: '~Strict comparison using \=\=\= between callable\(\)\: mixed and null will always evaluate to false\.~'
path: src/Tools/SchemaTool.php
# To be removed in 4.0
-
message: '#Negated boolean expression is always false\.#'
paths:
- src/Mapping/Driver/AttributeDriver.php
# Compatibility with Persistence 3
-
message: '#Expression on left side of \?\? is not nullable.#'
path: src/Mapping/Driver/AttributeDriver.php
+15 -16
View File
@@ -16,7 +16,6 @@ use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\Mapping\MappingException;
@@ -51,32 +50,32 @@ abstract class AbstractQuery
/**
* Hydrates an object graph. This is the default behavior.
*/
public const HYDRATE_OBJECT = 1;
final public const int HYDRATE_OBJECT = 1;
/**
* Hydrates an array graph.
*/
public const HYDRATE_ARRAY = 2;
final public const int HYDRATE_ARRAY = 2;
/**
* Hydrates a flat, rectangular result set with scalar values.
*/
public const HYDRATE_SCALAR = 3;
final public const int HYDRATE_SCALAR = 3;
/**
* Hydrates a single scalar value.
*/
public const HYDRATE_SINGLE_SCALAR = 4;
final public const int HYDRATE_SINGLE_SCALAR = 4;
/**
* Very simple object hydrator (optimized for performance).
*/
public const HYDRATE_SIMPLEOBJECT = 5;
final public const int HYDRATE_SIMPLEOBJECT = 5;
/**
* Hydrates scalar column value.
*/
public const HYDRATE_SCALAR_COLUMN = 6;
final public const int HYDRATE_SCALAR_COLUMN = 6;
/**
* The parameter map of this query.
@@ -320,16 +319,16 @@ abstract class AbstractQuery
/**
* Sets a query parameter.
*
* @param string|int $key The parameter position or name.
* @param mixed $value The parameter value.
* @param ParameterType|ArrayParameterType|string|int|null $type The parameter type. If specified, the given value
* will be run through the type conversion of this
* type. This is usually not needed for strings and
* numeric types.
* @param string|int $key The parameter position or name.
* @param mixed $value The parameter value.
* @param ParameterType|ArrayParameterType|string|null $type The parameter type. If specified, the given value
* will be run through the type conversion of this
* type. This is usually not needed for strings and
* numeric types.
*
* @return $this
*/
public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static
public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|null $type = null): static
{
$existingParameter = $this->getParameter($key);
@@ -378,7 +377,7 @@ abstract class AbstractQuery
}
try {
$class = DefaultProxyClassNameResolver::getClass($value);
$class = $value::class;
$value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value);
if ($value === null) {
@@ -1065,7 +1064,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
+6 -6
View File
@@ -12,31 +12,31 @@ use Doctrine\ORM\Cache\Region;
*/
interface Cache
{
public const DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
final public const string DEFAULT_QUERY_REGION_NAME = 'query_cache_region';
public const DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
final public const string DEFAULT_TIMESTAMP_REGION_NAME = 'timestamp_cache_region';
/**
* May read items from the cache, but will not add items.
*/
public const MODE_GET = 1;
final public const int MODE_GET = 1;
/**
* Will never read items from the cache,
* but will add items to the cache as it reads them from the database.
*/
public const MODE_PUT = 2;
final public const int MODE_PUT = 2;
/**
* May read items from the cache, and add items to the cache.
*/
public const MODE_NORMAL = 3;
final public const int MODE_NORMAL = 3;
/**
* The query will never read items from the cache,
* but will refresh items to the cache as it reads them from the database.
*/
public const MODE_REFRESH = 4;
final public const int MODE_REFRESH = 4;
public function getEntityCacheRegion(string $className): Region|null;
+16 -2
View File
@@ -9,8 +9,8 @@ use Doctrine\ORM\Cache\Persister\CachedPersister;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\ORMInvalidArgumentException;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\UnitOfWork;
use Override;
use function is_array;
use function is_object;
@@ -40,6 +40,7 @@ class DefaultCache implements Cache
->getCacheFactory();
}
#[Override]
public function getEntityCacheRegion(string $className): Region|null
{
$metadata = $this->em->getClassMetadata($className);
@@ -52,6 +53,7 @@ class DefaultCache implements Cache
return $persister->getCacheRegion();
}
#[Override]
public function getCollectionCacheRegion(string $className, string $association): Region|null
{
$metadata = $this->em->getClassMetadata($className);
@@ -64,6 +66,7 @@ class DefaultCache implements Cache
return $persister->getCacheRegion();
}
#[Override]
public function containsEntity(string $className, mixed $identifier): bool
{
$metadata = $this->em->getClassMetadata($className);
@@ -76,6 +79,7 @@ class DefaultCache implements Cache
return $persister->getCacheRegion()->contains($this->buildEntityCacheKey($metadata, $identifier));
}
#[Override]
public function evictEntity(string $className, mixed $identifier): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -88,6 +92,7 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evict($this->buildEntityCacheKey($metadata, $identifier));
}
#[Override]
public function evictEntityRegion(string $className): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -100,6 +105,7 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evictAll();
}
#[Override]
public function evictEntityRegions(): void
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
@@ -115,6 +121,7 @@ class DefaultCache implements Cache
}
}
#[Override]
public function containsCollection(string $className, string $association, mixed $ownerIdentifier): bool
{
$metadata = $this->em->getClassMetadata($className);
@@ -127,6 +134,7 @@ class DefaultCache implements Cache
return $persister->getCacheRegion()->contains($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
#[Override]
public function evictCollection(string $className, string $association, mixed $ownerIdentifier): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -139,6 +147,7 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evict($this->buildCollectionCacheKey($metadata, $association, $ownerIdentifier));
}
#[Override]
public function evictCollectionRegion(string $className, string $association): void
{
$metadata = $this->em->getClassMetadata($className);
@@ -151,6 +160,7 @@ class DefaultCache implements Cache
$persister->getCacheRegion()->evictAll();
}
#[Override]
public function evictCollectionRegions(): void
{
$metadatas = $this->em->getMetadataFactory()->getAllMetadata();
@@ -172,11 +182,13 @@ class DefaultCache implements Cache
}
}
#[Override]
public function containsQuery(string $regionName): bool
{
return isset($this->queryCaches[$regionName]);
}
#[Override]
public function evictQueryRegion(string|null $regionName = null): void
{
if ($regionName === null && $this->defaultQueryCache !== null) {
@@ -190,6 +202,7 @@ class DefaultCache implements Cache
}
}
#[Override]
public function evictQueryRegions(): void
{
$this->getQueryCache()->clear();
@@ -199,6 +212,7 @@ class DefaultCache implements Cache
}
}
#[Override]
public function getQueryCache(string|null $regionName = null): QueryCache
{
if ($regionName === null) {
@@ -233,7 +247,7 @@ class DefaultCache implements Cache
private function toIdentifierArray(ClassMetadata $metadata, mixed $identifier): array
{
if (is_object($identifier)) {
$class = DefaultProxyClassNameResolver::getClass($identifier);
$class = $identifier::class;
if ($this->em->getMetadataFactory()->hasMetadataFor($class)) {
$identifier = $this->uow->getSingleIdentifierValue($identifier)
?? throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
+9
View File
@@ -23,6 +23,7 @@ use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use InvalidArgumentException;
use LogicException;
use Override;
use Psr\Cache\CacheItemPoolInterface;
use function assert;
@@ -63,6 +64,7 @@ class DefaultCacheFactory implements CacheFactory
$this->timestampRegion = $region;
}
#[Override]
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata): CachedEntityPersister
{
assert($metadata->cache !== null);
@@ -88,6 +90,7 @@ class DefaultCacheFactory implements CacheFactory
throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
}
#[Override]
public function buildCachedCollectionPersister(
EntityManagerInterface $em,
CollectionPersister $persister,
@@ -116,6 +119,7 @@ class DefaultCacheFactory implements CacheFactory
throw new InvalidArgumentException(sprintf('Unrecognized access strategy type [%s]', $usage));
}
#[Override]
public function buildQueryCache(EntityManagerInterface $em, string|null $regionName = null): QueryCache
{
return new DefaultQueryCache(
@@ -129,11 +133,13 @@ class DefaultCacheFactory implements CacheFactory
);
}
#[Override]
public function buildCollectionHydrator(EntityManagerInterface $em, AssociationMapping $mapping): CollectionHydrator
{
return new DefaultCollectionHydrator($em);
}
#[Override]
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata): EntityHydrator
{
return new DefaultEntityHydrator($em);
@@ -142,6 +148,7 @@ class DefaultCacheFactory implements CacheFactory
/**
* {@inheritDoc}
*/
#[Override]
public function getRegion(array $cache): Region
{
if (isset($this->regions[$cache['region']])) {
@@ -170,6 +177,7 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']] = $region;
}
#[Override]
public function getTimestampRegion(): TimestampRegion
{
if ($this->timestampRegion === null) {
@@ -182,6 +190,7 @@ class DefaultCacheFactory implements CacheFactory
return $this->timestampRegion;
}
#[Override]
public function createCache(EntityManagerInterface $entityManager): Cache
{
return new DefaultCache($entityManager);
+3
View File
@@ -11,6 +11,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Override;
use function assert;
@@ -30,6 +31,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
$this->uow = $em->getUnitOfWork();
}
#[Override]
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, array|Collection $collection): CollectionCacheEntry
{
$data = [];
@@ -41,6 +43,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
return new CollectionCacheEntry($data);
}
#[Override]
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection): array|null
{
$assoc = $metadata->associationMappings[$key->association];
+4 -2
View File
@@ -6,10 +6,10 @@ namespace Doctrine\ORM\Cache;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Utility\IdentifierFlattener;
use Override;
use function assert;
use function is_array;
@@ -34,6 +34,7 @@ class DefaultEntityHydrator implements EntityHydrator
$this->identifierFlattener = new IdentifierFlattener($em->getUnitOfWork(), $em->getMetadataFactory());
}
#[Override]
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, object $entity): EntityCacheEntry
{
$data = $this->uow->getOriginalEntityData($entity);
@@ -97,7 +98,7 @@ class DefaultEntityHydrator implements EntityHydrator
}
if (! isset($assoc->id)) {
$targetClass = DefaultProxyClassNameResolver::getClass($data[$name]);
$targetClass = $data[$name]::class;
$targetId = $this->uow->getEntityIdentifier($data[$name]);
$data[$name] = new AssociationCacheEntry($targetClass, $targetId);
@@ -125,6 +126,7 @@ class DefaultEntityHydrator implements EntityHydrator
return new EntityCacheEntry($metadata->name, $data);
}
#[Override]
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, object|null $entity = null): object|null
{
$data = $entry->data;
+5
View File
@@ -18,6 +18,7 @@ use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_map;
use function array_shift;
@@ -54,6 +55,7 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritDoc}
*/
#[Override]
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []): array|null
{
if (! ($key->cacheMode & Cache::MODE_GET)) {
@@ -197,6 +199,7 @@ class DefaultQueryCache implements QueryCache
/**
* {@inheritDoc}
*/
#[Override]
public function put(QueryCacheKey $key, ResultSetMapping $rsm, mixed $result, array $hints = []): bool
{
if ($rsm->scalarMappings) {
@@ -406,11 +409,13 @@ class DefaultQueryCache implements QueryCache
return $values;
}
#[Override]
public function clear(): bool
{
return $this->region->evictAll();
}
#[Override]
public function getRegion(): Region
{
return $this->region;
+10
View File
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Override;
class CacheLoggerChain implements CacheLogger
{
@@ -29,6 +30,7 @@ class CacheLoggerChain implements CacheLogger
return $this->loggers;
}
#[Override]
public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -36,6 +38,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -43,6 +46,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function collectionCachePut(string $regionName, CollectionCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -50,6 +54,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function entityCacheHit(string $regionName, EntityCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -57,6 +62,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function entityCacheMiss(string $regionName, EntityCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -64,6 +70,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function entityCachePut(string $regionName, EntityCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -71,6 +78,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function queryCacheHit(string $regionName, QueryCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -78,6 +86,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function queryCacheMiss(string $regionName, QueryCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -85,6 +94,7 @@ class CacheLoggerChain implements CacheLogger
}
}
#[Override]
public function queryCachePut(string $regionName, QueryCacheKey $key): void
{
foreach ($this->loggers as $logger) {
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Cache\Logging;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\Cache\QueryCacheKey;
use Override;
use function array_sum;
@@ -24,54 +25,63 @@ class StatisticsCacheLogger implements CacheLogger
/** @var array<string, int> */
private array $cachePutCountMap = [];
#[Override]
public function collectionCacheMiss(string $regionName, CollectionCacheKey $key): void
{
$this->cacheMissCountMap[$regionName]
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function collectionCacheHit(string $regionName, CollectionCacheKey $key): void
{
$this->cacheHitCountMap[$regionName]
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function collectionCachePut(string $regionName, CollectionCacheKey $key): void
{
$this->cachePutCountMap[$regionName]
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function entityCacheMiss(string $regionName, EntityCacheKey $key): void
{
$this->cacheMissCountMap[$regionName]
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function entityCacheHit(string $regionName, EntityCacheKey $key): void
{
$this->cacheHitCountMap[$regionName]
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function entityCachePut(string $regionName, EntityCacheKey $key): void
{
$this->cachePutCountMap[$regionName]
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function queryCacheHit(string $regionName, QueryCacheKey $key): void
{
$this->cacheHitCountMap[$regionName]
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function queryCacheMiss(string $regionName, QueryCacheKey $key): void
{
$this->cacheMissCountMap[$regionName]
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
}
#[Override]
public function queryCachePut(string $regionName, QueryCacheKey $key): void
{
$this->cachePutCountMap[$regionName]
@@ -17,9 +17,9 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_values;
use function assert;
@@ -63,21 +63,25 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
$this->targetEntity = $em->getClassMetadata($association->targetEntity);
}
#[Override]
public function getCacheRegion(): Region
{
return $this->region;
}
#[Override]
public function getSourceEntityMetadata(): ClassMetadata
{
return $this->sourceEntity;
}
#[Override]
public function getTargetEntityMetadata(): ClassMetadata
{
return $this->targetEntity;
}
#[Override]
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key): array|null
{
$cache = $this->region->get($key);
@@ -89,6 +93,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
return $this->hydrator->loadCacheEntry($this->sourceEntity, $key, $cache, $collection);
}
#[Override]
public function storeCollectionCache(CollectionCacheKey $key, Collection|array $elements): void
{
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
@@ -111,7 +116,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
$class = $this->targetEntity;
$className = DefaultProxyClassNameResolver::getClass($elements[$index]);
$className = $elements[$index]::class;
if ($className !== $this->targetEntity->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -128,16 +133,19 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
}
#[Override]
public function contains(PersistentCollection $collection, object $element): bool
{
return $this->persister->contains($collection, $element);
}
#[Override]
public function containsKey(PersistentCollection $collection, mixed $key): bool
{
return $this->persister->containsKey($collection, $key);
}
#[Override]
public function count(PersistentCollection $collection): int
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
@@ -151,6 +159,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
return $this->persister->count($collection);
}
#[Override]
public function get(PersistentCollection $collection, mixed $index): mixed
{
return $this->persister->get($collection, $index);
@@ -159,6 +168,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* {@inheritDoc}
*/
#[Override]
public function slice(PersistentCollection $collection, int $offset, int|null $length = null): array
{
return $this->persister->slice($collection, $offset, $length);
@@ -167,6 +177,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadCriteria(PersistentCollection $collection, Criteria $criteria): array
{
return $this->persister->loadCriteria($collection, $criteria);
@@ -6,11 +6,13 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\PersistentCollection;
use Override;
use function spl_object_id;
class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
#[Override]
public function afterTransactionComplete(): void
{
if (isset($this->queuedCache['update'])) {
@@ -28,11 +30,13 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
$this->queuedCache = [];
}
#[Override]
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
@@ -43,6 +47,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
$this->queuedCache['delete'][spl_object_id($collection)] = $key;
}
#[Override]
public function update(PersistentCollection $collection): void
{
$isInitialized = $collection->isInitialized();
@@ -6,15 +6,16 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyCollection;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Override;
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
{
#[Override]
public function update(PersistentCollection $collection): void
{
if ($collection->isDirty() && $collection->getSnapshot()) {
throw CannotUpdateReadOnlyCollection::fromEntityAndField(
DefaultProxyClassNameResolver::getClass($collection->getOwner()),
$collection->getOwner()::class,
$this->association->fieldName,
);
}
@@ -10,6 +10,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Override;
use function spl_object_id;
@@ -24,6 +25,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
parent::__construct($persister, $region, $em, $association);
}
#[Override]
public function afterTransactionComplete(): void
{
if (isset($this->queuedCache['update'])) {
@@ -41,6 +43,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
if (isset($this->queuedCache['update'])) {
@@ -58,6 +61,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
$this->queuedCache = [];
}
#[Override]
public function delete(PersistentCollection $collection): void
{
$ownerId = $this->uow->getEntityIdentifier($collection->getOwner());
@@ -76,6 +80,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
];
}
#[Override]
public function update(PersistentCollection $collection): void
{
$isInitialized = $collection->isInitialized();
@@ -23,13 +23,12 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_merge;
use function func_get_args;
use function serialize;
use function sha1;
@@ -77,6 +76,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
}
#[Override]
public function addInsert(object $entity): void
{
$this->persister->addInsert($entity);
@@ -85,15 +85,17 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function getInserts(): array
{
return $this->persister->getInserts();
}
#[Override]
public function getSelectSQL(
array|Criteria $criteria,
AssociationMapping|null $assoc = null,
LockMode|int|null $lockMode = null,
LockMode|null $lockMode = null,
int|null $limit = null,
int|null $offset = null,
array|null $orderBy = null,
@@ -101,21 +103,25 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->getSelectSQL($criteria, $assoc, $lockMode, $limit, $offset, $orderBy);
}
#[Override]
public function getCountSQL(array|Criteria $criteria = []): string
{
return $this->persister->getCountSQL($criteria);
}
#[Override]
public function getInsertSQL(): string
{
return $this->persister->getInsertSQL();
}
#[Override]
public function getResultSetMapping(): ResultSetMapping
{
return $this->persister->getResultSetMapping();
}
#[Override]
public function getSelectConditionStatementSQL(
string $field,
mixed $value,
@@ -125,6 +131,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->getSelectConditionStatementSQL($field, $value, $assoc, $comparison);
}
#[Override]
public function exists(object $entity, Criteria|null $extraConditions = null): bool
{
if ($extraConditions === null) {
@@ -138,20 +145,23 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->exists($entity, $extraConditions);
}
#[Override]
public function getCacheRegion(): Region
{
return $this->region;
}
#[Override]
public function getEntityHydrator(): EntityHydrator
{
return $this->hydrator;
}
#[Override]
public function storeEntityCache(object $entity, EntityCacheKey $key): bool
{
$class = $this->class;
$className = DefaultProxyClassNameResolver::getClass($entity);
$className = $entity::class;
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -225,6 +235,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function expandParameters(array $criteria): array
{
return $this->persister->expandParameters($criteria);
@@ -233,11 +244,13 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function expandCriteriaParameters(Criteria $criteria): array
{
return $this->persister->expandCriteriaParameters($criteria);
}
#[Override]
public function getClassMetadata(): ClassMetadata
{
return $this->persister->getClassMetadata();
@@ -246,6 +259,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function getManyToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -258,6 +272,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function getOneToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -267,11 +282,13 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $this->persister->getOneToManyCollection($assoc, $sourceEntity, $offset, $limit);
}
#[Override]
public function getOwningTable(string $fieldName): string
{
return $this->persister->getOwningTable($fieldName);
}
#[Override]
public function executeInserts(): void
{
// The commit order/foreign key relationships may make it necessary that multiple calls to executeInsert()
@@ -288,12 +305,13 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function load(
array $criteria,
object|null $entity = null,
AssociationMapping|null $assoc = null,
array $hints = [],
LockMode|int|null $lockMode = null,
LockMode|null $lockMode = null,
int|null $limit = null,
array|null $orderBy = null,
): object|null {
@@ -335,6 +353,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadAll(
array $criteria = [],
array|null $orderBy = null,
@@ -371,6 +390,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadById(array $identifier, object|null $entity = null): object|null
{
$cacheKey = new EntityCacheKey($this->class->rootEntityName, $identifier);
@@ -398,7 +418,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
$class = $this->class;
$className = DefaultProxyClassNameResolver::getClass($entity);
$className = $entity::class;
if ($className !== $this->class->name) {
$class = $this->metadataFactory->getMetadataFor($className);
@@ -420,6 +440,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $entity;
}
#[Override]
public function count(array|Criteria $criteria = []): int
{
return $this->persister->count($criteria);
@@ -428,6 +449,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadCriteria(Criteria $criteria): array
{
$orderBy = $criteria->orderings();
@@ -463,6 +485,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadManyToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -494,6 +517,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
return $list;
}
#[Override]
public function loadOneToManyCollection(
AssociationMapping $assoc,
object $sourceEntity,
@@ -528,6 +552,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
#[Override]
public function loadOneToOneEntity(AssociationMapping $assoc, object $sourceEntity, array $identifier = []): object|null
{
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
@@ -536,7 +561,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
public function lock(array $criteria, LockMode|int $lockMode): void
#[Override]
public function lock(array $criteria, LockMode $lockMode): void
{
$this->persister->lock($criteria, $lockMode);
}
@@ -544,16 +570,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* {@inheritDoc}
*/
public function refresh(array $id, object $entity, LockMode|int|null $lockMode = null): void
#[Override]
public function refresh(array $id, object $entity, LockMode|null $lockMode = null): void
{
$this->persister->refresh($id, $entity, $lockMode);
}
/** @param array<string, mixed> $ownerId */
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, /* string $filterHash */): CollectionCacheKey
protected function buildCollectionCacheKey(AssociationMapping $association, array $ownerId, string $filterHash): CollectionCacheKey
{
$filterHash = (string) (func_get_args()[2] ?? ''); // todo: move to argument in next major release
return new CollectionCacheKey(
$this->metadataFactory->getMetadataFor($association->sourceEntity)->rootEntityName,
$association->fieldName,
@@ -5,12 +5,14 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\EntityCacheKey;
use Override;
/**
* Specific non-strict read/write cached entity persister
*/
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
{
#[Override]
public function afterTransactionComplete(): void
{
$isChanged = false;
@@ -42,11 +44,13 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
$this->queuedCache = [];
}
#[Override]
public function delete(object $entity): bool
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
@@ -61,6 +65,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
return $deleted;
}
#[Override]
public function update(object $entity): void
{
$this->persister->update($entity);
@@ -5,15 +5,16 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Override;
/**
* Specific read-only region entity persister
*/
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
{
#[Override]
public function update(object $entity): void
{
throw CannotUpdateReadOnlyEntity::fromEntity(DefaultProxyClassNameResolver::getClass($entity));
throw CannotUpdateReadOnlyEntity::fromEntity($entity::class);
}
}
@@ -9,6 +9,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Override;
/**
* Specific read-write entity persister
@@ -20,6 +21,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
parent::__construct($persister, $region, $em, $class);
}
#[Override]
public function afterTransactionComplete(): void
{
$isChanged = true;
@@ -47,6 +49,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache = [];
}
#[Override]
public function afterTransactionRolledBack(): void
{
if (isset($this->queuedCache['update'])) {
@@ -64,6 +67,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
$this->queuedCache = [];
}
#[Override]
public function delete(object $entity): bool
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
@@ -86,6 +90,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
return $deleted;
}
#[Override]
public function update(object $entity): void
{
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
+10 -2
View File
@@ -9,6 +9,7 @@ use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\CollectionCacheEntry;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use Override;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Traversable;
@@ -22,8 +23,8 @@ use function strtr;
*/
class DefaultRegion implements Region
{
private const REGION_KEY_SEPARATOR = '_';
private const REGION_PREFIX = 'DC2_REGION_';
private const string REGION_KEY_SEPARATOR = '_';
private const string REGION_PREFIX = 'DC2_REGION_';
public function __construct(
private readonly string $name,
@@ -32,16 +33,19 @@ class DefaultRegion implements Region
) {
}
#[Override]
public function getName(): string
{
return $this->name;
}
#[Override]
public function contains(CacheKey $key): bool
{
return $this->cacheItemPool->hasItem($this->getCacheEntryKey($key));
}
#[Override]
public function get(CacheKey $key): CacheEntry|null
{
$item = $this->cacheItemPool->getItem($this->getCacheEntryKey($key));
@@ -54,6 +58,7 @@ class DefaultRegion implements Region
return $entry;
}
#[Override]
public function getMultiple(CollectionCacheEntry $collection): array|null
{
$keys = array_map(
@@ -83,6 +88,7 @@ class DefaultRegion implements Region
return $result;
}
#[Override]
public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool
{
$item = $this->cacheItemPool
@@ -96,11 +102,13 @@ class DefaultRegion implements Region
return $this->cacheItemPool->save($item);
}
#[Override]
public function evict(CacheKey $key): bool
{
return $this->cacheItemPool->deleteItem($this->getCacheEntryKey($key));
}
#[Override]
public function evictAll(): bool
{
return $this->cacheItemPool->clear(self::REGION_PREFIX . $this->name);
+11 -1
View File
@@ -11,6 +11,7 @@ use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use InvalidArgumentException;
use Override;
use function array_filter;
use function array_map;
@@ -35,7 +36,7 @@ use const LOCK_EX;
*/
class FileLockRegion implements ConcurrentRegion
{
final public const LOCK_EXTENSION = 'lock';
final public const string LOCK_EXTENSION = 'lock';
/**
* @param numeric-string|int $lockLifetime
@@ -102,11 +103,13 @@ class FileLockRegion implements ConcurrentRegion
return @fileatime($filename);
}
#[Override]
public function getName(): string
{
return $this->region->getName();
}
#[Override]
public function contains(CacheKey $key): bool
{
if ($this->isLocked($key)) {
@@ -116,6 +119,7 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->contains($key);
}
#[Override]
public function get(CacheKey $key): CacheEntry|null
{
if ($this->isLocked($key)) {
@@ -125,6 +129,7 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->get($key);
}
#[Override]
public function getMultiple(CollectionCacheEntry $collection): array|null
{
if (array_filter(array_map($this->isLocked(...), $collection->identifiers))) {
@@ -134,6 +139,7 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->getMultiple($collection);
}
#[Override]
public function put(CacheKey $key, CacheEntry $entry, Lock|null $lock = null): bool
{
if ($this->isLocked($key, $lock)) {
@@ -143,6 +149,7 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->put($key, $entry);
}
#[Override]
public function evict(CacheKey $key): bool
{
if ($this->isLocked($key)) {
@@ -152,6 +159,7 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->evict($key);
}
#[Override]
public function evictAll(): bool
{
// The check below is necessary because on some platforms glob returns false
@@ -165,6 +173,7 @@ class FileLockRegion implements ConcurrentRegion
return $this->region->evictAll();
}
#[Override]
public function lock(CacheKey $key): Lock|null
{
if ($this->isLocked($key)) {
@@ -183,6 +192,7 @@ class FileLockRegion implements ConcurrentRegion
return $lock;
}
#[Override]
public function unlock(CacheKey $key, Lock $lock): bool
{
if ($this->isLocked($key, $lock)) {
@@ -7,12 +7,14 @@ namespace Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\TimestampCacheEntry;
use Doctrine\ORM\Cache\TimestampRegion;
use Override;
/**
* Tracks the timestamps of the most recent updates to particular keys.
*/
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
{
#[Override]
public function update(CacheKey $key): void
{
$this->put($key, new TimestampCacheEntry());
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache;
use Override;
use function microtime;
class TimestampQueryCacheValidator implements QueryCacheValidator
@@ -12,6 +14,7 @@ class TimestampQueryCacheValidator implements QueryCacheValidator
{
}
#[Override]
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry): bool
{
if ($this->regionUpdated($key, $entry)) {
-175
View File
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Exception\InvalidEntityRepository;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
@@ -18,21 +17,17 @@ use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Mapping\TypedFieldMapper;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use function class_exists;
use function is_a;
use function strtolower;
use const PHP_VERSION_ID;
/**
* Configuration container for all configuration options of Doctrine.
* It combines all configuration options from DBAL & ORM.
@@ -59,112 +54,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
return $this->identityGenerationPreferences;
}
/**
* Sets the directory where Doctrine generates any necessary proxy class files.
*/
public function setProxyDir(string $dir): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['proxyDir'] = $dir;
}
/**
* Gets the directory where Doctrine generates any necessary proxy class files.
*/
public function getProxyDir(): string|null
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['proxyDir'] ?? null;
}
/**
* Gets the strategy for automatically generating proxy classes.
*
* @return ProxyFactory::AUTOGENERATE_*
*/
public function getAutoGenerateProxyClasses(): int
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
}
/**
* Sets the strategy for automatically generating proxy classes.
*
* @param bool|ProxyFactory::AUTOGENERATE_* $autoGenerate True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
*/
public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
}
/**
* Gets the namespace where proxy classes reside.
*/
public function getProxyNamespace(): string|null
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
return $this->attributes['proxyNamespace'] ?? null;
}
/**
* Sets the namespace where proxy classes reside.
*/
public function setProxyNamespace(string $ns): void
{
if (PHP_VERSION_ID >= 80400) {
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
__METHOD__,
);
}
$this->attributes['proxyNamespace'] = $ns;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
@@ -650,70 +539,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
}
public function isNativeLazyObjectsEnabled(): bool
{
return $this->attributes['nativeLazyObjects'] ?? false;
}
public function enableNativeLazyObjects(bool $nativeLazyObjects): void
{
if (PHP_VERSION_ID >= 80400 && ! $nativeLazyObjects) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12005',
'Disabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
);
}
if (PHP_VERSION_ID < 80400 && $nativeLazyObjects) {
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
}
$this->attributes['nativeLazyObjects'] = $nativeLazyObjects;
}
/**
* @deprecated lazy ghost objects are always enabled
*
* @return true
*/
public function isLazyGhostObjectEnabled(): bool
{
return true;
}
/** @deprecated lazy ghost objects cannot be disabled */
public function setLazyGhostObjectEnabled(bool $flag): void
{
if (! $flag) {
throw new LogicException(<<<'EXCEPTION'
The lazy ghost object feature cannot be disabled anymore.
Please remove the call to setLazyGhostObjectEnabled(false).
EXCEPTION);
}
}
/** @deprecated rejecting ID collisions in the identity map cannot be disabled */
public function setRejectIdCollisionInIdentityMap(bool $flag): void
{
if (! $flag) {
throw new LogicException(<<<'EXCEPTION'
Rejecting ID collisions in the identity map cannot be disabled anymore.
Please remove the call to setRejectIdCollisionInIdentityMap(false).
EXCEPTION);
}
}
/**
* @deprecated rejecting ID collisions in the identity map is always enabled
*
* @return true
*/
public function isRejectIdCollisionInIdentityMapEnabled(): bool
{
return true;
}
public function setEagerFetchBatchSize(int $batchSize = 100): void
{
$this->attributes['fetchModeSubselectBatchSize'] = $batchSize;
+33 -5
View File
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Decorator;
use DateTimeInterface;
use Doctrine\Common\EventManager;
use Doctrine\Common\EventManagerInterface;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Cache;
@@ -24,6 +24,7 @@ use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\ObjectManagerDecorator;
use Override;
/**
* Base class for EntityManager decorators
@@ -37,136 +38,163 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
$this->wrapped = $wrapped;
}
#[Override]
public function getRepository(string $className): EntityRepository
{
return $this->wrapped->getRepository($className);
}
#[Override]
public function getMetadataFactory(): ClassMetadataFactory
{
return $this->wrapped->getMetadataFactory();
}
#[Override]
public function getClassMetadata(string $className): ClassMetadata
{
return $this->wrapped->getClassMetadata($className);
}
#[Override]
public function getConnection(): Connection
{
return $this->wrapped->getConnection();
}
#[Override]
public function getExpressionBuilder(): Expr
{
return $this->wrapped->getExpressionBuilder();
}
#[Override]
public function beginTransaction(): void
{
$this->wrapped->beginTransaction();
}
#[Override]
public function wrapInTransaction(callable $func): mixed
{
return $this->wrapped->wrapInTransaction($func);
}
#[Override]
public function commit(): void
{
$this->wrapped->commit();
}
#[Override]
public function rollback(): void
{
$this->wrapped->rollback();
}
#[Override]
public function createQuery(string $dql = ''): Query
{
return $this->wrapped->createQuery($dql);
}
#[Override]
public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery
{
return $this->wrapped->createNativeQuery($sql, $rsm);
}
#[Override]
public function createQueryBuilder(): QueryBuilder
{
return $this->wrapped->createQueryBuilder();
}
#[Override]
public function getReference(string $entityName, mixed $id): object|null
{
return $this->wrapped->getReference($entityName, $id);
}
#[Override]
public function close(): void
{
$this->wrapped->close();
}
public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void
#[Override]
public function lock(object $entity, LockMode $lockMode, DateTimeInterface|int|null $lockVersion = null): void
{
$this->wrapped->lock($entity, $lockMode, $lockVersion);
}
public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null
#[Override]
public function find(string $className, mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null
{
return $this->wrapped->find($className, $id, $lockMode, $lockVersion);
}
public function refresh(object $object, LockMode|int|null $lockMode = null): void
#[Override]
public function refresh(object $object, LockMode|null $lockMode = null): void
{
$this->wrapped->refresh($object, $lockMode);
}
public function getEventManager(): EventManager
#[Override]
public function getEventManager(): EventManagerInterface
{
return $this->wrapped->getEventManager();
}
#[Override]
public function getConfiguration(): Configuration
{
return $this->wrapped->getConfiguration();
}
#[Override]
public function isOpen(): bool
{
return $this->wrapped->isOpen();
}
#[Override]
public function getUnitOfWork(): UnitOfWork
{
return $this->wrapped->getUnitOfWork();
}
#[Override]
public function newHydrator(string|int $hydrationMode): AbstractHydrator
{
return $this->wrapped->newHydrator($hydrationMode);
}
#[Override]
public function getProxyFactory(): ProxyFactory
{
return $this->wrapped->getProxyFactory();
}
#[Override]
public function getFilters(): FilterCollection
{
return $this->wrapped->getFilters();
}
#[Override]
public function isFiltersStateClean(): bool
{
return $this->wrapped->isFiltersStateClean();
}
#[Override]
public function hasFilters(): bool
{
return $this->wrapped->hasFilters();
}
#[Override]
public function getCache(): Cache|null
{
return $this->wrapped->getCache();
+47 -27
View File
@@ -7,6 +7,7 @@ namespace Doctrine\ORM;
use BackedEnum;
use DateTimeInterface;
use Doctrine\Common\EventManager;
use Doctrine\Common\EventManagerInterface;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Exception\EntityManagerClosed;
@@ -18,18 +19,17 @@ use Doctrine\ORM\Exception\UnrecognizedIdentifierFields;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Repository\RepositoryFactory;
use Override;
use function array_keys;
use function is_array;
use function is_object;
use function ltrim;
use function method_exists;
/**
* The EntityManager is the central access point to ORM functionality.
@@ -72,7 +72,7 @@ class EntityManager implements EntityManagerInterface
/**
* The event manager that is the central point of the event system.
*/
private EventManager $eventManager;
private EventManagerInterface $eventManager;
/**
* The proxy factory used to create dynamic proxies.
@@ -113,17 +113,13 @@ class EntityManager implements EntityManagerInterface
public function __construct(
private Connection $conn,
private Configuration $config,
EventManager|null $eventManager = null,
EventManagerInterface|null $eventManager = null,
) {
if (! $config->getMetadataDriverImpl()) {
throw MissingMappingDriverImplementation::create();
}
$this->eventManager = $eventManager
?? (method_exists($conn, 'getEventManager')
? $conn->getEventManager()
: new EventManager()
);
$this->eventManager = $eventManager ?? new EventManager();
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
@@ -134,16 +130,7 @@ class EntityManager implements EntityManagerInterface
$this->repositoryFactory = $config->getRepositoryFactory();
$this->unitOfWork = new UnitOfWork($this);
if ($config->isNativeLazyObjectsEnabled()) {
$this->proxyFactory = new ProxyFactory($this);
} else {
$this->proxyFactory = new ProxyFactory(
$this,
$config->getProxyDir(),
$config->getProxyNamespace(),
$config->getAutoGenerateProxyClasses(),
);
}
$this->proxyFactory = new ProxyFactory($this);
if ($config->isSecondLevelCacheEnabled()) {
$cacheConfig = $config->getSecondLevelCacheConfiguration();
@@ -152,31 +139,37 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function getConnection(): Connection
{
return $this->conn;
}
#[Override]
public function getMetadataFactory(): ClassMetadataFactory
{
return $this->metadataFactory;
}
#[Override]
public function getExpressionBuilder(): Expr
{
return $this->expressionBuilder ??= new Expr();
}
#[Override]
public function beginTransaction(): void
{
$this->conn->beginTransaction();
}
#[Override]
public function getCache(): Cache|null
{
return $this->cache;
}
#[Override]
public function wrapInTransaction(callable $func): mixed
{
$this->conn->beginTransaction();
@@ -202,11 +195,13 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function commit(): void
{
$this->conn->commit();
}
#[Override]
public function rollback(): void
{
$this->conn->rollBack();
@@ -219,11 +214,13 @@ class EntityManager implements EntityManagerInterface
*
* {@inheritDoc}
*/
#[Override]
public function getClassMetadata(string $className): Mapping\ClassMetadata
{
return $this->metadataFactory->getMetadataFor($className);
}
#[Override]
public function createQuery(string $dql = ''): Query
{
$query = new Query($this);
@@ -235,6 +232,7 @@ class EntityManager implements EntityManagerInterface
return $query;
}
#[Override]
public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery
{
$query = new NativeQuery($this);
@@ -245,6 +243,7 @@ class EntityManager implements EntityManagerInterface
return $query;
}
#[Override]
public function createQueryBuilder(): QueryBuilder
{
return new QueryBuilder($this);
@@ -262,6 +261,7 @@ class EntityManager implements EntityManagerInterface
* makes use of optimistic locking fails.
* @throws ORMException
*/
#[Override]
public function flush(): void
{
$this->errorIfClosed();
@@ -271,7 +271,8 @@ class EntityManager implements EntityManagerInterface
/**
* {@inheritDoc}
*/
public function find($className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null
#[Override]
public function find($className, mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null
{
$class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\'));
@@ -289,7 +290,7 @@ class EntityManager implements EntityManagerInterface
foreach ($id as $i => $value) {
if (is_object($value)) {
$className = DefaultProxyClassNameResolver::getClass($value);
$className = $value::class;
if ($this->metadataFactory->hasMetadataFor($className)) {
$id[$i] = $this->unitOfWork->getSingleIdentifierValue($value);
@@ -367,6 +368,7 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function getReference(string $entityName, mixed $id): object|null
{
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
@@ -412,11 +414,13 @@ class EntityManager implements EntityManagerInterface
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*/
#[Override]
public function clear(): void
{
$this->unitOfWork->clear();
}
#[Override]
public function close(): void
{
$this->clear();
@@ -436,6 +440,7 @@ class EntityManager implements EntityManagerInterface
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
#[Override]
public function persist(object $object): void
{
$this->errorIfClosed();
@@ -452,6 +457,7 @@ class EntityManager implements EntityManagerInterface
* @throws ORMInvalidArgumentException
* @throws ORMException
*/
#[Override]
public function remove(object $object): void
{
$this->errorIfClosed();
@@ -459,7 +465,8 @@ class EntityManager implements EntityManagerInterface
$this->unitOfWork->remove($object);
}
public function refresh(object $object, LockMode|int|null $lockMode = null): void
#[Override]
public function refresh(object $object, LockMode|null $lockMode = null): void
{
$this->errorIfClosed();
@@ -475,12 +482,14 @@ class EntityManager implements EntityManagerInterface
*
* @throws ORMInvalidArgumentException
*/
#[Override]
public function detach(object $object): void
{
$this->unitOfWork->detach($object);
}
public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void
#[Override]
public function lock(object $entity, LockMode $lockMode, DateTimeInterface|int|null $lockVersion = null): void
{
$this->unitOfWork->lock($entity, $lockMode, $lockVersion);
}
@@ -494,6 +503,7 @@ class EntityManager implements EntityManagerInterface
*
* @template T of object
*/
#[Override]
public function getRepository(string $className): EntityRepository
{
return $this->repositoryFactory->getRepository($this, $className);
@@ -504,6 +514,7 @@ class EntityManager implements EntityManagerInterface
*
* @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
*/
#[Override]
public function contains(object $object): bool
{
return $this->unitOfWork->isScheduledForInsert($object)
@@ -511,11 +522,13 @@ class EntityManager implements EntityManagerInterface
&& ! $this->unitOfWork->isScheduledForDelete($object);
}
public function getEventManager(): EventManager
#[Override]
public function getEventManager(): EventManagerInterface
{
return $this->eventManager;
}
#[Override]
public function getConfiguration(): Configuration
{
return $this->config;
@@ -533,16 +546,19 @@ class EntityManager implements EntityManagerInterface
}
}
#[Override]
public function isOpen(): bool
{
return ! $this->closed;
}
#[Override]
public function getUnitOfWork(): UnitOfWork
{
return $this->unitOfWork;
}
#[Override]
public function newHydrator(string|int $hydrationMode): AbstractHydrator
{
return match ($hydrationMode) {
@@ -556,11 +572,13 @@ class EntityManager implements EntityManagerInterface
};
}
#[Override]
public function getProxyFactory(): ProxyFactory
{
return $this->proxyFactory;
}
#[Override]
public function initializeObject(object $obj): void
{
$this->unitOfWork->initializeObject($obj);
@@ -569,33 +587,35 @@ class EntityManager implements EntityManagerInterface
/**
* {@inheritDoc}
*/
#[Override]
public function isUninitializedObject($value): bool
{
return $this->unitOfWork->isUninitializedObject($value);
}
#[Override]
public function getFilters(): FilterCollection
{
return $this->filterCollection ??= new FilterCollection($this);
}
#[Override]
public function isFiltersStateClean(): bool
{
return $this->filterCollection === null || $this->filterCollection->isClean();
}
#[Override]
public function hasFilters(): bool
{
return $this->filterCollection !== null;
}
/**
* @phpstan-param LockMode::* $lockMode
*
* @throws OptimisticLockException
* @throws TransactionRequiredException
*/
private function checkLockRequirements(LockMode|int $lockMode, ClassMetadata $class): void
private function checkLockRequirements(LockMode $lockMode, ClassMetadata $class): void
{
switch ($lockMode) {
case LockMode::OPTIMISTIC:
+20 -20
View File
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use DateTimeInterface;
use Doctrine\Common\EventManager;
use Doctrine\Common\EventManagerInterface;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Exception\ORMException;
@@ -16,6 +16,7 @@ use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\FilterCollection;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ObjectManager;
use Override;
interface EntityManagerInterface extends ObjectManager
{
@@ -28,6 +29,7 @@ interface EntityManagerInterface extends ObjectManager
*
* @template T of object
*/
#[Override]
public function getRepository(string $className): EntityRepository;
/**
@@ -40,6 +42,7 @@ interface EntityManagerInterface extends ObjectManager
*/
public function getConnection(): Connection;
#[Override]
public function getMetadataFactory(): ClassMetadataFactory;
/**
@@ -110,15 +113,13 @@ interface EntityManagerInterface extends ObjectManager
/**
* Finds an Entity by its identifier.
*
* @param string $className The class name of the entity to find.
* @param mixed $id The identity of the entity to find.
* @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @param string $className The class name of the entity to find.
* @param mixed $id The identity of the entity to find.
* @param LockMode|null $lockMode The lock mode or NULL if no specific lock mode
* should be used during the search.
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @phpstan-param class-string<T> $className
* @phpstan-param LockMode::*|null $lockMode
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @phpstan-return T|null
@@ -130,22 +131,22 @@ interface EntityManagerInterface extends ObjectManager
*
* @template T of object
*/
public function find(string $className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null;
#[Override]
public function find(string $className, mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null;
/**
* Refreshes the persistent state of an object from the database,
* overriding any local changes that have not yet been persisted.
*
* @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @phpstan-param LockMode::*|null $lockMode
* @param LockMode|null $lockMode The lock mode or NULL if no specific lock mode
* should be used during the search.
*
* @throws ORMInvalidArgumentException
* @throws ORMException
* @throws TransactionRequiredException
*/
public function refresh(object $object, LockMode|int|null $lockMode = null): void;
#[Override]
public function refresh(object $object, LockMode|null $lockMode = null): void;
/**
* Gets a reference to the entity identified by the given type and identifier
@@ -172,17 +173,15 @@ interface EntityManagerInterface extends ObjectManager
/**
* Acquire a lock on the given entity.
*
* @phpstan-param LockMode::* $lockMode
*
* @throws OptimisticLockException
* @throws PessimisticLockException
*/
public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void;
public function lock(object $entity, LockMode $lockMode, DateTimeInterface|int|null $lockVersion = null): void;
/**
* Gets the EventManager used by the EntityManager.
* Gets the EventManagerInterface used by the EntityManager.
*/
public function getEventManager(): EventManager;
public function getEventManager(): EventManagerInterface;
/**
* Gets the Configuration used by the EntityManager.
@@ -237,5 +236,6 @@ interface EntityManagerInterface extends ObjectManager
*
* @phpstan-template T of object
*/
#[Override]
public function getClassMetadata(string $className): Mapping\ClassMetadata;
}
+10 -5
View File
@@ -15,6 +15,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall;
use Doctrine\Persistence\ObjectRepository;
use Override;
use function array_slice;
use function lcfirst;
@@ -73,15 +74,14 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds an entity by its primary key / identifier.
*
* @param LockMode|int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @phpstan-param LockMode::*|null $lockMode
* @param LockMode|null $lockMode The lock mode or NULL if no specific lock mode
* should be used during the search.
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @phpstan-return ?T
*/
public function find(mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null
#[Override]
public function find(mixed $id, LockMode|null $lockMode = null, int|null $lockVersion = null): object|null
{
return $this->em->find($this->entityName, $id, $lockMode, $lockVersion);
}
@@ -91,6 +91,7 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return list<T> The entities.
*/
#[Override]
public function findAll(): array
{
return $this->findBy([]);
@@ -103,6 +104,7 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return list<T>
*/
#[Override]
public function findBy(array $criteria, array|null $orderBy = null, int|null $limit = null, int|null $offset = null): array
{
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
@@ -118,6 +120,7 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return T|null
*/
#[Override]
public function findOneBy(array $criteria, array|null $orderBy = null): object|null
{
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
@@ -175,6 +178,7 @@ class EntityRepository implements ObjectRepository, Selectable
return $this->entityName;
}
#[Override]
public function getClassName(): string
{
return $this->getEntityName();
@@ -197,6 +201,7 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @phpstan-return AbstractLazyCollection<int, T>&Selectable<int, T>
*/
#[Override]
public function matching(Criteria $criteria): AbstractLazyCollection&Selectable
{
$persister = $this->em->getUnitOfWork()->getEntityPersister($this->entityName);
+11 -11
View File
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\Common\EventManager;
use Doctrine\Common\EventDispatcher;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\EntityListenerResolver;
@@ -15,21 +15,21 @@ use Doctrine\ORM\Mapping\EntityListenerResolver;
*/
class ListenersInvoker
{
final public const INVOKE_NONE = 0;
final public const INVOKE_LISTENERS = 1;
final public const INVOKE_CALLBACKS = 2;
final public const INVOKE_MANAGER = 4;
final public const int INVOKE_NONE = 0;
final public const int INVOKE_LISTENERS = 1;
final public const int INVOKE_CALLBACKS = 2;
final public const int INVOKE_MANAGER = 4;
/** The Entity listener resolver. */
private readonly EntityListenerResolver $resolver;
/** The EventManager used for dispatching events. */
private readonly EventManager $eventManager;
/** The EventDispatcher used for dispatching events. */
private readonly EventDispatcher $eventDispatcher;
public function __construct(EntityManagerInterface $em)
{
$this->eventManager = $em->getEventManager();
$this->resolver = $em->getConfiguration()->getEntityListenerResolver();
$this->eventDispatcher = $em->getEventManager();
$this->resolver = $em->getConfiguration()->getEntityListenerResolver();
}
/**
@@ -52,7 +52,7 @@ class ListenersInvoker
$invoke |= self::INVOKE_LISTENERS;
}
if ($this->eventManager->hasListeners($eventName)) {
if ($this->eventDispatcher->hasListeners($eventName)) {
$invoke |= self::INVOKE_MANAGER;
}
@@ -92,7 +92,7 @@ class ListenersInvoker
}
if ($invoke & self::INVOKE_MANAGER) {
$this->eventManager->dispatchEvent($eventName, $event);
$this->eventDispatcher->dispatchEvent($eventName, $event);
}
}
}
+13 -13
View File
@@ -24,7 +24,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
public const preRemove = 'preRemove';
final public const string preRemove = 'preRemove';
/**
* The postRemove event occurs for an entity after the entity has
@@ -32,7 +32,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
public const postRemove = 'postRemove';
final public const string postRemove = 'postRemove';
/**
* The prePersist event occurs for a given entity before the respective
@@ -40,7 +40,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
public const prePersist = 'prePersist';
final public const string prePersist = 'prePersist';
/**
* The postPersist event occurs for an entity after the entity has
@@ -49,7 +49,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
public const postPersist = 'postPersist';
final public const string postPersist = 'postPersist';
/**
* The preUpdate event occurs before the database update operations to
@@ -57,7 +57,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
public const preUpdate = 'preUpdate';
final public const string preUpdate = 'preUpdate';
/**
* The postUpdate event occurs after the database update operations to
@@ -65,7 +65,7 @@ final class Events
*
* This is an entity lifecycle event.
*/
public const postUpdate = 'postUpdate';
final public const string postUpdate = 'postUpdate';
/**
* The postLoad event occurs for an entity after the entity has been loaded
@@ -78,26 +78,26 @@ final class Events
*
* This is an entity lifecycle event.
*/
public const postLoad = 'postLoad';
final public const string postLoad = 'postLoad';
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (attributes/xml).
*/
public const loadClassMetadata = 'loadClassMetadata';
final public const string loadClassMetadata = 'loadClassMetadata';
/**
* The onClassMetadataNotFound event occurs whenever loading metadata for a class
* failed.
*/
public const onClassMetadataNotFound = 'onClassMetadataNotFound';
final public const string onClassMetadataNotFound = 'onClassMetadataNotFound';
/**
* The preFlush event occurs when the EntityManager#flush() operation is invoked,
* but before any changes to managed entities have been calculated. This event is
* always raised right after EntityManager#flush() call.
*/
public const preFlush = 'preFlush';
final public const string preFlush = 'preFlush';
/**
* The onFlush event occurs when the EntityManager#flush() operation is invoked,
@@ -105,7 +105,7 @@ final class Events
* actual database operations are executed. The event is only raised if there is
* actually something to do for the underlying UnitOfWork.
*/
public const onFlush = 'onFlush';
final public const string onFlush = 'onFlush';
/**
* The postFlush event occurs when the EntityManager#flush() operation is invoked and
@@ -113,11 +113,11 @@ final class Events
* actually something to do for the underlying UnitOfWork. The event won't be raised if an error occurs during the
* flush operation.
*/
public const postFlush = 'postFlush';
final public const string postFlush = 'postFlush';
/**
* The onClear event occurs when the EntityManager#clear() operation is invoked,
* after all references to entities have been removed from the unit of work.
*/
public const onClear = 'onClear';
final public const string onClear = 'onClear';
}
@@ -11,7 +11,7 @@ use function sprintf;
final class MultipleSelectorsFoundException extends LogicException implements ORMException
{
public const MULTIPLE_SELECTORS_FOUND_EXCEPTION = 'Multiple selectors found: %s. Please select only one.';
final public const string MULTIPLE_SELECTORS_FOUND_EXCEPTION = 'Multiple selectors found: %s. Please select only one.';
/** @param string[] $selectors */
public static function create(array $selectors): self
-44
View File
@@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
/** @deprecated */
final class NotSupported extends LogicException implements ORMException
{
public static function create(): self
{
return new self('This behaviour is (currently) not supported by Doctrine 2');
}
public static function createForDbal3(string $context): self
{
return new self(sprintf(
<<<'EXCEPTION'
Context: %s
Problem: Feature was deprecated in doctrine/dbal 2.x and is not supported by installed doctrine/dbal:3.x
Solution: See the doctrine/deprecations logs for new alternative approaches.
EXCEPTION
,
$context,
));
}
public static function createForPersistence3(string $context): self
{
return new self(sprintf(
<<<'EXCEPTION'
Context: %s
Problem: Feature was deprecated in doctrine/persistence 2.x and is not supported by installed doctrine/persistence:3.x
Solution: See the doctrine/deprecations logs for new alternative approaches.
EXCEPTION
,
$context,
));
}
}
+2
View File
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\EntityMissingAssignedId;
use Override;
/**
* Special generator for application-assigned identifiers (doesn't really generate anything).
@@ -19,6 +20,7 @@ class AssignedGenerator extends AbstractIdGenerator
*
* @throws EntityMissingAssignedId
*/
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): array
{
$class = $em->getClassMetadata($entity::class);
+3
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManagerInterface;
use Override;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
@@ -13,11 +14,13 @@ use Doctrine\ORM\EntityManagerInterface;
*/
class BigIntegerIdentityGenerator extends AbstractIdGenerator
{
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): string
{
return (string) $em->getConnection()->lastInsertId();
}
#[Override]
public function isPostInsertGenerator(): bool
{
return true;
+3
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManagerInterface;
use Override;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
@@ -13,11 +14,13 @@ use Doctrine\ORM\EntityManagerInterface;
*/
class IdentityGenerator extends AbstractIdGenerator
{
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): int
{
return (int) $em->getConnection()->lastInsertId();
}
#[Override]
public function isPostInsertGenerator(): bool
{
return true;
+3 -34
View File
@@ -5,17 +5,13 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Serializable;
use function serialize;
use function unserialize;
use Override;
/**
* Represents an ID generator that uses a database sequence.
*/
class SequenceGenerator extends AbstractIdGenerator implements Serializable
class SequenceGenerator extends AbstractIdGenerator
{
private int $nextValue = 0;
private int|null $maxValue = null;
@@ -32,6 +28,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
) {
}
#[Override]
public function generateId(EntityManagerInterface $em, object|null $entity): int
{
if ($this->maxValue === null || $this->nextValue === $this->maxValue) {
@@ -66,20 +63,6 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
return $this->nextValue;
}
/** @deprecated without replacement. */
final public function serialize(): string
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11468',
'%s() is deprecated, use __serialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.',
__METHOD__,
self::class,
);
return serialize($this->__serialize());
}
/** @return array<string, mixed> */
public function __serialize(): array
{
@@ -89,20 +72,6 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
];
}
/** @deprecated without replacement. */
final public function unserialize(string $serialized): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11468',
'%s() is deprecated, use __unserialize() instead. %s won\'t implement the Serializable interface anymore in ORM 4.',
__METHOD__,
self::class,
);
$this->__unserialize(unserialize($serialized));
}
/** @param array<string, mixed> $data */
public function __unserialize(array $data): void
{
+7 -1
View File
@@ -17,6 +17,7 @@ use Doctrine\ORM\UnitOfWork;
use Generator;
use LogicException;
use ReflectionClass;
use ReflectionEnum;
use function array_key_exists;
use function array_keys;
@@ -597,13 +598,18 @@ abstract class AbstractHydrator
*/
final protected function buildEnum(mixed $value, string $enumType): BackedEnum|array
{
$reflection = new ReflectionEnum($enumType);
$isIntBacked = $reflection->isBacked() && $reflection->getBackingType()->getName() === 'int';
if (is_array($value)) {
return array_map(
static fn ($value) => $enumType::from($value),
static fn ($value) => $enumType::from($isIntBacked ? (int) $value : $value),
$value,
);
}
$value = $isIntBacked ? (int) $value : $value;
return $enumType::from($value);
}
}
+5
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\Hydration;
use Override;
use function array_key_last;
use function count;
use function is_array;
@@ -32,6 +34,7 @@ class ArrayHydrator extends AbstractHydrator
private int $resultCounter = 0;
#[Override]
protected function prepare(): void
{
$this->isSimpleQuery = count($this->resultSetMapping()->aliasMap) <= 1;
@@ -46,6 +49,7 @@ class ArrayHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -60,6 +64,7 @@ class ArrayHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
// 1) Initialize
@@ -10,6 +10,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Override;
use function array_fill_keys;
use function array_keys;
@@ -51,6 +52,7 @@ class ObjectHydrator extends AbstractHydrator
/** @var mixed[] */
private array $existingCollections = [];
#[Override]
protected function prepare(): void
{
if (! isset($this->hints[UnitOfWork::HINT_DEFEREAGERLOAD])) {
@@ -108,6 +110,7 @@ class ObjectHydrator extends AbstractHydrator
}
}
#[Override]
protected function cleanup(): void
{
$eagerLoad = isset($this->hints[UnitOfWork::HINT_DEFEREAGERLOAD]) && $this->hints[UnitOfWork::HINT_DEFEREAGERLOAD] === true;
@@ -127,6 +130,7 @@ class ObjectHydrator extends AbstractHydrator
$this->uow->hydrationComplete();
}
#[Override]
protected function cleanupAfterRowIteration(): void
{
$this->identifierMap =
@@ -139,6 +143,7 @@ class ObjectHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -318,6 +323,7 @@ class ObjectHydrator extends AbstractHydrator
* @param mixed[] $row The data of the row to process.
* @param mixed[] $result The result array to fill.
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
// Initialize
@@ -574,6 +580,7 @@ class ObjectHydrator extends AbstractHydrator
}
/** @param mixed[] $data pre-hydrated SQL Result Row. */
#[Override]
protected function hydrateNestedEntity(array $data, string $dqlAlias): mixed
{
if (isset($this->resultSetMapping()->nestedEntities[$dqlAlias])) {
@@ -587,6 +594,7 @@ class ObjectHydrator extends AbstractHydrator
* When executed in a hydrate() loop we may have to clear internal state to
* decrease memory consumption.
*/
#[Override]
public function onClear(mixed $eventArgs): void
{
parent::onClear($eventArgs);
@@ -6,8 +6,8 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\ORM\Exception\MultipleSelectorsFoundException;
use Override;
use function array_column;
use function count;
/**
@@ -21,14 +21,13 @@ final class ScalarColumnHydrator extends AbstractHydrator
* @throws MultipleSelectorsFoundException
* @throws Exception
*/
#[Override]
protected function hydrateAllData(): array
{
if (count($this->resultSetMapping()->fieldMappings) > 1) {
throw MultipleSelectorsFoundException::create($this->resultSetMapping()->fieldMappings);
}
$result = $this->statement()->fetchAllNumeric();
return array_column($result, 0);
return $this->statement()->fetchFirstColumn();
}
}
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\Hydration;
use Override;
/**
* Hydrator that produces flat, rectangular results of scalar data.
* The created result is almost the same as a regular SQL result set, except
@@ -14,6 +16,7 @@ class ScalarHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -28,6 +31,7 @@ class ScalarHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
$result[] = $this->gatherScalarRowData($row);
@@ -9,6 +9,7 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Query;
use Exception;
use Override;
use RuntimeException;
use ValueError;
@@ -28,6 +29,7 @@ class SimpleObjectHydrator extends AbstractHydrator
private ClassMetadata|null $class = null;
#[Override]
protected function prepare(): void
{
if (count($this->resultSetMapping()->aliasMap) !== 1) {
@@ -41,6 +43,7 @@ class SimpleObjectHydrator extends AbstractHydrator
$this->class = $this->getClassMetadata(reset($this->resultSetMapping()->aliasMap));
}
#[Override]
protected function cleanup(): void
{
parent::cleanup();
@@ -52,6 +55,7 @@ class SimpleObjectHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateAllData(): array
{
$result = [];
@@ -68,6 +72,7 @@ class SimpleObjectHydrator extends AbstractHydrator
/**
* {@inheritDoc}
*/
#[Override]
protected function hydrateRowData(array $row, array &$result): void
{
assert($this->class !== null);
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Override;
use function array_shift;
use function count;
@@ -16,6 +17,7 @@ use function key;
*/
class SingleScalarHydrator extends AbstractHydrator
{
#[Override]
protected function hydrateAllData(): mixed
{
$data = $this->statement()->fetchAllAssociative();
+3 -3
View File
@@ -25,9 +25,9 @@ use function spl_object_id;
*/
final class StronglyConnectedComponents
{
private const NOT_VISITED = 1;
private const IN_PROGRESS = 2;
private const VISITED = 3;
private const int NOT_VISITED = 1;
private const int IN_PROGRESS = 2;
private const int VISITED = 3;
/**
* Array of all nodes, indexed by object ids.
+3 -3
View File
@@ -20,9 +20,9 @@ use function spl_object_id;
*/
final class TopologicalSort
{
private const NOT_VISITED = 1;
private const IN_PROGRESS = 2;
private const VISITED = 3;
private const int NOT_VISITED = 1;
private const int IN_PROGRESS = 2;
private const int VISITED = 3;
/**
* Array of all nodes, indexed by object ids.
+7 -3
View File
@@ -10,8 +10,7 @@ use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use function assert;
use Override;
/**
* A lazy collection that allows a fast count when using criteria object
@@ -26,6 +25,7 @@ use function assert;
*/
class LazyCriteriaCollection extends AbstractLazyCollection implements Selectable
{
/** @var non-negative-int|null */
private int|null $count = null;
public function __construct(
@@ -37,6 +37,7 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
/**
* Do an efficient count on the collection
*/
#[Override]
public function count(): int
{
if ($this->isInitialized()) {
@@ -54,6 +55,7 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
/**
* check if collection is empty without loading it
*/
#[Override]
public function isEmpty(): bool
{
if ($this->isInitialized()) {
@@ -70,6 +72,7 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
*
* @return bool TRUE if the collection contains $element, FALSE otherwise.
*/
#[Override]
public function contains(mixed $element): bool
{
if ($this->isInitialized()) {
@@ -80,14 +83,15 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
}
/** @return ReadableCollection<TKey, TValue>&Selectable<TKey, TValue> */
#[Override]
public function matching(Criteria $criteria): ReadableCollection&Selectable
{
$this->initialize();
assert($this->collection instanceof Selectable);
return $this->collection->matching($criteria);
}
#[Override]
protected function doInitialize(): void
{
$elements = $this->entityPersister->loadCriteria($this->criteria);
+9
View File
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Internal\SQLResultCasing;
use Override;
/**
* ANSI compliant quote strategy, this strategy does not apply any quote.
@@ -15,6 +16,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
{
use SQLResultCasing;
#[Override]
public function getColumnName(
string $fieldName,
ClassMetadata $class,
@@ -23,6 +25,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
return $class->fieldMappings[$fieldName]->columnName;
}
#[Override]
public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string
{
return $class->table['name'];
@@ -31,16 +34,19 @@ class AnsiQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
{
return $definition['sequenceName'];
}
#[Override]
public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string
{
return $joinColumn->name;
}
#[Override]
public function getReferencedJoinColumnName(
JoinColumnMapping $joinColumn,
ClassMetadata $class,
@@ -49,6 +55,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
return $joinColumn->referencedColumnName;
}
#[Override]
public function getJoinTableName(
ManyToManyOwningSideMapping $association,
ClassMetadata $class,
@@ -60,11 +67,13 @@ class AnsiQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array
{
return $class->identifier;
}
#[Override]
public function getColumnAlias(
string $columnName,
int $counter,
-70
View File
@@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Deprecations\Deprecation;
use InvalidArgumentException;
use function property_exists;
/** @internal */
trait ArrayAccessImplementation
{
/** @param string $offset */
public function offsetExists(mixed $offset): bool
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
return isset($this->$offset);
}
/** @param string $offset */
public function offsetGet(mixed $offset): mixed
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
if (! property_exists($this, $offset)) {
throw new InvalidArgumentException('Undefined property: ' . $offset);
}
return $this->$offset;
}
/** @param string $offset */
public function offsetSet(mixed $offset, mixed $value): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
$this->$offset = $value;
}
/** @param string $offset */
public function offsetUnset(mixed $offset): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
$this->$offset = null;
}
}
+1 -3
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use Exception;
use OutOfRangeException;
@@ -14,8 +13,7 @@ use function in_array;
use function property_exists;
use function sprintf;
/** @template-implements ArrayAccess<string, mixed> */
abstract class AssociationMapping implements ArrayAccess
abstract class AssociationMapping
{
/**
* The names of persistence operations to cascade on the association.
@@ -113,6 +113,10 @@ class AssociationBuilder
string|null $onDelete = null,
string|null $columnDef = null,
): static {
if ($this->mapping['id'] ?? false) {
$nullable = null;
}
$this->joinColumns[] = [
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
@@ -133,6 +137,9 @@ class AssociationBuilder
public function makePrimaryKey(): static
{
$this->mapping['id'] = true;
foreach ($this->joinColumns ?? [] as $i => $joinColumn) {
$this->joinColumns[$i]['nullable'] = null;
}
return $this;
}
@@ -17,7 +17,7 @@ use function get_class_methods;
class EntityListenerBuilder
{
/** Hash-map to handle event names. */
private const EVENTS = [
private const array EVENTS = [
Events::preRemove => true,
Events::postRemove => true,
Events::prePersist => true,
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Builder;
use Override;
/**
* ManyToMany Association Builder
*
@@ -24,6 +26,31 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
return $this;
}
/**
* Add Join Columns.
*
* @return $this
*/
#[Override]
public function addJoinColumn(
string $columnName,
string $referencedColumnName,
bool $nullable = true,
bool $unique = false,
string|null $onDelete = null,
string|null $columnDef = null,
): static {
$this->joinColumns[] = [
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
'unique' => $unique,
'onDelete' => $onDelete,
'columnDefinition' => $columnDef,
];
return $this;
}
/**
* Adds Inverse Join Columns.
*
@@ -40,7 +67,6 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
$this->inverseJoinColumns[] = [
'name' => $columnName,
'referencedColumnName' => $referencedColumnName,
'nullable' => $nullable,
'unique' => $unique,
'onDelete' => $onDelete,
'columnDefinition' => $columnDef,
@@ -49,6 +75,7 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
return $this;
}
#[Override]
public function build(): ClassMetadataBuilder
{
$mapping = $this->mapping;
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Builder;
use Override;
/**
* OneToMany Association Builder
*
@@ -31,6 +33,7 @@ class OneToManyAssociationBuilder extends AssociationBuilder
return $this;
}
#[Override]
public function build(): ClassMetadataBuilder
{
$mapping = $this->mapping;
+2
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
use Override;
use ReflectionProperty;
final class ChainTypedFieldMapper implements TypedFieldMapper
@@ -24,6 +25,7 @@ final class ChainTypedFieldMapper implements TypedFieldMapper
/**
* {@inheritDoc}
*/
#[Override]
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
foreach ($this->typedFieldMappers as $typedFieldMapper) {
+89 -114
View File
@@ -22,9 +22,9 @@ use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\ReflectionService;
use InvalidArgumentException;
use LogicException;
use Override;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionProperty;
use Stringable;
use function array_column;
@@ -80,40 +80,38 @@ use function trim;
*/
class ClassMetadata implements PersistenceClassMetadata, Stringable
{
use GetReflectionClassImplementation;
/* The inheritance mapping types */
/**
* NONE means the class does not participate in an inheritance hierarchy
* and therefore does not need an inheritance mapping type.
*/
public const INHERITANCE_TYPE_NONE = 1;
final public const int INHERITANCE_TYPE_NONE = 1;
/**
* JOINED means the class will be persisted according to the rules of
* <tt>Class Table Inheritance</tt>.
*/
public const INHERITANCE_TYPE_JOINED = 2;
final public const int INHERITANCE_TYPE_JOINED = 2;
/**
* SINGLE_TABLE means the class will be persisted according to the rules of
* <tt>Single Table Inheritance</tt>.
*/
public const INHERITANCE_TYPE_SINGLE_TABLE = 3;
final public const int INHERITANCE_TYPE_SINGLE_TABLE = 3;
/* The Id generator types. */
/**
* AUTO means the generator type will depend on what the used platform prefers.
* Offers full portability.
*/
public const GENERATOR_TYPE_AUTO = 1;
final public const int GENERATOR_TYPE_AUTO = 1;
/**
* SEQUENCE means a separate sequence object will be used. Platforms that do
* not have native sequence support may emulate it. Full portability is currently
* not guaranteed.
*/
public const GENERATOR_TYPE_SEQUENCE = 2;
final public const int GENERATOR_TYPE_SEQUENCE = 2;
/**
* IDENTITY means an identity column is used for id generation. The database
@@ -121,18 +119,18 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
* native identity columns may emulate them. Full portability is currently
* not guaranteed.
*/
public const GENERATOR_TYPE_IDENTITY = 4;
final public const int GENERATOR_TYPE_IDENTITY = 4;
/**
* NONE means the class does not have a generated id. That means the class
* must have a natural, manually assigned id.
*/
public const GENERATOR_TYPE_NONE = 5;
final public const int GENERATOR_TYPE_NONE = 5;
/**
* CUSTOM means that customer will use own ID generator that supposedly work
*/
public const GENERATOR_TYPE_CUSTOM = 7;
final public const int GENERATOR_TYPE_CUSTOM = 7;
/**
* DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
@@ -141,92 +139,92 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* This is the default change tracking policy.
*/
public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
final public const int CHANGETRACKING_DEFERRED_IMPLICIT = 1;
/**
* DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
* by doing a property-by-property comparison with the original data. This will
* be done only for entities that were explicitly saved (through persist() or a cascade).
*/
public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
final public const int CHANGETRACKING_DEFERRED_EXPLICIT = 2;
/**
* Specifies that an association is to be fetched when it is first accessed.
*/
public const FETCH_LAZY = 2;
final public const int FETCH_LAZY = 2;
/**
* Specifies that an association is to be fetched when the owner of the
* association is fetched.
*/
public const FETCH_EAGER = 3;
final public const int FETCH_EAGER = 3;
/**
* Specifies that an association is to be fetched lazy (on first access) and that
* commands such as Collection#count, Collection#slice are issued directly against
* the database if the collection is not yet initialized.
*/
public const FETCH_EXTRA_LAZY = 4;
final public const int FETCH_EXTRA_LAZY = 4;
/**
* Identifies a one-to-one association.
*/
public const ONE_TO_ONE = 1;
final public const int ONE_TO_ONE = 1;
/**
* Identifies a many-to-one association.
*/
public const MANY_TO_ONE = 2;
final public const int MANY_TO_ONE = 2;
/**
* Identifies a one-to-many association.
*/
public const ONE_TO_MANY = 4;
final public const int ONE_TO_MANY = 4;
/**
* Identifies a many-to-many association.
*/
public const MANY_TO_MANY = 8;
final public const int MANY_TO_MANY = 8;
/**
* Combined bitmask for to-one (single-valued) associations.
*/
public const TO_ONE = 3;
final public const int TO_ONE = 3;
/**
* Combined bitmask for to-many (collection-valued) associations.
*/
public const TO_MANY = 12;
final public const int TO_MANY = 12;
/**
* ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
*/
public const CACHE_USAGE_READ_ONLY = 1;
final public const int CACHE_USAGE_READ_ONLY = 1;
/**
* Nonstrict Read Write Cache doesnt employ any locks but can do inserts, update and deletes.
*/
public const CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
final public const int CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
/**
* Read Write Attempts to lock the entity before update/delete.
*/
public const CACHE_USAGE_READ_WRITE = 3;
final public const int CACHE_USAGE_READ_WRITE = 3;
/**
* The value of this column is never generated by the database.
*/
public const GENERATED_NEVER = 0;
final public const int GENERATED_NEVER = 0;
/**
* The value of this column is generated by the database on INSERT, but not on UPDATE.
*/
public const GENERATED_INSERT = 1;
final public const int GENERATED_INSERT = 1;
/**
* The value of this column is generated by the database on both INSERT and UDPATE statements.
*/
public const GENERATED_ALWAYS = 2;
final public const int GENERATED_ALWAYS = 2;
/**
* READ-ONLY: The namespace the entity class is contained in.
@@ -519,9 +517,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* The ReflectionClass instance of the mapped class.
*
* @var ReflectionClass<T>|null
* @var ReflectionClass<T>
*/
public ReflectionClass|null $reflClass = null;
public ReflectionClass $reflClass;
/**
* Is this entity marked as "read-only"?
@@ -537,16 +535,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*/
protected NamingStrategy $namingStrategy;
/**
* The ReflectionProperty instances of the mapped class.
*
* @deprecated Use $propertyAccessors instead.
*
* @var LegacyReflectionFields|array<string, ReflectionProperty>
*/
public LegacyReflectionFields|array $reflFields = [];
/** @var array<string, PropertyAccessors\PropertyAccessor> */
/** @var array<string, PropertyAccessor> */
public array $propertyAccessors = [];
private InstantiatorInterface|null $instantiator = null;
@@ -571,55 +560,18 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* Gets the ReflectionProperties of the mapped class.
*
* @deprecated Use getPropertyAccessors() instead.
*
* @return LegacyReflectionFields|ReflectionProperty[] An array of ReflectionProperty instances.
* @phpstan-return LegacyReflectionFields|array<string, ReflectionProperty>
*/
public function getReflectionProperties(): array|LegacyReflectionFields
{
return $this->reflFields;
}
/**
* Gets the ReflectionProperties of the mapped class.
*
* @return PropertyAccessor[] An array of PropertyAccessor instances.
* @return array<string, PropertyAccessor> An array of PropertyAccessor instances by name.
*/
public function getPropertyAccessors(): array
{
return $this->propertyAccessors;
}
/**
* Gets a ReflectionProperty for a specific field of the mapped class.
*
* @deprecated Use getPropertyAccessor() instead.
*/
public function getReflectionProperty(string $name): ReflectionProperty|null
{
return $this->reflFields[$name];
}
public function getPropertyAccessor(string $name): PropertyAccessor|null
{
return $this->propertyAccessors[$name] ?? null;
}
/**
* @deprecated Use getPropertyAccessor() instead.
*
* @throws BadMethodCallException If the class has a composite identifier.
*/
public function getSingleIdReflectionProperty(): ReflectionProperty|null
{
if ($this->isIdentifierComposite) {
throw new BadMethodCallException('Class ' . $this->name . ' has a composite identifier.');
}
return $this->reflFields[$this->identifier[0]];
}
/** @throws BadMethodCallException If the class has a composite identifier. */
public function getSingleIdPropertyAccessor(): PropertyAccessor|null
{
@@ -638,6 +590,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @return array<string, mixed>
*/
#[Override]
public function getIdentifierValues(object $entity): array
{
if ($this->isIdentifierComposite) {
@@ -701,6 +654,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @todo Construct meaningful string representation.
*/
#[Override]
public function __toString(): string
{
return self::class . '@' . spl_object_id($this);
@@ -713,9 +667,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
* That means any metadata properties that are not set or empty or simply have
* their default value are NOT serialized.
*
* Parts that are also NOT serialized because they can not be properly unserialized:
* - reflClass (ReflectionClass)
* - reflFields (ReflectionProperty array)
* Parts that are also NOT serialized because they can not be properly
* unserialized, e.g. reflClass (ReflectionClass)
*
* @return string[] The names of all the fields that should be serialized.
*/
@@ -824,9 +777,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
public function wakeupReflection(ReflectionService $reflService): void
{
// Restore ReflectionClass and properties
$this->reflClass = $reflService->getClass($this->name);
/** @phpstan-ignore property.deprecated */
$this->reflFields = new LegacyReflectionFields($this, $reflService);
$this->reflClass = $reflService->getClass($this->name);
$this->instantiator = $this->instantiator ?: new Instantiator();
$parentAccessors = [];
@@ -907,10 +858,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
{
$this->reflClass = $reflService->getClass($this->name);
$this->namespace = $reflService->getClassNamespace($this->name);
if ($this->reflClass) {
$this->name = $this->rootEntityName = $this->reflClass->name;
}
$this->name = $this->rootEntityName = $this->reflClass->name;
$this->table['name'] = $this->namingStrategy->classToTableName($this->name);
}
@@ -970,6 +918,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
}
}
#[Override]
public function getReflectionClass(): ReflectionClass
{
return $this->reflClass;
}
/** @phpstan-param array{usage?: mixed, region?: mixed} $cache */
public function enableCache(array $cache): void
{
@@ -1036,6 +990,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* Checks whether a field is part of the identifier/primary key field(s).
*/
#[Override]
public function isIdentifier(string $fieldName): bool
{
if (! $this->identifier) {
@@ -1141,8 +1096,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*/
private function isTypedProperty(string $name): bool
{
return isset($this->reflClass)
&& $this->reflClass->hasProperty($name)
return $this->reflClass->hasProperty($name)
&& $this->reflClass->getProperty($name)->hasType();
}
@@ -1269,11 +1223,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
$this->containsEnumIdentifier = true;
}
if (
defined('Doctrine\DBAL\Types\Types::ENUM')
&& $mapping->type === Types::ENUM
&& ! isset($mapping->options['values'])
) {
if ($mapping->type === Types::ENUM && ! isset($mapping->options['values'])) {
$mapping->options['values'] = array_column($mapping->enumType::cases(), 'value');
}
}
@@ -1460,6 +1410,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifierFieldNames(): array
{
return $this->identifier;
@@ -1511,11 +1462,13 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifier(): array
{
return $this->identifier;
}
#[Override]
public function hasField(string $fieldName): bool
{
return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
@@ -1642,6 +1595,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @todo 3.0 Remove this. PersisterHelper should fix it somehow
*/
#[Override]
public function getTypeOfField(string $fieldName): string|null
{
return isset($this->fieldMappings[$fieldName])
@@ -2204,6 +2158,20 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
}
if (isset($columnDef['enumType'])) {
if (! enum_exists($columnDef['enumType'])) {
throw MappingException::nonEnumTypeMapped($this->name, $columnDef['fieldName'], $columnDef['enumType']);
}
if (
defined('Doctrine\DBAL\Types\Types::ENUM')
&& $columnDef['type'] === Types::ENUM
&& ! isset($columnDef['options']['values'])
) {
$columnDef['options']['values'] = array_column($columnDef['enumType']::cases(), 'value');
}
}
$this->discriminatorColumn = DiscriminatorColumnMapping::fromMappingArray($columnDef);
}
}
@@ -2222,6 +2190,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
* Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
*
* @param array<int|string, string> $map
*
* @throws MappingException
*/
public function setDiscriminatorMap(array $map): void
{
@@ -2241,6 +2211,16 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
);
}
$values = $this->discriminatorColumn->options['values'] ?? null;
if ($values !== null) {
$diff = array_diff(array_keys($map), $values);
if ($diff !== []) {
throw MappingException::invalidEntriesInDiscriminatorMap(array_values($diff), $this->name, $this->discriminatorColumn->enumType);
}
}
foreach ($map as $value => $className) {
$this->addDiscriminatorMapClass($value, $className);
}
@@ -2289,17 +2269,20 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
}
}
#[Override]
public function hasAssociation(string $fieldName): bool
{
return isset($this->associationMappings[$fieldName]);
}
#[Override]
public function isSingleValuedAssociation(string $fieldName): bool
{
return isset($this->associationMappings[$fieldName])
&& ($this->associationMappings[$fieldName]->isToOne());
}
#[Override]
public function isCollectionValuedAssociation(string $fieldName): bool
{
return isset($this->associationMappings[$fieldName])
@@ -2454,9 +2437,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
if (! isset($mapping['default'])) {
if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) {
$mapping['default'] = 1;
$mapping['options']['default'] = 1;
} elseif ($mapping['type'] === 'datetime') {
$mapping['default'] = 'CURRENT_TIMESTAMP';
$mapping['options']['default'] = 'CURRENT_TIMESTAMP';
} else {
throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
}
@@ -2495,6 +2478,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getFieldNames(): array
{
return array_keys($this->fieldMappings);
@@ -2503,6 +2487,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getAssociationNames(): array
{
return array_keys($this->associationMappings);
@@ -2515,23 +2500,27 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*
* @throws InvalidArgumentException
*/
#[Override]
public function getAssociationTargetClass(string $assocName): string
{
return $this->associationMappings[$assocName]->targetEntity
?? throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
}
#[Override]
public function getName(): string
{
return $this->name;
}
#[Override]
public function isAssociationInverseSide(string $assocName): bool
{
return isset($this->associationMappings[$assocName])
&& ! $this->associationMappings[$assocName]->isOwningSide();
}
#[Override]
public function getAssociationMappedByTargetField(string $assocName): string
{
$assoc = $this->getAssociationMapping($assocName);
@@ -2555,24 +2544,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* @param C $className
*
* @return string|null null if and only if the input value is null
* @phpstan-return (C is class-string ? class-string : (C is string ? string : null))
* @phpstan-return (C is class-string ? class-string : string)
*
* @template C of string|null
* @template C of string
*/
public function fullyQualifiedClassName(string|null $className): string|null
public function fullyQualifiedClassName(string $className): string
{
if ($className === null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11294',
'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0',
__METHOD__,
);
return null;
}
if (! str_contains($className, '\\') && $this->namespace) {
return $this->namespace . '\\' . $className;
}
@@ -2638,8 +2615,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
if (! empty($this->embeddedClasses[$property]->columnPrefix)) {
$fieldMapping['columnName'] = $this->embeddedClasses[$property]->columnPrefix . $fieldMapping['columnName'];
} elseif ($this->embeddedClasses[$property]->columnPrefix !== false) {
assert($this->reflClass !== null);
assert($embeddable->reflClass !== null);
$fieldMapping['columnName'] = $this->namingStrategy
->embeddedFieldToColumnName(
$property,
+21 -65
View File
@@ -4,10 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Common\EventManager;
use Doctrine\Common\EventDispatcher;
use Doctrine\DBAL\Platforms;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
@@ -19,11 +18,11 @@ use Doctrine\ORM\Id\IdentityGenerator;
use Doctrine\ORM\Id\SequenceGenerator;
use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator;
use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType;
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\Mapping\ReflectionService;
use Override;
use ReflectionClass;
use ReflectionException;
@@ -35,14 +34,11 @@ use function explode;
use function in_array;
use function is_a;
use function is_subclass_of;
use function method_exists;
use function str_contains;
use function strlen;
use function strtolower;
use function substr;
use const PHP_VERSION_ID;
/**
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
* metadata mapping information of a class which describes how a class should be mapped
@@ -55,21 +51,17 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private EntityManagerInterface|null $em = null;
private AbstractPlatform|null $targetPlatform = null;
private MappingDriver|null $driver = null;
private EventManager|null $evm = null;
private EventDispatcher|null $eventDispatcher = null;
/** @var mixed[] */
private array $embeddablesActiveNesting = [];
private const NON_IDENTITY_DEFAULT_STRATEGY = [
private const array NON_IDENTITY_DEFAULT_STRATEGY = [
Platforms\OraclePlatform::class => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
];
public function setEntityManager(EntityManagerInterface $em): void
{
if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
}
$this->em = $em;
}
@@ -107,22 +99,20 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
return $owningSide;
}
#[Override]
protected function initialize(): void
{
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
$this->evm = $this->em->getEventManager();
$this->initialized = true;
$this->driver = $this->em->getConfiguration()->getMetadataDriverImpl();
$this->eventDispatcher = $this->em->getEventManager();
$this->initialized = true;
}
#[Override]
protected function onNotFoundMetadata(string $className): ClassMetadata|null
{
if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
return null;
}
$eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em);
$this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
$this->eventDispatcher->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
$classMetadata = $eventArgs->getFoundMetadata();
assert($classMetadata instanceof ClassMetadata || $classMetadata === null);
@@ -132,6 +122,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* {@inheritDoc}
*/
#[Override]
protected function doLoadMetadata(
ClassMetadataInterface $class,
ClassMetadataInterface|null $parent,
@@ -245,10 +236,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
// During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402.
// So, we must not discover the missing subclasses before that.
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
$this->eventDispatcher->dispatchEvent(
Events::loadClassMetadata,
new LoadClassMetadataEventArgs($class, $this->em),
);
$this->findAbstractEntityClassesNotListedInDiscriminatorMap($class);
@@ -262,11 +253,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
*/
protected function validateRuntimeMetadata(ClassMetadata $class, ClassMetadataInterface|null $parent): void
{
if (! $class->reflClass) {
// only validate if there is a reflection class instance
return;
}
$class->validateIdentifier();
$class->validateAssociations();
$class->validateLifecycleCallbacks($this->getReflectionService());
@@ -302,6 +288,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
#[Override]
protected function newClassMetadataInstance(string $className): ClassMetadata
{
return new ClassMetadata(
@@ -626,39 +613,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
$nonIdentityDefaultStrategy = self::NON_IDENTITY_DEFAULT_STRATEGY;
// DBAL 3
if (method_exists($platform, 'getIdentitySequenceName')) {
$nonIdentityDefaultStrategy[Platforms\PostgreSQLPlatform::class] = ClassMetadata::GENERATOR_TYPE_SEQUENCE;
}
foreach ($nonIdentityDefaultStrategy as $platformFamily => $strategy) {
foreach (self::NON_IDENTITY_DEFAULT_STRATEGY as $platformFamily => $strategy) {
if (is_a($platform, $platformFamily)) {
if ($platform instanceof Platforms\PostgreSQLPlatform) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8893',
<<<'DEPRECATION'
Relying on non-optimal defaults for ID generation is deprecated, and IDENTITY
results in SERIAL, which is not recommended.
Instead, configure identifier generation strategies explicitly through
configuration.
We currently recommend "SEQUENCE" for "%s", when using DBAL 3,
and "IDENTITY" when using DBAL 4,
so you should probably use the following configuration before upgrading to DBAL 4,
and remove it after deploying that upgrade:
$configuration->setIdentityGenerationPreferences([
"%s" => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
]);
DEPRECATION,
$platformFamily,
$platformFamily,
);
}
return $strategy;
}
}
@@ -700,14 +656,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
#[Override]
protected function wakeupReflection(ClassMetadataInterface $class, ReflectionService $reflService): void
{
$class->wakeupReflection($reflService);
if (PHP_VERSION_ID < 80400) {
return;
}
foreach ($class->propertyAccessors as $propertyAccessor) {
$property = $propertyAccessor->getUnderlyingReflector();
@@ -717,11 +670,13 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
#[Override]
protected function initializeReflection(ClassMetadataInterface $class, ReflectionService $reflService): void
{
$class->initializeReflection($reflService);
}
#[Override]
protected function getDriver(): MappingDriver
{
assert($this->driver !== null);
@@ -729,6 +684,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
return $this->driver;
}
#[Override]
protected function isEntity(ClassMetadataInterface $class): bool
{
return ! $class->isMappedSuperclass;
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
use function trim;
/**
@@ -14,6 +16,7 @@ class DefaultEntityListenerResolver implements EntityListenerResolver
/** @var array<class-string, object> Map to store entity listener instances. */
private array $instances = [];
#[Override]
public function clear(string|null $className = null): void
{
if ($className === null) {
@@ -26,11 +29,13 @@ class DefaultEntityListenerResolver implements EntityListenerResolver
unset($this->instances[$className]);
}
#[Override]
public function register(object $object): void
{
$this->instances[$object::class] = $object;
}
#[Override]
public function resolve(string $className): object
{
$className = trim($className, '\\');
+9
View File
@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
use function str_contains;
use function strrpos;
use function strtolower;
@@ -16,6 +18,7 @@ use function substr;
*/
class DefaultNamingStrategy implements NamingStrategy
{
#[Override]
public function classToTableName(string $className): string
{
if (str_contains($className, '\\')) {
@@ -25,11 +28,13 @@ class DefaultNamingStrategy implements NamingStrategy
return $className;
}
#[Override]
public function propertyToColumnName(string $propertyName, string $className): string
{
return $propertyName;
}
#[Override]
public function embeddedFieldToColumnName(
string $propertyName,
string $embeddedColumnName,
@@ -39,16 +44,19 @@ class DefaultNamingStrategy implements NamingStrategy
return $propertyName . '_' . $embeddedColumnName;
}
#[Override]
public function referenceColumnName(): string
{
return 'id';
}
#[Override]
public function joinColumnName(string $propertyName, string $className): string
{
return $propertyName . '_' . $this->referenceColumnName();
}
#[Override]
public function joinTableName(
string $sourceEntity,
string $targetEntity,
@@ -58,6 +66,7 @@ class DefaultNamingStrategy implements NamingStrategy
$this->classToTableName($targetEntity));
}
#[Override]
public function joinKeyColumnName(
string $entityName,
string|null $referencedColumnName,
+9
View File
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Internal\SQLResultCasing;
use Override;
use function array_map;
use function array_merge;
@@ -24,6 +25,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
{
use SQLResultCasing;
#[Override]
public function getColumnName(string $fieldName, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($class->fieldMappings[$fieldName]->quoted)
@@ -36,6 +38,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
*
* @todo Table names should be computed in DBAL depending on the platform
*/
#[Override]
public function getTableName(ClassMetadata $class, AbstractPlatform $platform): string
{
$tableName = $class->table['name'];
@@ -58,6 +61,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($definition['quoted'])
@@ -68,6 +72,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
: $definition['sequenceName'];
}
#[Override]
public function getJoinColumnName(JoinColumnMapping $joinColumn, ClassMetadata $class, AbstractPlatform $platform): string
{
return isset($joinColumn->quoted)
@@ -75,6 +80,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
: $joinColumn->name;
}
#[Override]
public function getReferencedJoinColumnName(
JoinColumnMapping $joinColumn,
ClassMetadata $class,
@@ -85,6 +91,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
: $joinColumn->referencedColumnName;
}
#[Override]
public function getJoinTableName(
ManyToManyOwningSideMapping $association,
ClassMetadata $class,
@@ -108,6 +115,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
/**
* {@inheritDoc}
*/
#[Override]
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform): array
{
$quotedColumnNames = [];
@@ -136,6 +144,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
return $quotedColumnNames;
}
#[Override]
public function getColumnAlias(
string $columnName,
int $counter,
+4 -5
View File
@@ -11,6 +11,7 @@ use DateTime;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Override;
use ReflectionEnum;
use ReflectionNamedType;
use ReflectionProperty;
@@ -27,7 +28,7 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
/** @var array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
private array $typedFieldMappings;
private const DEFAULT_TYPED_FIELD_MAPPINGS = [
private const array DEFAULT_TYPED_FIELD_MAPPINGS = [
DateInterval::class => Types::DATEINTERVAL,
DateTime::class => Types::DATETIME_MUTABLE,
DateTimeImmutable::class => Types::DATETIME_IMMUTABLE,
@@ -52,6 +53,7 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
/**
* {@inheritDoc}
*/
#[Override]
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
$type = $field->getType();
@@ -63,10 +65,7 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
if (
! $type->isBuiltin()
&& enum_exists($type->getName())
&& (! isset($mapping['type']) || (
defined('Doctrine\DBAL\Types\Types::ENUM')
&& $mapping['type'] === Types::ENUM
))
&& (! isset($mapping['type']) || $mapping['type'] === Types::ENUM)
) {
$reflection = new ReflectionEnum($type->getName());
if (! $reflection->isBacked()) {
+1 -5
View File
@@ -4,18 +4,14 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use BackedEnum;
use Exception;
use function in_array;
use function property_exists;
/** @template-implements ArrayAccess<string, mixed> */
final class DiscriminatorColumnMapping implements ArrayAccess
final class DiscriminatorColumnMapping
{
use ArrayAccessImplementation;
/** The database length of the column. Optional. Default value taken from the type. */
public int|null $length = null;
+13 -16
View File
@@ -10,9 +10,10 @@ use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use InvalidArgumentException;
use Override;
use ReflectionClass;
use ReflectionMethod;
@@ -20,37 +21,32 @@ use function assert;
use function class_exists;
use function constant;
use function defined;
use function sprintf;
class AttributeDriver implements MappingDriver
{
use ColocatedMappingDriver;
use ReflectionBasedDriver;
private const ENTITY_ATTRIBUTE_CLASSES = [
private const array ENTITY_ATTRIBUTE_CLASSES = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
private readonly AttributeReader $reader;
/**
* @param array<string> $paths
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
*/
public function __construct(array $paths, bool $reportFieldsWhereDeclared = true)
/** @param string[]|ClassLocator $paths a ClassLocator, or an array of directories. */
public function __construct(array|ClassLocator $paths)
{
if (! $reportFieldsWhereDeclared) {
throw new InvalidArgumentException(sprintf(
'The $reportFieldsWhereDeclared argument is no longer supported, make sure to omit it when calling %s.',
__METHOD__,
));
}
$this->reader = new AttributeReader();
$this->addPaths($paths);
if ($paths instanceof ClassLocator) {
$this->classLocator = $paths;
} else {
$this->addPaths($paths);
}
}
#[Override]
public function isTransient(string $className): bool
{
$classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
@@ -73,6 +69,7 @@ class AttributeDriver implements MappingDriver
*
* @template T of object
*/
#[Override]
public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void
{
$reflectionClass = $metadata->getReflectionClass()
-639
View File
@@ -1,639 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\DBAL\Schema\AbstractAsset;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Index;
use Doctrine\DBAL\Schema\Index\IndexedColumn;
use Doctrine\DBAL\Schema\Index\IndexType;
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
use Doctrine\DBAL\Schema\NamedObject;
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use InvalidArgumentException;
use TypeError;
use function array_diff;
use function array_keys;
use function array_map;
use function array_merge;
use function assert;
use function count;
use function current;
use function enum_exists;
use function get_debug_type;
use function in_array;
use function method_exists;
use function preg_replace;
use function sort;
use function sprintf;
use function strtolower;
/**
* The DatabaseDriver reverse engineers the mapping metadata from a database.
*
* @deprecated No replacement planned
*
* @link www.doctrine-project.org
*/
class DatabaseDriver implements MappingDriver
{
/**
* Replacement for {@see Types::ARRAY}.
*
* To be removed as soon as support for DBAL 3 is dropped.
*/
private const ARRAY = 'array';
/**
* Replacement for {@see Types::OBJECT}.
*
* To be removed as soon as support for DBAL 3 is dropped.
*/
private const OBJECT = 'object';
/** @var array<string,Table>|null */
private array|null $tables = null;
/** @var array<class-string, string> */
private array $classToTableNames = [];
/** @phpstan-var array<string, Table> */
private array $manyToManyTables = [];
/** @var mixed[] */
private array $classNamesForTables = [];
/** @var mixed[] */
private array $fieldNamesForColumns = [];
/**
* The namespace for the generated entities.
*/
private string|null $namespace = null;
private Inflector $inflector;
public function __construct(private readonly AbstractSchemaManager $sm)
{
$this->inflector = InflectorFactory::create()->build();
}
/**
* Set the namespace for the generated entities.
*/
public function setNamespace(string $namespace): void
{
$this->namespace = $namespace;
}
public function isTransient(string $className): bool
{
return true;
}
/**
* {@inheritDoc}
*/
public function getAllClassNames(): array
{
$this->reverseEngineerMappingFromDatabase();
return array_keys($this->classToTableNames);
}
/**
* Sets class name for a table.
*/
public function setClassNameForTable(string $tableName, string $className): void
{
$this->classNamesForTables[$tableName] = $className;
}
/**
* Sets field name for a column on a specific table.
*/
public function setFieldNameForColumn(string $tableName, string $columnName, string $fieldName): void
{
$this->fieldNamesForColumns[$tableName][$columnName] = $fieldName;
}
/**
* Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager.
*
* @param Table[] $entityTables
* @param Table[] $manyToManyTables
* @phpstan-param list<Table> $entityTables
* @phpstan-param list<Table> $manyToManyTables
*/
public function setTables(array $entityTables, array $manyToManyTables): void
{
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($entityTables as $table) {
$className = $this->getClassNameForTable(self::getAssetName($table));
$this->classToTableNames[$className] = self::getAssetName($table);
$this->tables[self::getAssetName($table)] = $table;
}
foreach ($manyToManyTables as $table) {
$this->manyToManyTables[self::getAssetName($table)] = $table;
}
}
public function setInflector(Inflector $inflector): void
{
$this->inflector = $inflector;
}
/**
* {@inheritDoc}
*
* @param class-string<T> $className
* @param ClassMetadata<T> $metadata
*
* @template T of object
*/
public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void
{
if (! $metadata instanceof ClassMetadata) {
throw new TypeError(sprintf(
'Argument #2 passed to %s() must be an instance of %s, %s given.',
__METHOD__,
ClassMetadata::class,
get_debug_type($metadata),
));
}
$this->reverseEngineerMappingFromDatabase();
if (! isset($this->classToTableNames[$className])) {
throw new InvalidArgumentException('Unknown class ' . $className);
}
$tableName = $this->classToTableNames[$className];
$metadata->name = $className;
$metadata->table['name'] = $tableName;
$this->buildIndexes($metadata);
$this->buildFieldMappings($metadata);
$this->buildToOneAssociationMappings($metadata);
foreach ($this->manyToManyTables as $manyTable) {
foreach ($manyTable->getForeignKeys() as $foreignKey) {
// foreign key maps to the table of the current entity, many to many association probably exists
if (! (strtolower($tableName) === strtolower(self::getReferencedTableName($foreignKey)))) {
continue;
}
$myFk = $foreignKey;
$otherFk = null;
foreach ($manyTable->getForeignKeys() as $foreignKey) {
if ($foreignKey !== $myFk) {
$otherFk = $foreignKey;
break;
}
}
if (! $otherFk) {
// the definition of this many to many table does not contain
// enough foreign key information to continue reverse engineering.
continue;
}
$localColumn = current(self::getReferencingColumnNames($myFk));
$associationMapping = [];
$associationMapping['fieldName'] = $this->getFieldNameForColumn(self::getAssetName($manyTable), current(self::getReferencingColumnNames($otherFk)), true);
$associationMapping['targetEntity'] = $this->getClassNameForTable(self::getReferencedTableName($otherFk));
if (self::getAssetName(current($manyTable->getColumns())) === $localColumn) {
$associationMapping['inversedBy'] = $this->getFieldNameForColumn(self::getAssetName($manyTable), current(self::getReferencingColumnNames($myFk)), true);
$associationMapping['joinTable'] = [
'name' => strtolower(self::getAssetName($manyTable)),
'joinColumns' => [],
'inverseJoinColumns' => [],
];
$fkCols = self::getReferencedColumnNames($myFk);
$cols = self::getReferencingColumnNames($myFk);
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['joinColumns'][] = [
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
];
}
$fkCols = self::getReferencedColumnNames($otherFk);
$cols = self::getReferencingColumnNames($otherFk);
for ($i = 0, $colsCount = count($cols); $i < $colsCount; $i++) {
$associationMapping['joinTable']['inverseJoinColumns'][] = [
'name' => $cols[$i],
'referencedColumnName' => $fkCols[$i],
];
}
} else {
$associationMapping['mappedBy'] = $this->getFieldNameForColumn(
// @phpstan-ignore function.alreadyNarrowedType (DBAL 3 compatibility)
method_exists(Table::class, 'getObjectName')
? $manyTable->getObjectName()->toString()
: $manyTable->getName(), // DBAL < 4.4
current(self::getReferencingColumnNames($myFk)),
true,
);
}
$metadata->mapManyToMany($associationMapping);
break;
}
}
}
/** @throws MappingException */
private function reverseEngineerMappingFromDatabase(): void
{
if ($this->tables !== null) {
return;
}
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($this->sm->listTables() as $table) {
$tableName = self::getAssetName($table);
$foreignKeys = $table->getForeignKeys();
$allForeignKeyColumns = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeyColumns = array_merge($allForeignKeyColumns, self::getReferencingColumnNames($foreignKey));
}
if (method_exists($table, 'getPrimaryKeyConstraint')) {
$primaryKey = $table->getPrimaryKeyConstraint();
} else {
$primaryKey = $table->getPrimaryKey();
}
if ($primaryKey === null) {
throw new MappingException(
'Table ' . $tableName . ' has no primary key. Doctrine does not ' .
"support reverse engineering from tables that don't have a primary key.",
);
}
if ($primaryKey instanceof PrimaryKeyConstraint) {
$pkColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKey->getColumnNames());
} else {
$pkColumns = self::getIndexedColumns($primaryKey);
}
sort($pkColumns);
sort($allForeignKeyColumns);
if ($pkColumns === $allForeignKeyColumns && count($foreignKeys) === 2) {
$this->manyToManyTables[$tableName] = $table;
} else {
// lower-casing is necessary because of Oracle Uppercase Tablenames,
// assumption is lower-case + underscore separated.
$className = $this->getClassNameForTable($tableName);
$this->tables[$tableName] = $table;
$this->classToTableNames[$className] = $tableName;
}
}
}
/**
* Build indexes from a class metadata.
*/
private function buildIndexes(ClassMetadata $metadata): void
{
$tableName = $metadata->table['name'];
$table = $this->tables[$tableName];
$primaryKey = self::getPrimaryKey($table);
$indexes = $table->getIndexes();
foreach ($indexes as $index) {
if ($index === $primaryKey) {
continue;
}
if (enum_exists(IndexType::class) && method_exists($index, 'getType')) {
$isUnique = $index->getType() === IndexType::UNIQUE;
} else {
$isUnique = $index->isUnique();
}
$indexName = self::getAssetName($index);
$indexColumns = self::getIndexedColumns($index);
$constraintType = $isUnique
? 'uniqueConstraints'
: 'indexes';
$metadata->table[$constraintType][$indexName]['columns'] = $indexColumns;
}
}
/**
* Build field mapping from class metadata.
*/
private function buildFieldMappings(ClassMetadata $metadata): void
{
$tableName = $metadata->table['name'];
$columns = $this->tables[$tableName]->getColumns();
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
$allForeignKeys = [];
foreach ($foreignKeys as $foreignKey) {
$allForeignKeys = array_merge($allForeignKeys, self::getReferencingColumnNames($foreignKey));
}
$ids = [];
$fieldMappings = [];
foreach ($columns as $column) {
if (in_array(self::getAssetName($column), $allForeignKeys, true)) {
continue;
}
$fieldMapping = $this->buildFieldMapping($tableName, $column);
if ($primaryKeys && in_array(self::getAssetName($column), $primaryKeys, true)) {
$fieldMapping['id'] = true;
$ids[] = $fieldMapping;
}
$fieldMappings[] = $fieldMapping;
}
// We need to check for the columns here, because we might have associations as id as well.
if ($ids && count($primaryKeys) === 1) {
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_AUTO);
}
foreach ($fieldMappings as $fieldMapping) {
$metadata->mapField($fieldMapping);
}
}
/**
* Build field mapping from a schema column definition
*
* @return mixed[]
* @phpstan-return array{
* fieldName: string,
* columnName: string,
* type: string,
* nullable: bool,
* options: array{
* unsigned?: bool,
* fixed?: bool,
* comment: string|null,
* default?: mixed
* },
* precision?: int,
* scale?: int,
* length?: int|null
* }
*/
private function buildFieldMapping(string $tableName, Column $column): array
{
$fieldMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, self::getAssetName($column), false),
'columnName' => self::getAssetName($column),
'type' => Type::getTypeRegistry()->lookupName($column->getType()),
'nullable' => ! $column->getNotnull(),
'options' => [
'comment' => $column->getComment(),
],
];
// Type specific elements
switch ($fieldMapping['type']) {
case self::ARRAY:
case Types::BLOB:
case Types::GUID:
case self::OBJECT:
case Types::SIMPLE_ARRAY:
case Types::STRING:
case Types::TEXT:
$fieldMapping['length'] = $column->getLength();
$fieldMapping['options']['fixed'] = $column->getFixed();
break;
case Types::DECIMAL:
case Types::FLOAT:
$fieldMapping['precision'] = $column->getPrecision();
$fieldMapping['scale'] = $column->getScale();
break;
case Types::INTEGER:
case Types::BIGINT:
case Types::SMALLINT:
$fieldMapping['options']['unsigned'] = $column->getUnsigned();
break;
}
// Default
$default = $column->getDefault();
if ($default !== null) {
$fieldMapping['options']['default'] = $default;
}
return $fieldMapping;
}
/**
* Build to one (one to one, many to one) association mapping from class metadata.
*/
private function buildToOneAssociationMappings(ClassMetadata $metadata): void
{
assert($this->tables !== null);
$tableName = $metadata->table['name'];
$primaryKeys = $this->getTablePrimaryKeys($this->tables[$tableName]);
$foreignKeys = $this->tables[$tableName]->getForeignKeys();
foreach ($foreignKeys as $foreignKey) {
$foreignTableName = self::getReferencedTableName($foreignKey);
$fkColumns = self::getReferencingColumnNames($foreignKey);
$fkForeignColumns = self::getReferencedColumnNames($foreignKey);
$localColumn = current($fkColumns);
$associationMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, $localColumn, true),
'targetEntity' => $this->getClassNameForTable($foreignTableName),
];
if (isset($metadata->fieldMappings[$associationMapping['fieldName']])) {
$associationMapping['fieldName'] .= '2'; // "foo" => "foo2"
}
if ($primaryKeys && in_array($localColumn, $primaryKeys, true)) {
$associationMapping['id'] = true;
}
for ($i = 0, $fkColumnsCount = count($fkColumns); $i < $fkColumnsCount; $i++) {
$associationMapping['joinColumns'][] = [
'name' => $fkColumns[$i],
'referencedColumnName' => $fkForeignColumns[$i],
];
}
// Here we need to check if $fkColumns are the same as $primaryKeys
if (! array_diff($fkColumns, $primaryKeys)) {
$metadata->mapOneToOne($associationMapping);
} else {
$metadata->mapManyToOne($associationMapping);
}
}
}
/**
* Retrieve schema table definition primary keys.
*
* @return string[]
*/
private function getTablePrimaryKeys(Table $table): array
{
try {
if (method_exists($table, 'getPrimaryKeyConstraint')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $table->getPrimaryKeyConstraint()->getColumnNames());
}
return self::getIndexedColumns($table->getPrimaryKey());
} catch (SchemaException) {
// Do nothing
}
return [];
}
/**
* Returns the mapped class name for a table if it exists. Otherwise return "classified" version.
*
* @return class-string
*/
private function getClassNameForTable(string $tableName): string
{
if (isset($this->classNamesForTables[$tableName])) {
return $this->namespace . $this->classNamesForTables[$tableName];
}
return $this->namespace . $this->inflector->classify(strtolower($tableName));
}
/**
* Return the mapped field name for a column, if it exists. Otherwise return camelized version.
*
* @param bool $fk Whether the column is a foreignkey or not.
*/
private function getFieldNameForColumn(
string $tableName,
string $columnName,
bool $fk = false,
): string {
if (isset($this->fieldNamesForColumns[$tableName], $this->fieldNamesForColumns[$tableName][$columnName])) {
return $this->fieldNamesForColumns[$tableName][$columnName];
}
$columnName = strtolower($columnName);
// Replace _id if it is a foreignkey column
if ($fk) {
$columnName = preg_replace('/_id$/', '', $columnName);
}
return $this->inflector->camelize($columnName);
}
private static function getReferencedTableName(ForeignKeyConstraint $foreignKey): string
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencedTableName')) {
return $foreignKey->getReferencedTableName()->toString();
}
return $foreignKey->getForeignTableName();
}
/** @return string[] */
private static function getReferencingColumnNames(ForeignKeyConstraint $foreignKey): array
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencingColumnNames')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $foreignKey->getReferencingColumnNames());
}
return $foreignKey->getLocalColumns();
}
/** @return string[] */
private static function getReferencedColumnNames(ForeignKeyConstraint $foreignKey): array
{
if (method_exists(ForeignKeyConstraint::class, 'getReferencedColumnNames')) {
return array_map(static fn (UnqualifiedName $name) => $name->toString(), $foreignKey->getReferencedColumnNames());
}
return $foreignKey->getForeignColumns();
}
/** @return string[] */
private static function getIndexedColumns(Index $index): array
{
if (method_exists(Index::class, 'getIndexedColumns')) {
return array_map(static fn (IndexedColumn $indexedColumn) => $indexedColumn->getColumnName()->toString(), $index->getIndexedColumns());
}
return $index->getColumns();
}
private static function getPrimaryKey(Table $table): Index|null
{
$primaryKeyConstraint = null;
if (method_exists(Table::class, 'getPrimaryKeyConstraint')) {
$primaryKeyConstraint = $table->getPrimaryKeyConstraint();
}
foreach ($table->getIndexes() as $index) {
if ($primaryKeyConstraint !== null) {
$primaryKeyConstraintColumns = array_map(static fn (UnqualifiedName $name) => $name->toString(), $primaryKeyConstraint->getColumnNames());
if ($primaryKeyConstraintColumns === self::getIndexedColumns($index)) {
return $index;
}
} elseif ($index->isPrimary()) {
return $index;
}
}
return null;
}
private static function getAssetName(AbstractAsset $asset): string
{
return $asset instanceof NamedObject
? $asset->getObjectName()->toString()
// DBAL < 4.4
: $asset->getName();
}
}
@@ -1,35 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Persistence\Mapping\StaticReflectionService;
use function class_exists;
if (! class_exists(StaticReflectionService::class)) {
/** @internal */
trait LoadMappingFileImplementation
{
/**
* {@inheritDoc}
*/
protected function loadMappingFile($file): array
{
return $this->doLoadMappingFile($file);
}
}
} else {
/** @internal */
trait LoadMappingFileImplementation
{
/**
* {@inheritDoc}
*/
protected function loadMappingFile($file)
{
return $this->doLoadMappingFile($file);
}
}
}

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