Compare commits

..

149 Commits
3.6.3 ... 4.0.x

Author SHA1 Message Date
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
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
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
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
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
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
7376adede8 Merge pull request #12342 from greg0ire/override
Add Override attribute where applicable
2026-01-13 11:54:57 +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
Carlos Fernandes
f8bbdc40b0 Add dispatchPreFlushEvent() method and avoid calling getConnection() twice 2025-12-30 18:35:45 +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
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
262c396639 doc: update minimal version of PHP (#12312) 2025-11-30 00:31:21 +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
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
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
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
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
393 changed files with 1661 additions and 6028 deletions

View File

@@ -7,7 +7,3 @@ updates:
labels:
- "CI"
target-branch: "2.20.x"
groups:
doctrine:
patterns:
- "doctrine/*"

View File

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

View File

@@ -17,4 +17,4 @@ on:
jobs:
composer-lint:
name: "Composer Lint"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@14.0.0"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@13.1.0"

View File

@@ -38,14 +38,10 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
extension:
- "sqlite3"
- "pdo_sqlite"
@@ -53,37 +49,30 @@ jobs:
- "highest"
stability:
- "stable"
native_lazy:
- "0"
include:
- php-version: "8.2"
- php-version: "8.4"
dbal-version: "4@dev"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "0"
- php-version: "8.2"
- php-version: "8.4"
dbal-version: "4@dev"
extension: "sqlite3"
stability: "stable"
native_lazy: "0"
- php-version: "8.1"
- php-version: "8.4"
dbal-version: "default"
deps: "lowest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "0"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "pdo_sqlite"
stability: "stable"
native_lazy: "1"
- php-version: "8.4"
dbal-version: "default"
deps: "highest"
extension: "sqlite3"
stability: "dev"
native_lazy: "1"
steps:
- name: "Checkout"
@@ -111,12 +100,8 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Downgrade VarExporter"
run: 'composer require --no-update "symfony/var-exporter:^6.4 || ^7.4"'
if: "${{ matrix.native_lazy == '0' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v4"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
dependency-versions: "${{ matrix.deps }}"
@@ -125,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: |
@@ -147,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@v7"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-${{ matrix.native_lazy }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-coverage"
path: "coverage*.xml"
@@ -204,26 +187,23 @@ jobs:
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:
@@ -257,7 +237,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v4"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
@@ -285,13 +265,10 @@ jobs:
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
- "4@dev"
mariadb-version:
- "11.4"
@@ -331,7 +308,7 @@ jobs:
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v4"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
@@ -359,13 +336,10 @@ jobs:
strategy:
matrix:
php-version:
- "8.2"
- "8.3"
- "8.4"
- "8.5"
dbal-version:
- "default"
- "3.7"
mysql-version:
- "5.7"
- "8.0"
@@ -373,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"
@@ -413,7 +387,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v4"
uses: "ramsey/composer-install@v3"
with:
composer-options: "--ignore-platform-req=php+"
@@ -470,7 +444,7 @@ jobs:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v6"
uses: "codecov/codecov-action@v5"
with:
directory: reports
env:

View File

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

View File

@@ -32,7 +32,7 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.4"
steps:
- name: "Checkout"
@@ -48,7 +48,7 @@ jobs:
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v4"
uses: "ramsey/composer-install@v3"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"

View File

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

View File

@@ -25,14 +25,6 @@ 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@v6"
@@ -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@v4
uses: ramsey/composer-install@v2
- 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"

View File

@@ -3,7 +3,7 @@
| [![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

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

View File

@@ -31,20 +31,19 @@
],
"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 || ^8.0",
"symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0"
"symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^14.0",
@@ -52,9 +51,10 @@
"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 || ^8.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",

View File

@@ -33,18 +33,7 @@ steps of configuration.
$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**)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -268,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
---------------------------------------
@@ -440,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
-------------------------

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

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. See :ref:`reference-enum-mapping`.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``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.
@@ -321,160 +321,6 @@ 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

View File

@@ -1,24 +0,0 @@
<?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 = [];
}

View File

@@ -1,19 +0,0 @@
<?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;
}

View File

@@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Entity;
enum Suit: string
{
case Hearts = 'hearts';
case Diamonds = 'diamonds';
case Clubs = 'clubs';
case Spades = 'spades';
}

View File

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

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

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>

View File

@@ -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,96 +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 \#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
@@ -1518,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
@@ -1602,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
@@ -1681,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
@@ -1693,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
@@ -1818,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
@@ -2106,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
@@ -2184,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
@@ -2214,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
@@ -2484,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
@@ -2556,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\:\: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\|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,12 +2310,6 @@ parameters:
count: 1
path: src/Query/Expr/Func.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
@@ -2826,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
@@ -2838,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
@@ -3429,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
@@ -3453,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
@@ -3548,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

View File

@@ -1,162 +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
-
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
path: src/Mapping/Driver/DatabaseDriver.php
-
message: '#^Instantiated class Doctrine\\DBAL\\Schema\\DefaultExpression\\\w+ not found\.$#'
identifier: class.notFound
path: src/Tools/SchemaTool.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

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,46 +13,3 @@ parameters:
-
message: '~^Match expression does not handle remaining values:~'
path: src/Utility/PersisterHelper.php
# The return type is already narrow enough.
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns ''[a-z_]+'' so it can be removed from the return type\.$~'
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns Doctrine\\DBAL\\(?:Array)?ParameterType\:\:[A-Z_]+ so it can be removed from the return type\.$~'
# 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

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

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;

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

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

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];

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;

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;

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

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;
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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

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;

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

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:

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

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

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

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

View File

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

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

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

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;

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;

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
{

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

View File

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

View File

@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\ORM\Exception\MultipleSelectorsFoundException;
use Override;
use function count;
@@ -20,6 +21,7 @@ final class ScalarColumnHydrator extends AbstractHydrator
* @throws MultipleSelectorsFoundException
* @throws Exception
*/
#[Override]
protected function hydrateAllData(): array
{
if (count($this->resultSetMapping()->fieldMappings) > 1) {

View File

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

View File

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

View File

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

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.

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.

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

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,

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

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.

View File

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

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Builder;
use Override;
/**
* ManyToMany Association Builder
*
@@ -29,6 +31,7 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
*
* @return $this
*/
#[Override]
public function addJoinColumn(
string $columnName,
string $referencedColumnName,
@@ -72,6 +75,7 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
return $this;
}
#[Override]
public function build(): ClassMetadataBuilder
{
$mapping = $this->mapping;

View File

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

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

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,15 +535,6 @@ 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, PropertyAccessor> */
public array $propertyAccessors = [];
@@ -568,19 +557,6 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
$this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper();
}
/**
* 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.
*
@@ -591,35 +567,11 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
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])
@@ -2315,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])
@@ -2521,6 +2478,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getFieldNames(): array
{
return array_keys($this->fieldMappings);
@@ -2529,6 +2487,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
/**
* {@inheritDoc}
*/
#[Override]
public function getAssociationNames(): array
{
return array_keys($this->associationMappings);
@@ -2541,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);
@@ -2581,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;
}
@@ -2664,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,

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;

View File

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

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,

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,

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()) {

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;

View File

@@ -13,7 +13,7 @@ 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;
@@ -21,33 +21,22 @@ 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 string[]|ClassLocator $paths a ClassLocator, or an array of directories.
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
*/
public function __construct(array|ClassLocator $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();
if ($paths instanceof ClassLocator) {
@@ -57,6 +46,7 @@ class AttributeDriver implements MappingDriver
}
}
#[Override]
public function isTransient(string $className): bool
{
$classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
@@ -79,6 +69,7 @@ class AttributeDriver implements MappingDriver
*
* @template T of object
*/
#[Override]
public function loadMetadataForClass(string $className, PersistenceClassMetadata $metadata): void
{
$reflectionClass = $metadata->getReflectionClass()

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

View File

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

View File

@@ -11,7 +11,7 @@ use Doctrine\Persistence\Mapping\Driver\SymfonyFileLocator;
*/
class SimplifiedXmlDriver extends XmlDriver
{
public const DEFAULT_FILE_EXTENSION = '.orm.xml';
final public const string DEFAULT_FILE_EXTENSION = '.orm.xml';
/**
* {@inheritDoc}

View File

@@ -13,6 +13,7 @@ use Doctrine\Persistence\Mapping\Driver\FileLocator;
use DOMDocument;
use InvalidArgumentException;
use LogicException;
use Override;
use SimpleXMLElement;
use function assert;
@@ -41,9 +42,7 @@ use function strtoupper;
*/
class XmlDriver extends FileDriver
{
use LoadMappingFileImplementation;
public const DEFAULT_FILE_EXTENSION = '.dcm.xml';
public const string DEFAULT_FILE_EXTENSION = '.dcm.xml';
/**
* {@inheritDoc}
@@ -77,6 +76,7 @@ class XmlDriver extends FileDriver
*
* @template T of object
*/
#[Override]
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
{
$xmlRoot = $this->getElement($className);
@@ -916,8 +916,13 @@ class XmlDriver extends FileDriver
return $cascades;
}
/** @return array<class-string, SimpleXMLElement> */
private function doLoadMappingFile(string $file): array
/**
* {@inheritDoc}
*
* @return array<class-string, SimpleXMLElement>
*/
#[Override]
protected function loadMappingFile(string $file): array
{
$this->validateMapping($file);
$result = [];

View File

@@ -4,15 +4,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use function property_exists;
/** @template-implements ArrayAccess<string, mixed> */
final class EmbeddedClassMapping implements ArrayAccess
final class EmbeddedClassMapping
{
use ArrayAccessImplementation;
public string|false|null $columnPrefix = null;
public string|null $declaredField = null;
public string|null $originalField = null;

View File

@@ -4,17 +4,13 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use BackedEnum;
use function in_array;
use function property_exists;
/** @template-implements ArrayAccess<string, mixed> */
final class FieldMapping implements ArrayAccess
final class FieldMapping
{
use ArrayAccessImplementation;
/** The database length of the column. Optional. Default value taken from the type. */
public int|null $length = null;
/**
@@ -72,9 +68,6 @@ final class FieldMapping implements ArrayAccess
public array|null $options = null;
public bool|null $version = null;
/** @deprecated Use options with 'default' key instead */
public string|int|null $default = null;
/**
* @param string $type The type name of the mapped field. Can be one of
* Doctrine's mapping types or a custom mapping type.
@@ -162,7 +155,6 @@ final class FieldMapping implements ArrayAccess
'declared',
'declaredField',
'options',
'default',
] as $key
) {
if ($this->$key !== null) {

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Persistence\Mapping\StaticReflectionService;
use ReflectionClass;
use function class_exists;
if (! class_exists(StaticReflectionService::class)) {
trait GetReflectionClassImplementation
{
public function getReflectionClass(): ReflectionClass
{
return $this->reflClass;
}
}
} else {
trait GetReflectionClassImplementation
{
/**
* {@inheritDoc}
*
* Can return null when using static reflection, in violation of the LSP
*/
public function getReflectionClass(): ReflectionClass|null
{
return $this->reflClass;
}
}
}

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
abstract class InverseSideMapping extends AssociationMapping
{
/**
@@ -20,6 +22,7 @@ abstract class InverseSideMapping extends AssociationMapping
}
/** @return list<string> */
#[Override]
public function __sleep(): array
{
return [

View File

@@ -4,15 +4,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use function property_exists;
/** @template-implements ArrayAccess<string, mixed> */
final class JoinColumnMapping implements ArrayAccess
final class JoinColumnMapping
{
use ArrayAccessImplementation;
public bool|null $deferrable = null;
public bool|null $unique = null;
public bool|null $quoted = null;

View File

@@ -4,16 +4,11 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use function array_map;
use function in_array;
/** @template-implements ArrayAccess<string, mixed> */
final class JoinTableMapping implements ArrayAccess
final class JoinTableMapping
{
use ArrayAccessImplementation;
public bool|null $quoted = null;
/** @var list<JoinColumnMapping> */

View File

@@ -1,170 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ArrayAccess;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Persistence\Mapping\ReflectionService;
use Doctrine\Persistence\Reflection\EnumReflectionProperty;
use Generator;
use IteratorAggregate;
use OutOfBoundsException;
use ReflectionProperty;
use Traversable;
use function array_keys;
use function assert;
use function is_string;
use function str_contains;
use function str_replace;
/**
* @template-implements ArrayAccess<string, ReflectionProperty|null>
* @template-implements IteratorAggregate<string, ReflectionProperty|null>
*/
class LegacyReflectionFields implements ArrayAccess, IteratorAggregate
{
/** @var array<string, ReflectionProperty|null> */
private array $reflFields = [];
public function __construct(private ClassMetadata $classMetadata, private ReflectionService $reflectionService)
{
}
/** @param string $offset */
public function offsetExists($offset): bool // phpcs:ignore
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11659',
'Access to ClassMetadata::$reflFields is deprecated and will be removed in Doctrine ORM 4.0.',
);
return isset($this->classMetadata->propertyAccessors[$offset]);
}
/**
* @param string $field
*
* @psalm-suppress LessSpecificImplementedReturnType
*/
public function offsetGet($field): mixed // phpcs:ignore
{
if (isset($this->reflFields[$field])) {
return $this->reflFields[$field];
}
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11659',
'Access to ClassMetadata::$reflFields is deprecated and will be removed in Doctrine ORM 4.0.',
);
if (isset($this->classMetadata->propertyAccessors[$field])) {
$fieldName = str_contains($field, '.') ? $this->classMetadata->fieldMappings[$field]->originalField : $field;
$className = $this->classMetadata->name;
assert(is_string($fieldName));
if (isset($this->classMetadata->fieldMappings[$field]) && $this->classMetadata->fieldMappings[$field]->originalClass !== null) {
$className = $this->classMetadata->fieldMappings[$field]->originalClass;
} elseif (isset($this->classMetadata->fieldMappings[$field]) && $this->classMetadata->fieldMappings[$field]->declared !== null) {
$className = $this->classMetadata->fieldMappings[$field]->declared;
} elseif (isset($this->classMetadata->associationMappings[$field]) && $this->classMetadata->associationMappings[$field]->declared !== null) {
$className = $this->classMetadata->associationMappings[$field]->declared;
} elseif (isset($this->classMetadata->embeddedClasses[$field]) && $this->classMetadata->embeddedClasses[$field]->declared !== null) {
$className = $this->classMetadata->embeddedClasses[$field]->declared;
}
/** @psalm-suppress ArgumentTypeCoercion */
$this->reflFields[$field] = $this->getAccessibleProperty($className, $fieldName);
if (isset($this->classMetadata->fieldMappings[$field])) {
if ($this->classMetadata->fieldMappings[$field]->enumType !== null) {
$this->reflFields[$field] = new EnumReflectionProperty(
$this->reflFields[$field],
$this->classMetadata->fieldMappings[$field]->enumType,
);
}
if ($this->classMetadata->fieldMappings[$field]->originalField !== null) {
$parentField = str_replace('.' . $fieldName, '', $field);
$originalClass = $this->classMetadata->fieldMappings[$field]->originalClass;
if (! str_contains($parentField, '.')) {
$parentClass = $this->classMetadata->name;
} else {
$parentClass = $this->classMetadata->fieldMappings[$parentField]->originalClass;
}
/** @psalm-var class-string $parentClass */
/** @psalm-var class-string $originalClass */
$this->reflFields[$field] = new ReflectionEmbeddedProperty(
$this->getAccessibleProperty($parentClass, $parentField),
$this->reflFields[$field],
$originalClass,
);
}
}
return $this->reflFields[$field];
}
throw new OutOfBoundsException('Unknown field: ' . $this->classMetadata->name . ' ::$' . $field);
}
/**
* @param string $offset
* @param ReflectionProperty $value
*/
public function offsetSet($offset, $value): void // phpcs:ignore
{
$this->reflFields[$offset] = $value;
}
/** @param string $offset */
public function offsetUnset($offset): void // phpcs:ignore
{
unset($this->reflFields[$offset]);
}
/** @psalm-param class-string $class */
private function getAccessibleProperty(string $class, string $field): ReflectionProperty
{
$reflectionProperty = $this->reflectionService->getAccessibleProperty($class, $field);
assert($reflectionProperty !== null);
if ($reflectionProperty->isReadOnly()) {
$declaringClass = $reflectionProperty->class;
if ($declaringClass !== $class) {
$reflectionProperty = $this->reflectionService->getAccessibleProperty($declaringClass, $field);
assert($reflectionProperty !== null);
}
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
}
return $reflectionProperty;
}
/** @return Generator<string, ReflectionProperty> */
public function getIterator(): Traversable
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11659',
'Access to ClassMetadata::$reflFields is deprecated and will be removed in Doctrine ORM 4.0.',
);
$keys = array_keys($this->classMetadata->propertyAccessors);
foreach ($keys as $key) {
yield $key => $this->offsetGet($key);
}
}
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Deprecations\Deprecation;
use Override;
use function strtolower;
use function trim;
@@ -28,6 +28,7 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
public array $relationToTargetKeyColumns = [];
/** @return array<string, mixed> */
#[Override]
public function toArray(): array
{
$array = parent::toArray();
@@ -130,14 +131,7 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
if ($joinColumn->nullable !== null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12126',
<<<'DEPRECATION'
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
The ORM will always set it to false.
Doing so is deprecated and will be an error in 4.0.
DEPRECATION,
throw MappingException::cannotSetNullableOnManyToManyJoinColumns(
$mapping->sourceEntity,
$mapping->fieldName,
);
@@ -169,14 +163,7 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
if ($inverseJoinColumn->nullable !== null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/12126',
<<<'DEPRECATION'
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
The ORM will always set it to false.
Doing so is deprecated and will be an error in 4.0.
DEPRECATION,
throw MappingException::cannotSetNullableOnManyToManyJoinColumns(
$mapping->targetEntity,
$mapping->fieldName,
);
@@ -210,6 +197,7 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
}
/** @return list<string> */
#[Override]
public function __sleep(): array
{
$serialized = parent::__sleep();

View File

@@ -8,6 +8,7 @@ use BackedEnum;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException;
use LibXMLError;
use Override;
use ReflectionException;
use ValueError;
@@ -87,6 +88,7 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
return new self(sprintf("The embed mapping '%s' misses the 'class' attribute.", $fieldName));
}
#[Override]
public static function mappingFileNotFound(string $entityName, string $fileName): self
{
return new self(sprintf("No mapping file found named '%s' for class '%s'.", $fileName, $entityName));
@@ -317,6 +319,7 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
));
}
#[Override]
public static function fileMappingDriversRequireConfiguredDirectoryPath(string|null $path = null): self
{
if (! empty($path)) {
@@ -424,6 +427,28 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
return new self(sprintf("Discriminator column name on entity class '%s' is not defined.", $className));
}
public static function cannotSetNullableOnToOneIdentifierJoinColumns(
string $className,
string $fieldName,
): self {
return new self(sprintf(
'Cannot specify the "nullable" attribute for join columns in to-one associations (%s::$%s) that are part of the identifier',
$className,
$fieldName,
));
}
public static function cannotSetNullableOnManyToManyJoinColumns(
string $className,
string $fieldName,
): self {
return new self(sprintf(
'Cannot specify the "nullable" attribute for join columns in many-to-many associations (%s::$%s)',
$className,
$fieldName,
));
}
public static function cannotVersionIdField(string $className, string $fieldName): self
{
return new self(sprintf(

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
final class OneToManyAssociationMapping extends ToManyInverseSideMapping
{
/**
@@ -28,6 +30,7 @@ final class OneToManyAssociationMapping extends ToManyInverseSideMapping
* isOwningSide: bool,
* } $mappingArray
*/
#[Override]
public static function fromMappingArray(array $mappingArray): static
{
$mapping = parent::fromMappingArray($mappingArray);

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Override;
abstract class OwningSideMapping extends AssociationMapping
{
/**
@@ -15,6 +17,7 @@ abstract class OwningSideMapping extends AssociationMapping
public string|null $inversedBy = null;
/** @return list<string> */
#[Override]
public function __sleep(): array
{
$serialized = parent::__sleep();

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\PropertyAccessors;
use Doctrine\Instantiator\Instantiator;
use Override;
use ReflectionProperty;
/** @internal */
@@ -20,6 +21,7 @@ class EmbeddablePropertyAccessor implements PropertyAccessor
) {
}
#[Override]
public function setValue(object $object, mixed $value): void
{
$embeddedObject = $this->parent->getValue($object);
@@ -35,6 +37,7 @@ class EmbeddablePropertyAccessor implements PropertyAccessor
$this->child->setValue($embeddedObject, $value);
}
#[Override]
public function getValue(object $object): mixed
{
$embeddedObject = $this->parent->getValue($object);
@@ -46,6 +49,7 @@ class EmbeddablePropertyAccessor implements PropertyAccessor
return $this->child->getValue($embeddedObject);
}
#[Override]
public function getUnderlyingReflector(): ReflectionProperty
{
return $this->child->getUnderlyingReflector();

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\PropertyAccessors;
use BackedEnum;
use Override;
use ReflectionProperty;
use function array_map;
@@ -19,6 +20,7 @@ class EnumPropertyAccessor implements PropertyAccessor
{
}
#[Override]
public function setValue(object $object, mixed $value): void
{
if ($value !== null) {
@@ -28,6 +30,7 @@ class EnumPropertyAccessor implements PropertyAccessor
$this->parent->setValue($object, $value);
}
#[Override]
public function getValue(object $object): mixed
{
$enum = $this->parent->getValue($object);
@@ -78,6 +81,7 @@ class EnumPropertyAccessor implements PropertyAccessor
return $this->enumType::from($value);
}
#[Override]
public function getUnderlyingReflector(): ReflectionProperty
{
return $this->parent->getUnderlyingReflector();

View File

@@ -1,61 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\PropertyAccessors;
use Doctrine\ORM\Proxy\InternalProxy;
use ReflectionProperty;
use function ltrim;
/** @internal */
class ObjectCastPropertyAccessor implements PropertyAccessor
{
/** @param class-string $class */
public static function fromNames(string $class, string $name): self
{
$reflectionProperty = new ReflectionProperty($class, $name);
$key = $reflectionProperty->isPrivate() ? "\0" . ltrim($class, '\\') . "\0" . $name : ($reflectionProperty->isProtected() ? "\0*\0" . $name : $name);
return new self($reflectionProperty, $key);
}
public static function fromReflectionProperty(ReflectionProperty $reflectionProperty): self
{
$name = $reflectionProperty->getName();
$key = $reflectionProperty->isPrivate() ? "\0" . ltrim($reflectionProperty->getDeclaringClass()->getName(), '\\') . "\0" . $name : ($reflectionProperty->isProtected() ? "\0*\0" . $name : $name);
return new self($reflectionProperty, $key);
}
private function __construct(private ReflectionProperty $reflectionProperty, private string $key)
{
}
public function setValue(object $object, mixed $value): void
{
if (! ($object instanceof InternalProxy && ! $object->__isInitialized())) {
$this->reflectionProperty->setValue($object, $value);
return;
}
$object->__setInitialized(true);
$this->reflectionProperty->setValue($object, $value);
$object->__setInitialized(false);
}
public function getValue(object $object): mixed
{
return ((array) $object)[$this->key] ?? null;
}
public function getUnderlyingReflector(): ReflectionProperty
{
return $this->reflectionProperty;
}
}

View File

@@ -6,8 +6,6 @@ namespace Doctrine\ORM\Mapping\PropertyAccessors;
use ReflectionProperty;
use const PHP_VERSION_ID;
class PropertyAccessorFactory
{
/** @phpstan-param class-string $className */
@@ -15,9 +13,7 @@ class PropertyAccessorFactory
{
$reflectionProperty = new ReflectionProperty($className, $propertyName);
$accessor = PHP_VERSION_ID >= 80400
? RawValuePropertyAccessor::fromReflectionProperty($reflectionProperty)
: ObjectCastPropertyAccessor::fromReflectionProperty($reflectionProperty);
$accessor = RawValuePropertyAccessor::fromReflectionProperty($reflectionProperty);
if ($reflectionProperty->hasType() && ! $reflectionProperty->getType()->allowsNull()) {
$accessor = new TypedNoDefaultPropertyAccessor($accessor, $reflectionProperty);

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