Compare commits

...

117 Commits

Author SHA1 Message Date
Alexander M. Turek
bfed8cb6ed Update branch metadata for 2.11 (#9364) 2022-01-12 14:20:33 +01:00
Alexander M. Turek
09a2648f7e Fix doc blocks on ID generators (#9368) 2022-01-12 12:10:23 +01:00
Alexander M. Turek
ee591195cf Use EntityManagerInterface in type declarations (#9325) 2022-01-12 11:00:07 +01:00
Alexander M. Turek
e974313523 Merge branch '2.10.x' into 2.11.x
* 2.10.x:
  Add errors caused by the lexer update to the baselines (#9360)
2022-01-12 10:11:19 +01:00
Alexander M. Turek
1e972b6e0e Add errors caused by the lexer update to the baselines (#9360) 2022-01-12 10:06:40 +01:00
Christian Mehldau
e369cb6e73 Generated/Virtual Columns: Insertable / Updateable (#9118)
* Generated/Virtual Columns: Insertable / Updateable

Defines whether a column is included in an SQL INSERT and/or UPDATE statement.
Throws an exception for UPDATE statements attempting to update this field/column.

Closes #5728

* Apply suggestions from code review

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

* Add example for virtual column usage in attributes to docs.

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2022-01-12 08:06:11 +01:00
Grégoire Paris
ec391be4f2 Merge pull request #9356 from derrabus/remove/package-versions
Remove the `composer/package-versions-deprecated` package
2022-01-11 21:03:59 +01:00
Alexander M. Turek
697e23422f Remove the composer/package-versions-deprecated package 2022-01-11 10:42:42 +01:00
Alexander M. Turek
e487b6fe2b Relax assertion to include null as possible outcome (#9355) 2022-01-10 23:09:02 +01:00
Alexander M. Turek
656f881756 Merge branch '2.10.x' into 2.11.x
* 2.10.x:
  Fix WhereInWalker description to better describe the behaviour of this class (#9268)
2022-01-09 23:48:22 +01:00
Alexander M. Turek
cd2aa487a5 Leverage generic ObjectManagerDecorator (#9312) 2022-01-09 23:10:05 +01:00
LuigiCardamone
b7d822972e Fix WhereInWalker description to better describe the behaviour of this class (#9268)
* Fix WhereInWalker description:
- change the verb "replace" with "append" to better describe the behaviour of this class

* Rephrase comment in WhereInWalker as suggested from reviewer

Co-authored-by: Alexander M. Turek <me@derrabus.de>

* Rephrase comment in WhereInWalker as suggested from reviewer

Co-authored-by: Alexander M. Turek <me@derrabus.de>

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-01-09 23:09:28 +01:00
Alexander M. Turek
ec63f5d32a Regenerate Psalm baseline 2022-01-09 22:35:05 +01:00
Alexander M. Turek
952ccc5fc8 Merge branch '2.10.x' into 2.11.x
* 2.10.x:
  Update Psalm baseline for Persistence 2.3 (#9349)
2022-01-09 22:33:24 +01:00
Alexander M. Turek
9a2f1f380d Update Psalm baseline for Persistence 2.3 (#9349) 2022-01-09 20:11:12 +00:00
Alexander M. Turek
580b9196e6 Support readonly properties for read operations (#9316)
* Provide failing test for readonly properties

* Skip writing readonly properties if the value did not change
2022-01-09 20:15:56 +01:00
Grégoire Paris
0d911b9381 Merge pull request #9322 from derrabus/feature/psr-region-cache
PSR-6 second level cache
2022-01-09 16:23:04 +01:00
Grégoire Paris
c6d8aecc0f Merge pull request #9326 from kimhemsoe/rsm-custom-type
Add support for custom types with requireSQLConversion and ResultSetMappingBuilder::generateSelectClause()
2022-01-09 16:17:31 +01:00
Grégoire Paris
fdd3d112b0 Merge remote-tracking branch 'origin/2.10.x' into 2.11.x 2022-01-09 15:50:52 +01:00
Grégoire Paris
2fecb3cb1a Merge pull request #9341 from derrabus/bump/phpstan-psalm
PHPStan 1.3.3, Psalm 4.18.1
2022-01-09 15:48:49 +01:00
Alexander M. Turek
f3630ea16b PHPStan 1.3.3, Psalm 4.18.1 2022-01-09 15:39:44 +01:00
Grégoire Paris
fd19444761 Merge pull request #9344 from greg0ire/remove-dbal2-psalm-job
Remove Psalm job for analyzing DBAL 2
2022-01-09 15:38:56 +01:00
Grégoire Paris
4b1afb41b3 Remove Psalm job for analyzing DBAL 2
As of now, we cannot have specific config files for each DBAL version
and avoid repetition. We already have PHPStan performing checks with
DBAL 2, which could be considered enough.
2022-01-09 13:57:00 +01:00
Alexander M. Turek
f9f453f4d7 Use the readonly annotation (#9340) 2022-01-09 12:25:04 +01:00
Kim Hemsø Rasmussen
f508a4bb71 Add support for custom types with requireSQLConversion and ResultSetMappingBuilder::generateSelectClause() 2022-01-09 10:02:30 +01:00
Alexander M. Turek
5d0fbc47d0 PSR-6 second level cache 2022-01-09 02:02:50 +01:00
Alexander M. Turek
1e977426eb Fix type errors in AbstractQuery and QueryBuilder (#9275) 2022-01-09 00:26:58 +01:00
Grégoire Paris
2640f88f8a Merge pull request #9339 from greg0ire/fix-field-mapping-typing
Fix field mapping typing
2022-01-08 23:57:48 +01:00
Grégoire Paris
fa731b10ec Mark columnName as always set
This is enforced before writing to the property that holds FieldMapping
arrays.
As shown by the static analysis baselines reduction, this existence is
relied on throughout the codebase.
2022-01-08 14:12:04 +01:00
Grégoire Paris
4117ca349f Merge pull request #9304 from beberlei/EnumSupport
Add support for PHP 8.1 enums.
2022-01-08 11:49:08 +01:00
Benjamin Eberlei
2d475c9bb3 Add support for PHP 8.1 enums. 2022-01-08 09:53:11 +01:00
Grégoire Paris
6f54011e7b Merge remote-tracking branch 'origin/2.10.x' into 2.11.x 2022-01-07 20:28:54 +01:00
Grégoire Paris
760397c429 Remove ignore rules for issues fixed upstream (#9336)
The rules still should apply when using DBAL v2
2022-01-07 20:25:00 +01:00
Benjamin Eberlei
7190ac5127 [GH-9277] deprecate php driver (#9309)
* [GH-9277] Deprecate PHPDriver

* Update UPGRADE.md, fix wrong parameter

* Copy docblock to appease confused Psalm

* Talk about alternatives more
2022-01-06 10:19:42 +01:00
Alexander M. Turek
ceaefcb18d Merge 2.10.x into 2.11.x (#9331)
* Enable some previously disabled PHPCS rules (#9324)

* Fix broken type declaration (#9330)
2022-01-05 10:03:45 +01:00
Alexander M. Turek
844ce77cae Added runtime deprecation to UnitOfWork::commit() and clear() (#9327) 2022-01-05 08:14:46 +01:00
Alexander M. Turek
cf3a185b62 Document return type of getEntityState() (#9328) 2022-01-05 07:58:21 +01:00
Alexander M. Turek
efc982a48d Fix broken type declaration (#9330) 2022-01-05 07:55:33 +01:00
Alexander M. Turek
96bc214acd Enable some previously disabled PHPCS rules (#9324) 2022-01-03 23:25:34 +01:00
Alexander M. Turek
15999758a7 Merge branch '2.10.x' into 2.11.x
* 2.10.x:
  Run static analysis with language level PHP 8.1 (#9314)
  Document PHPUnit mocks with intersection types (#9318)
2022-01-02 19:36:10 +01:00
Alexander M. Turek
44aa8c2c5b Run static analysis with language level PHP 8.1 (#9314) 2022-01-02 18:01:31 +01:00
Alexander M. Turek
8c6fc5ae52 Document LockMode enums (#9319) 2022-01-02 18:01:00 +01:00
Alexander M. Turek
c4561571aa Document PHPUnit mocks with intersection types (#9318) 2022-01-02 18:00:17 +01:00
Alexander M. Turek
40a203843d Merge branch '2.10.x' into 2.11.x
* 2.10.x:
  Run PHP CodeSniffer on PHP 8.1 (#9317)
  Psalm 4.17.0 (#9315)
2022-01-02 14:16:56 +01:00
Alexander M. Turek
8b5ee54c6a Run PHP CodeSniffer on PHP 8.1 (#9317) 2022-01-02 14:15:30 +01:00
Alexander M. Turek
03fa495fbc Psalm 4.17.0 (#9315) 2022-01-01 23:41:05 +01:00
Alexander M. Turek
5901848944 Merge 2.10.x into 2.11.x (#9313) 2022-01-01 23:40:19 +01:00
Grégoire Paris
d40f9e57ff Run static analysis on PHP 8.1 (#9310)
This will make it easier to add code that leverages features only
defined since that version of PHP.
2022-01-01 20:28:17 +01:00
Alexander M. Turek
133cc95f33 Merge branch '2.10.x' into 2.11.x
* 2.10.x:
  Bump PHPStan & Psalm (#9303)
  Removing list "Lifecycle Events" (#9243)
  Drop unneeded backslashes
  Fix Hidden fields triggering error when using getSingleScalarResult() (#8340)
  Findby joined lookup (#8285)
2021-12-31 02:59:55 +01:00
Alexander M. Turek
d30e748e64 Bump PHPStan & Psalm (#9303) 2021-12-31 02:21:15 +01:00
Alexander M. Turek
98d77043d8 Fix type errors in AnnotationDriver (#9274) 2021-12-29 16:03:10 +01:00
Grégoire Paris
40d1e7bbfc Merge pull request #9214 from doctrine/2.7
Merge 2.7 into 2.10.x
2021-12-28 23:41:38 +01:00
Thomas Landauer
e8275f6e4d Removing list "Lifecycle Events" (#9243)
As announced in https://github.com/doctrine/orm/pull/9184#issuecomment-965837780
2021-12-28 14:04:58 +01:00
Alexander M. Turek
70dcffa025 Leverage get_debug_type() (#9297) 2021-12-28 08:02:16 +01:00
Alexander M. Turek
c94a9b1d8b Merge 2.10.x into 2.11.x (#9298)
* Bump reusable workflows

* Fix union type on QueryExpressionVisitorTest::testWalkComparison() (#9294)

* Synchronize Psalm baseline (#9296)

* Fix return type (#9295)

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-12-28 01:25:01 +01:00
Alexander M. Turek
6a9393e8ed Fix return type (#9295) 2021-12-28 00:49:50 +01:00
Alexander M. Turek
ab98d0ffc6 Synchronize Psalm baseline (#9296) 2021-12-28 00:49:32 +01:00
Alexander M. Turek
2ddeb79431 Fix union type on QueryExpressionVisitorTest::testWalkComparison() (#9294) 2021-12-27 23:43:09 +01:00
David ALLIX
92ff9c9108 Allow arithmetic expressions within IN operator (#9242)
* allow arithmetic expressions within IN operator

Co-authored-by: Artem Stepin <stepin.artem@gmail.com>
2021-12-27 19:03:47 +01:00
Grégoire Paris
7c58dc89c3 Merge pull request #9289 from derrabus/bump/workflows
Bump reusable workflows
2021-12-27 18:36:35 +01:00
Alexander M. Turek
b513f7c935 Bump reusable workflows 2021-12-27 13:05:20 +01:00
Alexander M. Turek
f1483f848c Merge 2.10.x into 2.11.x (#9287)
* Better explain limitations of DQL "DELETE" (#9281)

We think the current documentation does not stress these details enough, so that they are easily overlooked.

Co-authored-by: Malte Wunsch <mw@webfactory.de>

Co-authored-by: Malte Wunsch <mw@webfactory.de>

* Put actual value instead of index inside $originalEntityData. (#9244)

This fixes a bug with redundant UPDATE queries, that are executed when some entity uses foreign index of other entity as a primary key. This happens when after inserting related entities with $em->flush() call, you do the second $em->flush() without changing any data inside entities.
Fixes GH8217.

Co-authored-by: ivan <ivan.strygin@managinglife.com>

* Allow symfony/cache 6 (#9283)

* Fix XML export for `change-tracking-policy` (#9285)

* Whitelist composer plugins used by this repository (#9286)

Co-authored-by: Matthias Pigulla <mp@webfactory.de>
Co-authored-by: Malte Wunsch <mw@webfactory.de>
Co-authored-by: Ivan Strygin <feolius@gmail.com>
Co-authored-by: ivan <ivan.strygin@managinglife.com>
Co-authored-by: Fedir Zinchuk <getthesite@gmail.com>
2021-12-26 01:06:54 +01:00
Alexander M. Turek
ea4c9b21b7 Enable UnusedUse sniff again (#9267) 2021-12-25 23:06:50 +01:00
Alexander M. Turek
72edfbc270 Whitelist composer plugins used by this repository (#9286) 2021-12-25 13:04:42 +01:00
Fedir Zinchuk
5ccf2eac40 Fix XML export for change-tracking-policy (#9285) 2021-12-24 00:22:42 +01:00
Alexander M. Turek
6696b0dfbf Allow symfony/cache 6 (#9283) 2021-12-24 00:12:11 +01:00
Ivan Strygin
aead77d597 Put actual value instead of index inside $originalEntityData. (#9244)
This fixes a bug with redundant UPDATE queries, that are executed when some entity uses foreign index of other entity as a primary key. This happens when after inserting related entities with $em->flush() call, you do the second $em->flush() without changing any data inside entities.
Fixes GH8217.

Co-authored-by: ivan <ivan.strygin@managinglife.com>
2021-12-24 00:10:42 +01:00
Alexander M. Turek
130c27c1da Fix return types of cache interfaces (#9271) 2021-12-22 01:04:07 +01:00
Matthias Pigulla
f6e1dd44f0 Better explain limitations of DQL "DELETE" (#9281)
We think the current documentation does not stress these details enough, so that they are easily overlooked.

Co-authored-by: Malte Wunsch <mw@webfactory.de>

Co-authored-by: Malte Wunsch <mw@webfactory.de>
2021-12-22 00:44:25 +01:00
Alexander M. Turek
1e9973a0c0 Merge release 2.10.4 into 2.11.x (#9280) 2021-12-21 11:01:59 +01:00
Alexander M. Turek
91761738fd Fix docblocks on nullable EM properties (#9273) 2021-12-20 22:31:57 +01:00
Andrii Dembitskyi
cccb2e2fdf Docs: use canonical order for phpdoc tags, add missed semicolon (#9190) 2021-12-20 22:23:47 +01:00
Benjamin Eberlei
18138d895e Make PrimaryReadReplicaConnection enforcement explicit (#9239)
* Move primary replica connection logic into ORM explicitly.

* Housekeeping: Use full named variables

* Housekeeping: phpcs
2021-12-20 13:50:25 +01:00
Alexander M. Turek
95d434d003 Merge 2.10.x into 2.11.x (#9276)
* Docs: consistency for FQCN, spacing, etc (#9232)

* Docs: consistent spacing, consistent array-style, consistent FQCN, avoid double escaped slashes, avoid double quotes if not necessary

* Docs: use special note block instead of markdown-based style

* Docs: Quote FQCN in table with backticks to be compatible with all render engines

* Drop all mentions API doc - it is not available anymore

* Add missed FQCN for code snippets

* Revert "Fix SchemaValidator with abstract child class in discriminator map (#9096)" (#9262)

This reverts commit bbb68d0072.

* [docs] Fix wording for attributes=>parameters. (#9265)

Co-authored-by: Andrii Dembitskyi <andrew.dembitskiy@gmail.com>
Co-authored-by: olsavmic <molsavsky1@gmail.com>
Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2021-12-20 04:11:33 +01:00
Alexander M. Turek
70c651ebb7 Regenerate Psalm baseline (#9272) 2021-12-19 17:06:20 +01:00
Sergei Morozov
8cb62a616a Improve compatibility with Doctrine DBAL 4 (#9266)
* Improve compatibility with AbstractPlatform::getLocateExpression() in DBAL 4

* Improve compatibility with AbstractPlatform::getTrimExpression() in DBAL 4

* Improve compatibility with Connection::quote() in DBAL 4
2021-12-19 13:19:30 +01:00
Benjamin Eberlei
fa2b52c974 [docs] Fix wording for attributes=>parameters. (#9265) 2021-12-18 11:16:35 +01:00
Benjamin Eberlei
6d306c1946 Support for nesting attributes with PHP 8.1 (#9241)
* [GH-9240] Refactor Association/AttributeOverrides to use @NamedConstructorArguments with AnnotationDriver.

* [GH-9240] Add support for PHP 8.1 nested attributes.

Supported/new attributes are #[AttributeOverrides], #[AssociationOverrides], #[JoinTable] with nested joinColumns, inverseJoinColumns.

* [GH-9240] Add support for nesting Index, UniqueCosntraint into #[Table] on PHP 8.1

* Apply review comments by gregooire.

* Add documentation for new attributes.

* Add docs for new nested #[JoinTable] support of join columns

* Add docs for new nested #[Table] support of index, uniqueConstraints

* Rename "Required/Optional atttributes" to "Required/Optional parameters"

* Remove nesting for JoinTable#joinColumns and Table#indexes/uniqueConstraints again.

* Hosuekeeping: phpcs/psalm

* housekeeping

* Remove unused function imports.
2021-12-18 11:03:12 +01:00
olsavmic
5bf814032f Revert "Fix SchemaValidator with abstract child class in discriminator map (#9096)" (#9262)
This reverts commit bbb68d0072.
2021-12-18 11:01:30 +01:00
Sergei Morozov
bea5e7166c Address more DBAL 3.2 deprecations (#9256)
* Instantiate comparator via the schema manager, if possible

* Do not use AbstractPlatform::getName()
2021-12-16 23:18:18 +01:00
Alexander M. Turek
003090b70c Deprecate Setup::registerAutoloadDirectory() (#9249) 2021-12-13 23:36:10 +01:00
Andrii Dembitskyi
02a4e4099d Docs: consistency for FQCN, spacing, etc (#9232)
* Docs: consistent spacing, consistent array-style, consistent FQCN, avoid double escaped slashes, avoid double quotes if not necessary

* Docs: use special note block instead of markdown-based style

* Docs: Quote FQCN in table with backticks to be compatible with all render engines

* Drop all mentions API doc - it is not available anymore

* Add missed FQCN for code snippets
2021-12-13 23:10:01 +01:00
Alexander M. Turek
56e0ac02af Merge 2.10.x into 2.11.x (#9248) 2021-12-13 22:21:00 +01:00
Alexander M. Turek
12a70bbefb PHPCS 3.6.2, Psalm 4.15.0 (#9247) 2021-12-13 21:28:56 +01:00
Grégoire Paris
5a4ddb2870 Merge pull request #9184 from ThomasLandauer/patch-1
[Documentation] Events Overview Table: Adding "Passed Argument" column
2021-12-12 16:21:13 +01:00
Simon Podlipsky
42195060e6 Add SchemaIgnoreClasses property for #8195. (#9202)
Co-authored-by: Simon Podlipsky <simon@podlipsky.net>

Co-authored-by: Iab Foulds <ianfoulds@x-act.co.uk>
2021-12-12 13:42:07 +01:00
Alexander M. Turek
68fa55f310 Remove fallbacks for old doctrine/annotations version (#9235) 2021-12-11 17:11:34 +01:00
Thomas Landauer
0b0c3e7e58 Update docs/en/reference/events.rst
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-12-09 12:00:30 +01:00
Kevin van Sonsbeek
92434f91c7 Added psalm param to abstract addFilterConstraint (#9229) 2021-12-08 22:31:52 +00:00
Alexander Schranz
6414ad4cbb Merge pull request #9210 from alexander-schranz/patch-2
Fix making columns  optional in indexes xml schema as they can be defined via fields now
2021-12-06 00:55:01 +01:00
Alexander M. Turek
ac5aea1c81 Merge release 2.10.3 into 2.11.x (#9224) 2021-12-03 22:40:00 +01:00
Alexander M. Turek
a75605b8c3 Merge pull request #9211 from derrabus/deprecate/convert-mapping
Add deprecation hints to `orm:convert-mapping` command
2021-12-03 15:50:09 +01:00
Grégoire Paris
66c95a65c5 Drop unneeded backslashes 2021-12-01 21:53:36 +01:00
Bruce
62a0d7359b Fix Hidden fields triggering error when using getSingleScalarResult() (#8340)
* Fix Hidden fields triggering error when using getSingleScalarResult()

Fixes #4257
HIDDEN fields was causing the "unicity" check to fail (NonUniqueResultException), because we was counting raw data instead of gathered row data.

* Fix Coding Standards (7.4)

* Fix Coding Standards (7.4) #2

* Fix Coding Standards (7.4) - Fix whitespaces

* Fix Coding Standards (7.4) - Fix whitespaces in tests

* Fix Coding Standards (7.4) - Fix more things

* Refactor tests into separate methods

* Fix Coding Standards (7.4) - Equals sign not aligned with surrounding assignments
2021-12-01 21:52:31 +01:00
Benjamin Eberlei
2c7d7ebb48 Findby joined lookup (#8285)
* [GH-7512] Bugfix: Load metadata on object-typed  value in EntityPersisters

* [GH-7512] Refactor double check for object/entity and flatten code.

Co-authored-by: Joe Mizzi <themizzi@me.com>
2021-12-01 21:52:29 +01:00
Thomas Landauer
8b6fe52f74 Update events.rst 2021-12-01 01:01:04 +01:00
Alexander M. Turek
eabb7f84e9 Add deprecation hints to orm:convert-mapping command 2021-11-30 23:04:44 +01:00
Alexander M. Turek
f0a20dbc9c Merge 2.10.x into 2.11.x (#9213) 2021-11-30 22:37:08 +01:00
Thomas Landauer
32cd2106d0 Completing links to EventArgs classes in overview table
Questions:
1. Is https://github.com/doctrine/persistence/blob/master/lib/Doctrine/Persistence/Event/LifecycleEventArgs.php correct at all? Shouldn't this be https://github.com/doctrine/orm/blob/2.10.x/lib/Doctrine/ORM/Event/LifecycleEventArgs.php, like all the others?

2. Which one is correct for `preUpdate`? https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/reference/events.html#entity-listeners-class says `PreUpdateEventArgs`, but https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/reference/events.html#listening-and-subscribing-to-lifecycle-events says `LifecycleEventArgs`

For the two links to `doctrine/persistence`, I'm linking to `/master/` now, which is being forwarded to `/2.2.x/`.
2021-11-30 15:51:20 +01:00
Alexander M. Turek
6857a2e8d4 Add missing deprecations for YAML metadata mapping (#9206) 2021-11-29 16:46:05 +01:00
Alexander M. Turek
5e8b34ae30 Merge pull request #9203 from derrabus/bump/dbal-3.2
Drop support for DBAL 3.1
2021-11-29 16:45:30 +01:00
Alexander M. Turek
a9b682b7c0 Drop support for DBAL 3.1 2021-11-29 10:37:05 +01:00
Alexander M. Turek
0aadc456dc Merge 2.10.x into 2.11.x (#9205)
* Adding Attributes code block (#9161)

Just that there is some real-world example somewhere ;-) see https://github.com/doctrine/orm/issues/9020#issuecomment-955582801

* Use `equal to` instead of `equal of` in `assertSqlGeneration()` (#9195)

* Add a psalm type for field mapping

Field mapping have different definitions
in property definition and method return.
As suggested in issue and to avoid further desynchronization,
a psalm type has been created.
Fixes #9193

* Psalm 4.13.1, PHPStan 1.2.0 (#9204)

Co-authored-by: Thomas Landauer <thomas@landauer.at>
Co-authored-by: Simon Podlipsky <simon@podlipsky.net>
Co-authored-by: Julien LARY <47776596+laryjulien@users.noreply.github.com>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-11-28 01:08:49 +01:00
Thomas Landauer
77b7107d05 Using const for type 2021-11-23 01:41:01 +01:00
Grégoire Paris
2488b4c50c Merge pull request #9196 from greg0ire/2.11.x
Merge 2.10.x up into 2.11.x
2021-11-20 15:38:36 +01:00
Grégoire Paris
ed642c72c9 Merge remote-tracking branch 'origin/2.10.x' into 2.11.x 2021-11-20 15:28:20 +01:00
Thomas Landauer
2dde65c4ba [Documentation] Events Overview Table: Adding "Passed Argument" column
As announced in https://github.com/doctrine/orm/pull/9160#issuecomment-954304588 I'm adding the passed "EventArgs" class to the overview table. Once this is complete, my further plan is to remove the entire paragraph https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/reference/events.html#lifecycle-callbacks-event-argument, and probably also the second code block at https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/reference/events.html#entity-listeners-class

Is there a better way to link to the source code of `LifecycleEventArgs` than https://github.com/doctrine/persistence/blob/2.2.x/lib/Doctrine/Persistence/Event/LifecycleEventArgs.php ?

Also, I changed `postLoad` to `preUpdate` in the code block, to have an example that does not receive `LifecycleEventArgs` ;-)
2021-11-11 00:22:25 +01:00
Thomas Landauer
1e971d85c4 Merging two ~identical lists on event types (#9160)
* Merging two ~identical lists on event types

Just noticed that what I added in https://github.com/doctrine/orm/pull/9128 was (in other words) already there ;-)

* Update docs/en/reference/events.rst

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2021-11-06 21:32:46 +01:00
Grégoire Paris
2074fc3ff9 Merge pull request #9133 from judahnator/2.10.x
Adding a setup helper for attribute metadata config
2021-10-28 22:23:25 +02:00
Judah Wright
8fef44333b Adding a setup helper for attribute metadata config 2021-10-27 15:05:38 -07:00
Grégoire Paris
3361691d0a Merge pull request #9140 from doctrine/2.10.x-merge-up-into-2.11.x_JJHD4HD8
Merge release 2.10.2 into 2.11.x
2021-10-21 20:49:48 +02:00
Alexander M. Turek
b6a2257758 Merge 2.10.x into 2.11.x (#9137) 2021-10-21 19:50:56 +02:00
Alexander M. Turek
06d9c584a3 Merge 2.10.x into 2.11.x (#9127) 2021-10-15 18:41:04 +02:00
Alexander M. Turek
b0381b3705 Merge release 2.10.1 into 2.11.x (#9092) 2021-10-05 15:12:04 +02:00
Alexander M. Turek
3984f74eb4 Deprecate ensureProductionSettings() (#9074) 2021-10-04 10:00:34 +02:00
Alexander M. Turek
ebdced6175 Deprecate AbstractHydrator::hydrateRow() (#9072) 2021-10-03 21:46:30 +00:00
Grégoire Paris
1a702075ba Merge pull request #9071 from doctrine/2.10.x
Merge up
2021-10-03 22:49:21 +02:00
471 changed files with 4720 additions and 3088 deletions

View File

@@ -12,21 +12,27 @@
"upcoming": true
},
{
"name": "2.11",
"branchName": "2.11.x",
"slug": "2.11",
"name": "2.12",
"branchName": "2.12.x",
"slug": "2.12",
"upcoming": true
},
{
"name": "2.10",
"branchName": "2.10.x",
"slug": "2.10",
"name": "2.11",
"branchName": "2.11.x",
"slug": "2.11",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"name": "2.10",
"branchName": "2.10.x",
"slug": "2.10",
"maintained": false
},
{
"name": "2.9",
"branchName": "2.9.x",

View File

@@ -10,6 +10,6 @@ on:
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.1.1"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.4.1"
with:
php-version: "7.4"
php-version: "8.1"

View File

@@ -7,9 +7,8 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@1.1.1"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@1.4.1"
secrets:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.0"
- "8.1"
dbal-version:
- "default"
- "2.13"
@@ -58,10 +58,7 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.0"
dbal-version:
- "default"
- "2.13"
- "8.1"
steps:
- name: "Checkout code"
@@ -73,10 +70,6 @@ jobs:
coverage: "none"
php-version: "${{ matrix.php-version }}"
- 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@v1"
with:

View File

@@ -1,7 +1,7 @@
| [3.0.x][3.0] | [2.11.x][2.11] | [2.10.x][2.10] |
| [3.0.x][3.0] | [2.12.x][2.12] | [2.11.x][2.11] |
|:----------------:|:----------------:|:----------:|
| [![Build status][3.0 image]][3.0] | [![Build status][2.11 image]][2.11] | [![Build status][2.10 image]][2.10] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.11 coverage image]][2.11 coverage] | [![Coverage Status][2.10 coverage image]][2.10 coverage] |
| [![Build status][3.0 image]][3.0] | [![Build status][2.12 image]][2.12] | [![Build status][2.11 image]][2.11] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.12 coverage image]][2.12 coverage] | [![Coverage Status][2.11 coverage image]][2.11 coverage] |
Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
@@ -20,10 +20,10 @@ without requiring unnecessary code duplication.
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x
[2.10 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.10.x
[2.10]: https://github.com/doctrine/orm/tree/2.10.x
[2.10 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.10.x/graph/badge.svg
[2.10 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.10.x
[2.12 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.12.x
[2.12]: https://github.com/doctrine/orm/tree/2.12.x
[2.12 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.12.x/graph/badge.svg
[2.12 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.12.x
[2.11 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.11.x
[2.11]: https://github.com/doctrine/orm/tree/2.11.x
[2.11 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.11.x/graph/badge.svg

View File

@@ -1,3 +1,56 @@
# Upgrade to 2.11
## Rename `AbstractIdGenerator::generate()` to `generateId()`
Implementations of `AbstractIdGenerator` have to override the method
`generateId()` without calling the parent implementation. Not doing so is
deprecated. Calling `generate()` on any `AbstractIdGenerator` implementation
is deprecated.
## PSR-6-based second level cache
The second level cache has been reworked to consume a PSR-6 cache. Using a
Doctrine Cache instance is deprecated.
* `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as
second argument now.
* `DefaultMultiGetRegion`: This class is deprecated in favor of `DefaultRegion`.
* `DefaultRegion`:
* The constructor expects a PSR-6 cache item pool as second argument now.
* The protected `$cache` property is deprecated.
* The properties `$name` and `$lifetime` as well as the constant
`REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are flagged as
`@internal` now. They all will become `private` in 3.0.
* The method `getCache()` is deprecated without replacement.
## Deprecated: `Doctrine\ORM\Mapping\Driver\PHPDriver`
Use `StaticPHPDriver` instead when you want to programmatically configure
entity metadata.
You can convert mappings with the `orm:convert-mapping` command or more simply
in this case, `include` the metadata file from the `loadMetadata` static method
used by the `StaticPHPDriver`.
## Deprecated: `Setup::registerAutoloadDirectory()`
Use Composer's autoloader instead.
## Deprecated: `AbstractHydrator::hydrateRow()`
Following the deprecation of the method `AbstractHydrator::iterate()`, the
method `hydrateRow()` has been deprecated as well.
## Deprecate cache settings inspection
Doctrine does not provide its own cache implementation anymore and relies on
the PSR-6 standard instead. As a consequence, we cannot determine anymore
whether a given cache adapter is suitable for a production environment.
Because of that, functionality that aims to do so has been deprecated:
* `Configuration::ensureProductionSettings()`
* the `orm:ensure-production-settings` console command
# Upgrade to 2.10
## BC Break: `UnitOfWork` now relies on SPL object IDs, not hashes
@@ -182,8 +235,7 @@ These methods have been deprecated:
## Deprecated `Doctrine\ORM\Version`
The `Doctrine\ORM\Version` class is now deprecated and will be removed in Doctrine ORM 3.0:
please refrain from checking the ORM version at runtime or use
[ocramius/package-versions](https://github.com/Ocramius/PackageVersions/).
please refrain from checking the ORM version at runtime or use Composer's [runtime API](https://getcomposer.org/doc/07-runtime.md#knowing-whether-package-x-is-installed-in-version-y).
## Deprecated `EntityManager#merge()` method

View File

@@ -13,17 +13,21 @@
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
],
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
},
"require": {
"php": "^7.1 ||^8.0",
"php": "^7.1 || ^8.0",
"composer-runtime-api": "^2",
"ext-ctype": "*",
"ext-pdo": "*",
"composer/package-versions-deprecated": "^1.8",
"doctrine/cache": "^1.12.1 || ^2.1.1",
"doctrine/collections": "^1.5",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.13.1 || ^3.1.1",
"doctrine/dbal": "^2.13.1 || ^3.2",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.1",
"doctrine/inflector": "^1.4 || ^2.0",
@@ -39,12 +43,12 @@
"doctrine/annotations": "^1.13",
"doctrine/coding-standard": "^9.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "1.2.0",
"phpstan/phpstan": "1.3.3",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"squizlabs/php_codesniffer": "3.6.1",
"symfony/cache": "^4.4 || ^5.2",
"squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.13.1"
"vimeo/psalm": "4.18.1"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 2.0"

View File

@@ -4,7 +4,7 @@ Advanced field value conversion using custom mapping types
.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>
When creating entities, you sometimes have the need to transform field values
before they are saved to the database. In Doctrine you can use Custom Mapping
before they are saved to the database. In Doctrine you can use Custom Mapping
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
There are several ways to achieve this: converting the value inside the Type
@@ -15,7 +15,7 @@ type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
of MySQL and enables you to store a single location in a coordinate space by
using x and y coordinates. You can use the Point type to store a
using x and y coordinates. You can use the Point type to store a
longitude/latitude pair to represent a geographic location.
The entity
@@ -29,9 +29,9 @@ The entity class:
.. code-block:: php
<?php
namespace Geo\Entity;
/**
* @Entity
*/
@@ -84,7 +84,7 @@ The entity class:
}
}
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
@@ -92,7 +92,7 @@ The point class:
.. code-block:: php
<?php
namespace Geo\ValueObject;
class Point
@@ -196,7 +196,7 @@ The format of the string representation format is called
`Well-known text (WKT) <https://en.wikipedia.org/wiki/Well-known_text>`_.
The advantage of this format is, that it is both human readable and parsable by MySQL.
Internally, MySQL stores geometry values in a binary format that is not
Internally, MySQL stores geometry values in a binary format that is not
identical to the WKT format. So, we need to let MySQL transform the WKT
representation into its internal format.
@@ -210,13 +210,13 @@ which convert WKT strings to and from the internal format of MySQL.
.. note::
When using DQL queries, the ``convertToPHPValueSQL`` and
When using DQL queries, the ``convertToPHPValueSQL`` and
``convertToDatabaseValueSQL`` methods only apply to identification variables
and path expressions in SELECT clauses. Expressions in WHERE clauses are
and path expressions in SELECT clauses. Expressions in WHERE clauses are
**not** wrapped!
If you want to use Point values in WHERE clauses, you have to implement a
:doc:`user defined function <dql-user-defined-functions>` for
:doc:`user defined function <dql-user-defined-functions>` for
``PointFromText``.
Example usage
@@ -252,5 +252,5 @@ Example usage
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
$location = $query->getSingleResult();
/* @var Geo\ValueObject\Point */
/** @var Geo\ValueObject\Point */
$point = $location->getPoint();

View File

@@ -88,7 +88,7 @@ API would look for this use-case:
$pageNum = 1;
$query = $em->createQuery($dql);
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
$totalResults = Paginate::count($query);
$results = $query->getResult();
@@ -101,12 +101,12 @@ The ``Paginate::count(Query $query)`` looks like:
{
static public function count(Query $query)
{
/* @var $countQuery Query */
/** @var Query $countQuery */
$countQuery = clone $query;
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
$countQuery->setFirstResult(null)->setMaxResults(null);
return $countQuery->getSingleScalarResult();
}
}
@@ -137,13 +137,13 @@ implementation is:
break;
}
}
$pathExpression = new PathExpression(
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
$parent['metadata']->getSingleIdentifierFieldName()
);
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
$AST->selectClause->selectExpressions = array(
new SelectExpression(
new AggregateExpression('count', $pathExpression, true), null
@@ -196,7 +196,7 @@ modify the generation of the SELECT clause, adding the
public function walkSelectClause($selectClause)
{
$sql = parent::walkSelectClause($selectClause);
if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
if ($selectClause->isDistinct) {
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
@@ -204,7 +204,7 @@ modify the generation of the SELECT clause, adding the
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
}
}
return $sql;
}
}

View File

@@ -45,7 +45,7 @@ configuration:
$config->addCustomStringFunction($name, $class);
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
The ``$name`` is the name the function will be referred to in the
@@ -96,7 +96,7 @@ discuss it step by step:
// (1)
public $firstDateExpression = null;
public $secondDateExpression = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // (2)
@@ -106,7 +106,7 @@ discuss it step by step:
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATEDIFF(' .
@@ -180,28 +180,28 @@ I'll skip the blah and show the code for this function:
public $firstDateExpression = null;
public $intervalExpression = null;
public $unit = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_IDENTIFIER);
/* @var $lexer Lexer */
/** @var Lexer $lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return 'DATE_ADD(' .

View File

@@ -440,6 +440,19 @@ That will be available for all entities without a custom repository class.
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (***OPTIONAL***)
-----------------------------------
Specifies the Entity FQCNs to ignore.
SchemaTool will then skip these (e.g. when comparing schemas).
.. code-block:: php
<?php
$config->setSchemaIgnoreClasses([$fqcn]);
$config->getSchemaIgnoreClasses();
Setting up the Console
----------------------

View File

@@ -123,6 +123,18 @@ Optional attributes:
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be
included when updating the row of the underlying entities table.
If not specified, default value is true.
- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
used after an INSERT or UPDATE statement to determine if the database
generated this value and it needs to be fetched using a SELECT statement.
- **options**: Array of additional options:
- ``default``: The default value to set for the column if no value
@@ -193,6 +205,13 @@ Examples:
*/
protected $loginCount;
/**
* Generated column
* @Column(type="string", name="user_fullname", insertable=false, updatable=false)
* MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
*/
protected $fullname;
.. _annref_column_result:
@ColumnResult

View File

@@ -10,6 +10,8 @@ annotation metadata supported since the first version 2.0.
Index
-----
- :ref:`#[AssociationOverride] <attrref_associationoverride]`
- :ref:`#[AttributeOverride] <attrref_attributeoverride]`
- :ref:`#[Column] <attrref_column>`
- :ref:`#[Cache] <attrref_cache>`
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
@@ -49,6 +51,93 @@ Index
Reference
---------
.. _attrref_associationoverride:
#[AssociationOverride]
~~~~~~~~~~~~~~~~~~~~~~
In an inheritance hierarchy this attribute allows to override the
assocation mapping definitions of the parent mappings. It needs to be nested
within a ``#[AssociationOverrides]`` on the class level.
Required parameters:
- **name**: Name of the association mapping to overwrite.
Optional parameters:
- **joinColumns**: A list of nested ``#[JoinColumn]`` declarations.
- **joinTable**: A nested ``#[JoinTable]`` declaration in case of a many-to-many overwrite.
- **inversedBy**: The name of the inversedBy field on the target entity side.
- **fetch**: The fetch strategy, one of: EAGER, LAZY, EXTRA_LAZY.
Examples:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\AssociationOverride;
use Doctrine\ORM\Mapping\AssociationOverrides;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[AssociationOverrides([
new AssociationOverride(
name: "groups",
joinTable: new JoinTable(
name: "ddc964_users_admingroups",
),
joinColumns: [new JoinColumn(name: "adminuser_id")],
inverseJoinColumns: [new JoinColumn(name: "admingroup_id")]
),
new AssociationOverride(
name: "address",
joinColumns: [new JoinColumn(name: "adminaddress_id", referencedColumnName: "id")]
)
])]
class DDC964Admin extends DDC964User
{
}
.. _attrref_attributeoverride:
#[AttributeOverride]
~~~~~~~~~~~~~~~~~~~~
In an inheritance hierarchy this attribute allows to override the
field mapping definitions of the parent mappings. It needs to be nested
within a ``#[AttributeOverrides]`` on the class level.
Required parameters:
- **name**: Name of the association mapping to overwrite.
- **column**: A nested ``#[Column]`` attribute with the overwritten field settings.
Examples:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\AttributeOverride;
use Doctrine\ORM\Mapping\AttributeOverrides;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[Entity]
#[AttributeOverrides([
new AttributeOverride(
name: "id",
column: new Column(name: "guest_id", type: "integer", length: 140)
),
new AttributeOverride(
name: "name",
column: new Column(name: "guest_name", nullable: false, unique: true, length: 240)
)]
)]
class DDC964Guest extends DDC964User
{
}
.. _attrref_column:
#[Column]
@@ -59,12 +148,12 @@ inside the instance variables PHP DocBlock comment. Any value hold
inside this variable will be saved to and loaded from the database
as part of the lifecycle of the instance variables entity-class.
Required attributes:
Required parameters:
- **type**: Name of the DBAL Type which does the conversion between PHP
and Database representation.
Optional attributes:
Optional parameters:
- **name**: By default the property name is used for the database
column name also, however the ``name`` attribute allows you to
@@ -89,6 +178,18 @@ Optional attributes:
- **nullable**: Determines if NULL values allowed for this column.
If not specified, default value is ``false``.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be
included when updating the row of the underlying entities table.
If not specified, default value is true.
- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
used after an INSERT or UPDATE statement to determine if the database
generated this value and it needs to be fetched using a SELECT statement.
- **options**: Array of additional options:
- ``default``: The default value to set for the column if no value
@@ -159,13 +260,22 @@ Examples:
)]
protected $loginCount;
// MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
#[Column(
type: "string",
name: "user_fullname",
insertable: false,
updatable: false
)]
protected $fullname;
.. _attrref_cache:
#[Cache]
~~~~~~~~
Add caching strategy to a root entity or a collection.
Optional attributes:
Optional parameters:
- **usage**: One of ``READ_ONLY``, ``READ_WRITE`` or ``NONSTRICT_READ_WRITE``, By default this is ``READ_ONLY``.
- **region**: An specific region name
@@ -210,7 +320,7 @@ Example:
This attribute allows you to specify a user-provided class to generate identifiers. This attribute only works when both :ref:`#[Id] <attrref_id>` and :ref:`#[GeneratedValue(strategy: "CUSTOM")] <attrref_generatedvalue>` are specified.
Required attributes:
Required parameters:
- **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator
@@ -244,13 +354,13 @@ actually instantiated as.
If this attribute is not specified, the discriminator column defaults
to a string column of length 255 called ``dtype``.
Required attributes:
Required parameters:
- **name**: The column name of the discriminator. This name is also
used during Array hydration as key to specify the class-name.
Optional attributes:
Optional parameters:
- **type**: By default this is string.
@@ -319,7 +429,7 @@ attribute to establish the relationship between the two classes.
The embedded attribute is required on an entity's member variable,
in order to specify that it is an embedded class.
Required attributes:
Required parameters:
- **class**: The embeddable class
@@ -331,7 +441,7 @@ Required attributes:
Required attribute to mark a PHP class as an entity. Doctrine manages
the persistence of all classes marked as entities.
Optional attributes:
Optional parameters:
- **repositoryClass**: Specifies the FQCN of a subclass of the
``EntityRepository``. Use of repositories for entities is encouraged to keep
@@ -368,7 +478,7 @@ conjunction with #[Id].
If this attribute is not specified with ``#[Id]`` the ``NONE`` strategy is
used as default.
Optional attributes:
Optional parameters:
- **strategy**: Set the name of the identifier generation strategy.
Valid values are ``AUTO``, ``SEQUENCE``, ``IDENTITY``, ``UUID``
@@ -424,14 +534,14 @@ Attribute is used on the entity-class level. It provides a hint to the SchemaToo
generate a database index on the specified table columns. It only
has meaning in the ``SchemaTool`` schema generation context.
Required attributes:
Required parameters:
- **name**: Name of the Index
- **fields**: Array of fields. Exactly one of **fields, columns** is required.
- **columns**: Array of columns. Exactly one of **fields, columns** is required.
Optional attributes:
Optional parameters:
- **options**: Array of platform specific options:
@@ -546,9 +656,10 @@ are missing they will be computed considering the field's name and the current
The ``#[InverseJoinColumn]`` is the same as ``#[JoinColumn]`` and is used in the context
of a ``#[ManyToMany]`` attribute declaration to specifiy the details of the join table's
column information used for the join to the inverse entity.
column information used for the join to the inverse entity. This is only required
on PHP 8.0, where nested attributes are not yet supported.
Optional attributes:
Optional parameters:
- **name**: Column name that holds the foreign key identifier for
this relation. In the context of ``#[JoinTable]`` it specifies the column
@@ -596,7 +707,7 @@ details of the database join table. If you do not specify
using the affected table and the column names.
A notable difference to the annotation metadata support, ``#[JoinColumn]``
and ``#[InverseJoinColumn]`` are specified at the property level and are not
and ``#[InverseJoinColumn]`` can be specified at the property level and are not
nested within the ``#[JoinTable]`` attribute.
Required attribute:
@@ -623,14 +734,14 @@ Example:
Defines that the annotated instance variable holds a reference that
describes a many-to-one relationship between two entities.
Required attributes:
Required parameters:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
Optional parameters:
- **cascade**: Cascade Option
@@ -659,14 +770,14 @@ additional, optional attribute that has reasonable default
configuration values using the table and names of the two related
entities.
Required attributes:
Required parameters:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
Optional parameters:
- **mappedBy**: This option specifies the property name on the
@@ -720,7 +831,7 @@ The ``#[MappedSuperclass]`` attribute cannot be used in conjunction with
``#[Entity]``. See the Inheritance Mapping section for
:doc:`more details on the restrictions of mapped superclasses <inheritance-mapping>`.
Optional attributes:
Optional parameters:
- **repositoryClass**: Specifies the FQCN of a subclass of the EntityRepository.
That will be inherited for all subclasses of that Mapped Superclass.
@@ -756,13 +867,13 @@ be specified. When no
:ref:`#[JoinColumn] <attrref_joincolumn>` is specified it defaults to using the target entity table and
primary key column names and the current naming strategy to determine a name for the join column.
Required attributes:
Required parameters:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
Optional parameters:
- **cascade**: Cascade Option
- **fetch**: One of LAZY or EAGER
@@ -786,13 +897,13 @@ Example:
#[OneToMany]
~~~~~~~~~~~~
Required attributes:
Required parameters:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
*IMPORTANT:* No leading backslash!
Optional attributes:
Optional parameters:
- **cascade**: Cascade Option
- **orphanRemoval**: Boolean that specifies if orphans, inverse
@@ -916,11 +1027,11 @@ For use with ``#[GeneratedValue(strategy: "SEQUENCE")]`` this
attribute allows to specify details about the sequence, such as
the increment size and initial values of the sequence.
Required attributes:
Required parameters:
- **sequenceName**: Name of the sequence
Optional attributes:
Optional parameters:
- **allocationSize**: Increment the sequence by the allocation size
when its fetched. A value larger than 1 allows optimization for
@@ -954,11 +1065,11 @@ placed on the entity-class level and is optional. If it is
not specified the table name will default to the entity's
unqualified classname.
Required attributes:
Required parameters:
- **name**: Name of the table
Optional attributes:
Optional parameters:
- **schema**: Name of the schema the table lies in.
@@ -985,12 +1096,12 @@ generate a database unique constraint on the specified table
columns. It only has meaning in the SchemaTool schema generation
context.
Required attributes:
Required parameters:
- **name**: Name of the Index
- **columns**: Array of columns.
Optional attributes:
Optional parameters:
- **options**: Array of platform specific options:

View File

@@ -199,6 +199,12 @@ list:
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``insertable``: (optional, default TRUE) Whether the database
column should be inserted.
- ``updatable``: (optional, default TRUE) Whether the database
column should be updated.
- ``enumType``: (optional, requires PHP 8.1 and ORM 2.11) The PHP enum type
name to convert the database value into.
- ``precision``: (optional, 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.
@@ -233,6 +239,9 @@ Additionally, Doctrine will map PHP types to ``type`` attribute as follows:
- ``int``: ``integer``
- ``string`` or any other type: ``string``
As of version 2.11 Doctrine can also automatically map typed properties using a
PHP 8.1 enum to set the right ``type`` and ``enumType``.
.. _reference-mapping-types:
Doctrine Mapping Types

View File

@@ -670,11 +670,23 @@ The same restrictions apply for the reference of related entities.
.. warning::
DQL DELETE statements are ported directly into a
Database DELETE statement and therefore bypass any events and checks for the
version column if they are not explicitly added to the WHERE clause
of the query. Additionally Deletes of specified entities are *NOT*
cascaded to related entities even if specified in the metadata.
DQL DELETE statements are ported directly into an SQL DELETE statement.
Therefore, some limitations apply:
- Lifecycle events for the affected entities are not executed.
- A cascading ``remove`` operation (as indicated e. g. by ``cascade={"remove"}``
or ``cascade={"all"}`` in the mapping configuration) is not being performed
for associated entities. You can rely on database level cascade operations by
configuring each join column with the ``onDelete`` option.
- Checks for the version column are bypassed if they are not explicitly added
to the WHERE clause of the query.
When you rely on one of these features, one option is to use the
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
It means collections and related entities are fetched into memory
(even if they are marked as lazy). Pulling object graphs into memory on cascade
can cause considerable performance overhead, especially when the cascaded collections
are large. Make sure to weigh the benefits and downsides.
Comments in queries
-------------------
@@ -718,7 +730,7 @@ clauses:
- ``SQRT(q)`` - Return the square-root of q.
- ``SUBSTRING(str, start [, length])`` - Return substring of given
string.
- ``TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str)`` - Trim
- ``TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str)`` - Trim
the string by the given trim char, defaults to whitespaces.
- ``UPPER(str)`` - Return the upper-case of the given string.
- ``DATE_ADD(date, value, unit)`` - Add the given time to a given date.
@@ -1712,7 +1724,7 @@ Literal Values
.. code-block:: php
Literal ::= string | char | integer | float | boolean
InParameter ::= Literal | InputParameter
InParameter ::= ArithmeticExpression | InputParameter
Input Parameter
~~~~~~~~~~~~~~~

View File

@@ -121,52 +121,55 @@ Now you can test the ``$eventSubscriber`` instance to see if the
echo 'pre foo invoked!';
}
Registering Events
~~~~~~~~~~~~~~~~~~
Registering Event Handlers
~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two ways to register an event:
There are two ways to set up an event handler:
* *All events* can be registered by calling ``$eventManager->addEventListener()``
or ``eventManager->addEventSubscriber()``, see
* For *all events* you can create a Lifecycle Event Listener or Subscriber class and register
it by calling ``$eventManager->addEventListener()`` or ``eventManager->addEventSubscriber()``,
see
:ref:`Listening and subscribing to Lifecycle Events<listening-and-subscribing-to-lifecycle-events>`
* *Lifecycle Callbacks* can also be registered in the entity mapping (annotation, attribute, etc.),
see :ref:`Lifecycle Callbacks<lifecycle-callbacks>`
* For *some events* (see table below), you can create a *Lifecycle Callback* method in the
entity, see :ref:`Lifecycle Callbacks<lifecycle-callbacks>`.
.. _reference-events-lifecycle-events:
Events Overview
---------------
+-----------------------------------------------------------------+-----------------------+-----------+
| Event | Dispatched by | Lifecycle |
| | | Callback |
+=================================================================+=======================+===========+
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes |
| | on *initial* persist | |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No |
| | metadata | |
+-----------------------------------------------------------------+-----------------------+-----------+
| ``onClassMetadataNotFound`` | ``MappingException`` | No |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`preFlush<reference-events-pre-flush>` | ``$em->flush()`` | Yes |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`onFlush<reference-events-on-flush>` | ``$em->flush()`` | No |
+-----------------------------------------------------------------+-----------------------+-----------+
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No |
+-----------------------------------------------------------------+-----------------------+-----------+
| ``onClear`` | ``$em->clear()`` | No |
+-----------------------------------------------------------------+-----------------------+-----------+
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| Event | Dispatched by | Lifecycle | Passed |
| | | Callback | Argument |
+=================================================================+=======================+===========+=====================================+
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `_LifecycleEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `_LifecycleEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `_LifecycleEventArgs`_ |
| | on *initial* persist | | |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `_LifecycleEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `_PreUpdateEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `_LifecycleEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `_LifecycleEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `_LoadClassMetadataEventArgs` |
| | metadata | | |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `_OnClassMetadataNotFoundEventArgs` |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preFlush<reference-events-pre-flush>` | ``$em->flush()`` | Yes | `_PreFlushEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onFlush<reference-events-on-flush>` | ``$em->flush()`` | No | `_OnFlushEventArgs` |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No | `_PostFlushEventArgs` |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onClear<reference-events-on-clear>` | ``$em->clear()`` | No | `_OnClearEventArgs` |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
Naming convention
~~~~~~~~~~~~~~~~~
@@ -187,94 +190,6 @@ several reasons:
An example for a correct notation can be found in the example
``TestEvent`` above.
.. _reference-events-lifecycle-events:
Lifecycle Events
----------------
The ``EntityManager`` and ``UnitOfWork`` classes trigger a bunch of
events during the life-time of their registered entities.
- ``preRemove`` - The ``preRemove`` event occurs for a given entity
before the respective ``EntityManager`` remove operation for that
entity is executed. It is not called for a DQL ``DELETE`` statement.
- ``postRemove`` - The ``postRemove`` event occurs for an entity after the
entity has been deleted. It will be invoked after the database
delete operations. It is not called for a DQL ``DELETE`` statement.
- ``prePersist`` - The ``prePersist`` event occurs for a given entity
before the respective ``EntityManager`` persist operation for that
entity is executed. It should be noted that this event is only triggered on
*initial* persist of an entity (i.e. it does not trigger on future updates).
- ``postPersist`` - The ``postPersist`` event occurs for an entity after
the entity has been made persistent. It will be invoked after the
database insert operations. Generated primary key values are
available in the postPersist event.
- ``preUpdate`` - The ``preUpdate`` event occurs before the database
update operations to entity data. It is not called for a DQL
``UPDATE`` statement nor when the computed changeset is empty.
- ``postUpdate`` - The ``postUpdate`` event occurs after the database
update operations to entity data. It is not called for a DQL
``UPDATE`` statement.
- ``postLoad`` - The postLoad event occurs for an entity after the
entity has been loaded into the current ``EntityManager`` from the
database or after the refresh operation has been applied to it.
- ``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml/yaml). This event is not a lifecycle callback.
- ``onClassMetadataNotFound`` - Loading class metadata for a particular
requested class name failed. Manipulating the given event args instance
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
- ``preFlush`` - The ``preFlush`` event occurs at the very beginning of
a flush operation.
- ``onFlush`` - The ``onFlush`` event occurs after the change-sets of all
managed entities are computed. This event is not a lifecycle
callback.
- ``postFlush`` - The ``postFlush`` event occurs at the end of a flush operation. This
event is not a lifecycle callback.
- ``onClear`` - The ``onClear`` event occurs when the
``EntityManager#clear()`` operation is invoked, after all references
to entities have been removed from the unit of work. This event is not
a lifecycle callback.
.. warning::
Note that, when using ``Doctrine\ORM\AbstractQuery#toIterable()``, ``postLoad``
events will be executed immediately after objects are being hydrated, and therefore
associations are not guaranteed to be initialized. It is not safe to combine
usage of ``Doctrine\ORM\AbstractQuery#toIterable()`` and ``postLoad`` event
handlers.
.. warning::
Note that the ``postRemove`` event or any events triggered after an entity removal
can receive an uninitializable proxy in case you have configured an entity to
cascade remove relations. In this case, you should load yourself the proxy in
the associated pre event.
These can be hooked into by two different types of event
listeners:
- Lifecycle Callbacks are methods on the entity classes that are
called when the event is triggered. They receive some kind
of ``EventArgs`` instance.
- Lifecycle Event Listeners and Subscribers are classes with specific callback
methods that receives some kind of ``EventArgs`` instance.
The ``EventArgs`` instance received by the listener gives access to the entity,
``EntityManager`` instance and other relevant data.
.. note::
All Lifecycle events that happen during the ``flush()`` of
an ``EntityManager`` have very specific constraints on the allowed
operations that can be executed. Please read the
:ref:`reference-events-implementing-listeners` section very carefully
to understand which operations are allowed in which lifecycle event.
.. _lifecycle-callbacks:
Lifecycle Callbacks
@@ -296,20 +211,21 @@ specific to a particular entity class's lifecycle.
.. code-block:: attribute
<?php
use Doctrine\DBAL\Types\Types;
use Doctrine\Persistence\Event\LifecycleEventArgs;
/**
* #[Entity]
* #[HasLifecycleCallbacks]
*/
#[Entity]
#[HasLifecycleCallbacks]
class User
{
// ...
#[Column(type: 'string', length: 255)]
#[Column(type: Types::STRING, length: 255)]
public $value;
#[PrePersist]
public function doStuffOnPrePersist()
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
@@ -320,15 +236,17 @@ specific to a particular entity class's lifecycle.
$this->value = 'changed from prePersist callback!';
}
#[PostLoad]
public function doStuffOnPostLoad()
#[PreUpdate]
public function doStuffOnPreUpdate(PreUpdateEventArgs $eventArgs)
{
$this->value = 'changed from postLoad callback!';
$this->value = 'changed from preUpdate callback!';
}
}
.. code-block:: annotation
<?php
use Doctrine\Persistence\Event\LifecycleEventArgs;
/**
* @Entity
@@ -342,7 +260,7 @@ specific to a particular entity class's lifecycle.
public $value;
/** @PrePersist */
public function doStuffOnPrePersist()
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
@@ -353,10 +271,10 @@ specific to a particular entity class's lifecycle.
$this->value = 'changed from prePersist callback!';
}
/** @PostLoad */
public function doStuffOnPostLoad()
/** @PreUpdate */
public function doStuffOnPreUpdate(PreUpdateEventArgs $eventArgs)
{
$this->value = 'changed from postLoad callback!';
$this->value = 'changed from preUpdate callback!';
}
}
.. code-block:: xml
@@ -372,7 +290,7 @@ specific to a particular entity class's lifecycle.
<lifecycle-callbacks>
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersist"/>
<lifecycle-callback type="postLoad" method="doStuffOnPostLoad"/>
<lifecycle-callback type="preUpdate" method="doStuffOnPreUpdate"/>
</lifecycle-callbacks>
</entity>
</doctrine-mapping>
@@ -386,7 +304,7 @@ specific to a particular entity class's lifecycle.
type: string(255)
lifecycleCallbacks:
prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersist ]
postLoad: [ doStuffOnPostLoad ]
preUpdate: [ doStuffOnPreUpdate ]
Lifecycle Callbacks Event Argument
----------------------------------
@@ -533,21 +451,23 @@ that (prior to version 2.4) you do not have access to the
prePersist
~~~~~~~~~~
There are two ways for the ``prePersist`` event to be triggered.
One is obviously when you call ``EntityManager#persist()``. The
event is also called for all cascaded associations.
There are two ways for the ``prePersist`` event to be triggered:
There is another way for ``prePersist`` to be called, inside the
- One is obviously when you call ``EntityManager::persist()``. The
event is also called for all :ref:`cascaded associations<transitive-persistence>`.
- The other is inside the
``flush()`` method when changes to associations are computed and
this association is marked as cascade persist. Any new entity found
this association is marked as :ref:`cascade: persist<transitive-persistence>`. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called "persistence by reachability".
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
In both cases you get passed a ``LifecycleEventArgs`` instance
which has access to the entity and the entity manager.
The following restrictions apply to ``prePersist``:
This event is only triggered on *initial* persist of an entity
(i.e. it does not trigger on future updates).
The following restrictions apply to ``prePersist``:
- If you are using a PrePersist Identity Generator such as
sequences the ID value will *NOT* be available within any
@@ -555,15 +475,17 @@ The following restrictions apply to ``prePersist``:
- Doctrine will not recognize changes made to relations in a prePersist
event. This includes modifications to
collections such as additions, removals or replacement.
.. _reference-events-pre-remove:
preRemove
~~~~~~~~~
The ``preRemove`` event is called on every entity when its passed
to the ``EntityManager#remove()`` method. It is cascaded for all
associations that are marked as cascade delete.
The ``preRemove`` event is called on every entity immediately when it is passed
to the ``EntityManager::remove()`` method. It is cascaded for all
associations that are marked as :ref:`cascade: remove<transitive-persistence>`
It is not called for a DQL ``DELETE`` statement.
There are no restrictions to what methods can be called inside the
``preRemove`` event, except when the remove method itself was
@@ -574,10 +496,10 @@ called during a flush operation.
preFlush
~~~~~~~~
``preFlush`` is called at ``EntityManager#flush()`` before
anything else. ``EntityManager#flush()`` should not be called inside
its listeners, since `preFlush` event is dispatched in it, which would
result in infinite loop.
``preFlush`` is called inside ``EntityManager::flush()`` before
anything else. ``EntityManager::flush()`` must not be called inside
its listeners, since it would fire the ``preFlush`` event again, which would
result in an infinite loop.
.. code-block:: php
@@ -598,12 +520,11 @@ result in infinite loop.
onFlush
~~~~~~~
OnFlush is a very powerful event. It is called inside
``EntityManager#flush()`` after the changes to all the managed
``onFlush`` is a very powerful event. It is called inside
``EntityManager::flush()`` after the changes to all the managed
entities and their associations have been computed. This means, the
``onFlush`` event has access to the sets of:
- Entities scheduled for insert
- Entities scheduled for update
- Entities scheduled for removal
@@ -611,7 +532,7 @@ entities and their associations have been computed. This means, the
- Collections scheduled for removal
To make use of the ``onFlush`` event you have to be familiar with the
internal ``UnitOfWork`` API, which grants you access to the previously
internal :ref:`UnitOfWork<unit-of-work>` API, which grants you access to the previously
mentioned sets. See this example:
.. code-block:: php
@@ -646,11 +567,10 @@ mentioned sets. See this example:
}
}
The following restrictions apply to the onFlush event:
The following restrictions apply to the ``onFlush`` event:
- If you create and persist a new entity in ``onFlush``, then
calling ``EntityManager#persist()`` is not enough.
calling ``EntityManager::persist()`` is not enough.
You have to execute an additional call to
``$unitOfWork->computeChangeSet($classMetadata, $entity)``.
- Changing primitive fields or associations requires you to
@@ -663,8 +583,9 @@ The following restrictions apply to the onFlush event:
postFlush
~~~~~~~~~
``postFlush`` is called at the end of ``EntityManager#flush()``.
``EntityManager#flush()`` can **NOT** be called safely inside its listeners.
``postFlush`` is called at the end of ``EntityManager::flush()``.
``EntityManager::flush()`` can **NOT** be called safely inside its listeners.
This event is not a lifecycle callback.
.. code-block:: php
@@ -685,22 +606,22 @@ postFlush
preUpdate
~~~~~~~~~
PreUpdate is called inside the ``EntityManager#flush()`` method,
PreUpdate is called inside the ``EntityManager::flush()`` method,
right before an SQL ``UPDATE`` statement. This event is not
triggered when the computed changeset is empty.
triggered when the computed changeset is empty, nor for a DQL
``UPDATE`` statement.
Changes to associations of the updated entity are never allowed in
this event, since Doctrine cannot guarantee to correctly handle
referential integrity at this point of the flush operation. This
event has a powerful feature however, it is executed with a
``PreUpdateEventArgs`` instance, which contains a reference to the
`_PreUpdateEventArgs`_ instance, which contains a reference to the
computed change-set of this entity.
This means you have access to all the fields that have changed for
this entity with their old and new value. The following methods are
available on the ``PreUpdateEventArgs``:
- ``getEntity()`` to get access to the actual entity.
- ``getEntityChangeSet()`` to get a copy of the changeset array.
Changes to this returned array do not affect updating.
@@ -754,15 +675,14 @@ lifecycle callback when there are expensive validations to call:
Restrictions for this event:
- Changes to associations of the passed entities are not
recognized by the flush operation anymore.
- Changes to fields of the passed entities are not recognized by
the flush operation anymore, use the computed change-set passed to
the event to modify primitive field values, e.g. use
``$eventArgs->setNewValue($field, $value);`` as in the Alice to Bob example above.
- Any calls to ``EntityManager#persist()`` or
``EntityManager#remove()``, even in combination with the ``UnitOfWork``
- Any calls to ``EntityManager::persist()`` or
``EntityManager::remove()``, even in combination with the ``UnitOfWork``
API are strongly discouraged and don't work as expected outside the
flush operation.
@@ -771,19 +691,54 @@ Restrictions for this event:
postUpdate, postRemove, postPersist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The three post events are called inside ``EntityManager#flush()``.
These three post* events are called inside ``EntityManager::flush()``.
Changes in here are not relevant to the persistence in the
database, but you can use these events to alter non-persistable items,
like non-mapped fields, logging or even associated classes that are
not directly mapped by Doctrine.
- The ``postUpdate`` event occurs after the database
update operations to entity data. It is not called for a DQL
``UPDATE`` statement.
- The ``postPersist`` event occurs for an entity after
the entity has been made persistent. It will be invoked after the
database insert operations. Generated primary key values are
available in the postPersist event.
- The ``postRemove`` event occurs for an entity after the
entity has been deleted. It will be invoked after the database
delete operations. It is not called for a DQL ``DELETE`` statement.
.. warning::
The ``postRemove`` event or any events triggered after an entity removal
can receive an uninitializable proxy in case you have configured an entity to
cascade remove relations. In this case, you should load yourself the proxy in
the associated ``pre*`` event.
.. _reference-events-post-load:
postLoad
~~~~~~~~
This event is called after an entity is constructed by the
EntityManager.
The postLoad event occurs after the entity has been loaded into the current
``EntityManager`` from the database or after ``refresh()`` has been applied to it.
.. warning::
When using ``Doctrine\ORM\AbstractQuery::toIterable()``, ``postLoad``
events will be executed immediately after objects are being hydrated, and therefore
associations are not guaranteed to be initialized. It is not safe to combine
usage of ``Doctrine\ORM\AbstractQuery::toIterable()`` and ``postLoad`` event
handlers.
.. _reference-events-on-clear:
onClear
~~~~~~~~
The ``onClear`` event occurs when the ``EntityManager::clear()`` operation is invoked,
after all references to entities have been removed from the unit of work.
This event is not a lifecycle callback.
Entity listeners
----------------
@@ -1008,9 +963,11 @@ Implementing your own resolver :
Load ClassMetadata Event
------------------------
When the mapping information for an entity is read, it is populated
in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance. You can hook in to this
process and manipulate the instance.
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
You can hook in to this process and manipulate the instance.
This event is not a lifecycle callback.
.. code-block:: php
@@ -1033,6 +990,11 @@ process and manipulate the instance.
}
}
If not class metadata can be found, an ``onClassMetadataNotFound`` event is dispatched.
Manipulating the given event args instance
allows providing fallback metadata even when no actual metadata exists
or could be found. This event is not a lifecycle callback.
SchemaTool Events
-----------------
@@ -1085,3 +1047,12 @@ and the EntityManager.
$em = $eventArgs->getEntityManager();
}
}
.. _LifecycleEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/LifecycleEventArgs.php
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php
.. _PreFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreFlushEventArgs.php
.. _PostFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostFlushEventArgs.php
.. _OnFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/OnFlushEventArgs.php
.. _OnClearEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/OnClearEventArgs.php
.. _LoadClassMetadataEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/LoadClassMetadataEventArgs.php
.. _OnClassMetadataNotFoundEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/OnClassMetadataNotFoundEventArgs.php

View File

@@ -9,6 +9,11 @@ the code in PHP files or inside of a static function named
PHP Files
---------
.. note::
PHPDriver is deprecated and will be removed in 3.0, use StaticPHPDriver
instead.
If you wish to write your mapping information inside PHP files that
are named after the entity and included to populate the metadata
for an entity you can do so by using the ``PHPDriver``:

View File

@@ -31,31 +31,31 @@ Each cache region resides in a specific cache namespace and has its own lifetime
Notice that when caching collection and queries only identifiers are stored.
The entity values will be stored in its own region
Something like below for an entity region :
Something like below for an entity region:
.. code-block:: php
<?php
[
'region_name:entity_1_hash' => ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null],
'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]],
'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]]
'region_name:entity_1_hash' => ['id' => 1, 'name' => 'FooBar', 'associationName' => null],
'region_name:entity_2_hash' => ['id' => 2, 'name' => 'Foo', 'associationName' => ['id' => 11]],
'region_name:entity_3_hash' => ['id' => 3, 'name' => 'Bar', 'associationName' => ['id' => 22]]
];
If the entity holds a collection that also needs to be cached.
An collection region could look something like :
An collection region could look something like:
.. code-block:: php
<?php
[
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId'=> 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]]
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId' => 1, 'list' => [1, 2, 3]],
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId' => 2, 'list' => [2, 3]],
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId' => 3, 'list' => [2, 4]]
];
A query region might be something like :
A query region might be something like:
.. code-block:: php
@@ -93,8 +93,6 @@ Cache region
``Doctrine\ORM\Cache\Region`` defines a contract for accessing a particular
cache region.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Region.html>`_.
Concurrent cache region
~~~~~~~~~~~~~~~~~~~~~~~
@@ -105,8 +103,6 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
``Doctrine\ORM\Cache\ConcurrentRegion`` defines a contract for concurrently managed data region.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
Timestamp region
~~~~~~~~~~~~~~~~
@@ -114,8 +110,6 @@ Timestamp region
Tracks the timestamps of the most recent updates to particular entity.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
.. _reference-second-level-cache-mode:
Caching mode
@@ -132,7 +126,7 @@ Caching mode
* Read Write Cache doesnt employ any locks but can do reads, inserts, updates and deletes.
* Good if the application needs to update data rarely.
* ``READ_WRITE``
@@ -147,21 +141,21 @@ Built-in cached persisters
Cached persisters are responsible to access cache regions.
+-----------------------+-------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+===========================================================================================+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadOnlyCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\NonStrictReadWriteCachedEntityPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadOnlyCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister |
+-----------------------+-------------------------------------------------------------------------------------------+
+-----------------------+------------------------------------------------------------------------------------------+
| Cache Usage | Persister |
+=======================+==========================================================================================+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister`` |
+-----------------------+------------------------------------------------------------------------------------------+
Configuration
-------------
@@ -172,13 +166,13 @@ Enable Second Level Cache
~~~~~~~~~~~~~~~~~~~~~~~~~
To enable the second-level-cache, you should provide a cache factory.
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
``Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
.. code-block:: php
<?php
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig */
/** @var \Doctrine\Common\Cache\Cache $cache */
/** @var \Psr\Cache\CacheItemPoolInterface $cache */
/** @var \Doctrine\ORM\Configuration $config */
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($cacheConfig, $cache);
@@ -196,7 +190,7 @@ Cache Factory
Cache Factory is the main point of extension.
It allows you to provide a specific implementation of the following components :
It allows you to provide a specific implementation of the following components:
``QueryCache``
stores and retrieves query cache results.
@@ -209,8 +203,6 @@ It allows you to provide a specific implementation of the following components :
``CollectionHydrator``
transforms collections into cache entries and cache entries into collections
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
Region Lifetime
~~~~~~~~~~~~~~~
@@ -234,12 +226,12 @@ Cache Log
~~~~~~~~~
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
``Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
.. code-block:: php
<?php
/* @var $config \Doctrine\ORM\Configuration */
/** @var \Doctrine\ORM\Configuration $config */
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
// Cache logger
@@ -269,12 +261,9 @@ By providing a cache logger you should be able to get information about all cach
$logger->getMissCount();
If you want to get more information you should implement
``\Doctrine\ORM\Cache\Logging\CacheLogger`` and collect
``Doctrine\ORM\Cache\Logging\CacheLogger`` and collect
all the information you want.
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
Entity cache definition
-----------------------
* Entity cache configuration allows you to define the caching strategy and region for an entity.
@@ -330,8 +319,8 @@ level cache region.
Country:
type: entity
cache:
usage : READ_ONLY
region : my_entity_region
usage: READ_ONLY
region: my_entity_region
id:
id:
type: integer
@@ -401,7 +390,7 @@ It caches the primary keys of association and cache each element will be cached
</id>
<field name="name" type="string" column="name"/>
<many-to-one field="country" target-entity="Country">
<cache usage="NONSTRICT_READ_WRITE" />
@@ -421,7 +410,7 @@ It caches the primary keys of association and cache each element will be cached
State:
type: entity
cache:
usage : NONSTRICT_READ_WRITE
usage: NONSTRICT_READ_WRITE
id:
id:
type: integer
@@ -439,17 +428,18 @@ It caches the primary keys of association and cache each element will be cached
country_id:
referencedColumnName: id
cache:
usage : NONSTRICT_READ_WRITE
usage: NONSTRICT_READ_WRITE
oneToMany:
cities:
targetEntity:City
mappedBy: state
cache:
usage : NONSTRICT_READ_WRITE
usage: NONSTRICT_READ_WRITE
.. note::
> Note: for this to work, the target entity must also be marked as cacheable.
for this to work, the target entity must also be marked as cacheable.
Cache usage
~~~~~~~~~~~
@@ -466,8 +456,8 @@ Basic entity cache
$country1 = $em->find('Country', 1); // Retrieve item from cache
$country1->setName("New Name");
$country1->setName('New Name');
$em->flush(); // Hit database to update the row and update cache
$em->clear(); // Clear entity manager
@@ -492,7 +482,7 @@ Association cache
$state = $em->find('State', 1);
// Hit database to update the row and update cache entry
$state->setName("New Name");
$state->setName('New Name');
$em->persist($state);
$em->flush();
@@ -543,14 +533,14 @@ The query cache stores the results of the query but as identifiers, entity value
.. code-block:: php
<?php
/* @var $em \Doctrine\ORM\EntityManager */
/** @var \Doctrine\ORM\EntityManager $em */
// Execute database query, store query cache and entity cache
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheable(true)
->getResult();
$em->clear()
$em->clear();
// Check if query result is valid and load entities from cache
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
@@ -570,10 +560,10 @@ The Cache Mode controls how a particular query interacts with the second-level c
.. code-block:: php
<?php
/* @var $em \Doctrine\ORM\EntityManager */
/** @var \Doctrine\ORM\EntityManager $em */
// Will refresh the query cache and all entities the cache as it reads from the database.
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
->setCacheMode(Cache::MODE_GET)
->setCacheMode(\Doctrine\ORM\Cache::MODE_GET)
->setCacheable(true)
->getResult();
@@ -597,7 +587,7 @@ Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_
<?php
// Execute and invalidate
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
->setHint(Query::HINT_CACHE_EVICT, true)
->setHint(\Doctrine\ORM\Query::HINT_CACHE_EVICT, true)
->execute();
@@ -659,7 +649,7 @@ However, you can use the cache API to check / invalidate cache entries.
.. code-block:: php
<?php
/* @var $cache \Doctrine\ORM\Cache */
/** @var \Doctrine\ORM\Cache $cache */
$cache = $em->getCache();
$cache->containsEntity('Entity\State', 1) // Check if the cache exists
@@ -704,19 +694,19 @@ For performance reasons the cache API does not extract from composite primary ke
}
// Supported
/* @var $article Article */
/** @var Article $article */
$article = $em->find('Article', 1);
// Supported
/* @var $article Article */
/** @var Article $article */
$article = $em->find('Article', $article);
// Supported
$id = array('source' => 1, 'target' => 2);
$id = ['source' => 1, 'target' => 2];
$reference = $em->find('Reference', $id);
// NOT Supported
$id = array('source' => new Article(1), 'target' => new Article(2));
$id = ['source' => new Article(1), 'target' => new Article(2)];
$reference = $em->find('Reference', $id);
Distributed environments

View File

@@ -521,6 +521,8 @@ For each cascade operation that gets activated, Doctrine also
applies that operation to the association, be it single or
collection valued.
.. _persistence-by-reachability:
Persistence by Reachability: Cascade Persist
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -256,6 +256,11 @@ Optional attributes:
table? Defaults to false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- insertable - Should this field be inserted? Defaults to true.
- updatable - Should this field be updated? Defaults to true.
- generated - Enum of the values ALWAYS, INSERT, NEVER that determines if
generated value must be fetched from database after INSERT or UPDATE.
Defaults to "NEVER".
- version - Should this field be used for optimistic locking? Only
works on fields with type integer or datetime.
- scale - Scale of a decimal type.

View File

@@ -288,6 +288,14 @@
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="generated-type">
<xs:restriction base="xs:token">
<xs:enumeration value="NEVER"/>
<xs:enumeration value="INSERT"/>
<xs:enumeration value="ALWAYS"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="field">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="options" type="orm:options" minOccurs="0" />
@@ -299,6 +307,10 @@
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="false" />
<xs:attribute name="insertable" type="xs:boolean" default="true" />
<xs:attribute name="updatable" type="xs:boolean" default="true" />
<xs:attribute name="generated" type="orm:generated-type" default="NEVER" />
<xs:attribute name="enum-type" type="xs:string" />
<xs:attribute name="version" type="xs:boolean" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="precision" type="xs:integer" use="optional" />
@@ -353,7 +365,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="columns" type="xs:string" use="optional"/>
<xs:attribute name="fields" type="xs:string" use="optional"/>
<xs:attribute name="flags" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
@@ -622,6 +634,8 @@
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
<xs:attribute name="nullable" type="xs:boolean" default="false" />
<xs:attribute name="insertable" type="xs:boolean" default="true" />
<xs:attribute name="updateable" type="xs:boolean" default="true" />
<xs:attribute name="version" type="xs:boolean" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="precision" type="xs:integer" use="optional" />

View File

@@ -22,6 +22,7 @@ use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\Mapping\MappingException;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use Traversable;
@@ -91,7 +92,7 @@ abstract class AbstractQuery
/**
* The user-specified ResultSetMapping to use.
*
* @var ResultSetMapping
* @var ResultSetMapping|null
*/
protected $_resultSetMapping;
@@ -113,6 +114,7 @@ abstract class AbstractQuery
* The hydration mode.
*
* @var string|int
* @psalm-var string|AbstractQuery::HYDRATE_*
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
@@ -150,6 +152,7 @@ abstract class AbstractQuery
* Second level query cache mode.
*
* @var int|null
* @psalm-var Cache::MODE_*|null
*/
protected $cacheMode;
@@ -251,7 +254,8 @@ abstract class AbstractQuery
}
/**
* @return int
* @return int|null
* @psalm-return Cache::MODE_*|null
*/
public function getCacheMode()
{
@@ -260,6 +264,7 @@ abstract class AbstractQuery
/**
* @param int $cacheMode
* @psalm-param Cache::MODE_* $cacheMode
*
* @return $this
*/
@@ -492,7 +497,7 @@ abstract class AbstractQuery
/**
* Gets the ResultSetMapping used for hydration.
*
* @return ResultSetMapping
* @return ResultSetMapping|null
*/
protected function getResultSetMapping()
{
@@ -540,7 +545,7 @@ abstract class AbstractQuery
return $this;
}
// DBAL < 3.2
// DBAL 2
if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
if (! $profile->getResultCacheDriver()) {
$defaultHydrationCacheImpl = $this->_em->getConfiguration()->getHydrationCache();
@@ -584,7 +589,7 @@ abstract class AbstractQuery
return $this;
}
// DBAL < 3.2
// DBAL 2
if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
if (! $profile->getResultCacheDriver()) {
$defaultResultCacheDriver = $this->_em->getConfiguration()->getResultCache();
@@ -640,7 +645,7 @@ abstract class AbstractQuery
return $this;
}
// DBAL < 3.2
// DBAL 2
if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
$resultCacheDriver = DoctrineProvider::wrap($resultCache);
@@ -746,7 +751,7 @@ abstract class AbstractQuery
return $this;
}
// Compatibility for DBAL < 3.2
// Compatibility for DBAL 2
if (! method_exists($this->_queryCacheProfile, 'setResultCache')) {
$this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver(DoctrineProvider::wrap($cache));
@@ -829,6 +834,7 @@ abstract class AbstractQuery
*
* @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
* One of the Query::HYDRATE_* constants.
* @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode
*
* @return $this
*/
@@ -843,6 +849,7 @@ abstract class AbstractQuery
* Gets the hydration mode currently used by the query.
*
* @return string|int
* @psalm-return string|AbstractQuery::HYDRATE_*
*/
public function getHydrationMode()
{
@@ -855,6 +862,7 @@ abstract class AbstractQuery
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
*
* @param string|int $hydrationMode
* @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode
*
* @return mixed
*/
@@ -902,7 +910,8 @@ abstract class AbstractQuery
/**
* Get exactly one result or null.
*
* @param string|int $hydrationMode
* @param string|int|null $hydrationMode
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return mixed
*
@@ -939,7 +948,8 @@ abstract class AbstractQuery
* If the result is not unique, a NonUniqueResultException is thrown.
* If there is no result, a NoResultException is thrown.
*
* @param string|int $hydrationMode
* @param string|int|null $hydrationMode
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return mixed
*
@@ -1037,6 +1047,7 @@ abstract class AbstractQuery
*
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
*
* @return IterableResult
*/
@@ -1057,7 +1068,11 @@ abstract class AbstractQuery
$this->setParameters($parameters);
}
$rsm = $this->getResultSetMapping();
$rsm = $this->getResultSetMapping();
if ($rsm === null) {
throw new LogicException('Uninitialized result set mapping.');
}
$stmt = $this->_doExecute();
return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
@@ -1070,6 +1085,7 @@ abstract class AbstractQuery
* @param ArrayCollection|array|mixed[] $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return iterable<mixed>
*/
@@ -1087,6 +1103,9 @@ abstract class AbstractQuery
}
$rsm = $this->getResultSetMapping();
if ($rsm === null) {
throw new LogicException('Uninitialized result set mapping.');
}
if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
throw QueryException::iterateWithMixedResultNotAllowed();
@@ -1103,6 +1122,7 @@ abstract class AbstractQuery
* @param ArrayCollection|mixed[]|null $parameters Query parameters.
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return mixed
*/
@@ -1121,6 +1141,7 @@ abstract class AbstractQuery
* @param ArrayCollection|mixed[]|null $parameters
* @param string|int|null $hydrationMode
* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return mixed
*/
@@ -1165,7 +1186,11 @@ abstract class AbstractQuery
return $stmt;
}
$rsm = $this->getResultSetMapping();
$rsm = $this->getResultSetMapping();
if ($rsm === null) {
throw new LogicException('Uninitialized result set mapping.');
}
$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
$setCacheEntry($data);
@@ -1177,7 +1202,7 @@ abstract class AbstractQuery
{
assert($this->_hydrationCacheProfile !== null);
// Support for DBAL < 3.2
// Support for DBAL 2
if (! method_exists($this->_hydrationCacheProfile, 'getResultCache')) {
$cacheDriver = $this->_hydrationCacheProfile->getResultCacheDriver();
assert($cacheDriver !== null);
@@ -1197,12 +1222,17 @@ abstract class AbstractQuery
* @param ArrayCollection|mixed[]|null $parameters
* @param string|int|null $hydrationMode
* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
*
* @return mixed
*/
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
{
$rsm = $this->getResultSetMapping();
$rsm = $this->getResultSetMapping();
if ($rsm === null) {
throw new LogicException('Uninitialized result set mapping.');
}
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
$queryKey = new QueryCacheKey(
$this->getHash(),
@@ -1237,6 +1267,7 @@ abstract class AbstractQuery
private function getTimestampKey(): ?TimestampCacheKey
{
assert($this->_resultSetMapping !== null);
$entityName = reset($this->_resultSetMapping->aliasMap);
if (empty($entityName)) {

View File

@@ -10,15 +10,13 @@ namespace Doctrine\ORM\Cache;
class AssociationCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var array<string, mixed> The entity identifier
*/
public $identifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The entity class name
*/
public $class;

View File

@@ -11,8 +11,7 @@ namespace Doctrine\ORM\Cache;
abstract class CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string Unique identifier
*/
public $hash;

View File

@@ -10,8 +10,7 @@ namespace Doctrine\ORM\Cache;
class CollectionCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var CacheKey[] The list of entity identifiers hold by the collection
*/
public $identifiers;

View File

@@ -15,22 +15,19 @@ use function strtolower;
class CollectionCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var array<string, mixed> The owner entity identifier
*/
public $ownerIdentifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The owner entity class
*/
public $entityClass;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The association name
*/
public $association;

View File

@@ -28,7 +28,7 @@ interface CollectionHydrator
* @param CollectionCacheEntry $entry The cached collection entry.
* @param PersistentCollection $collection The collection to load the cache into.
*
* @return mixed[]
* @return mixed[]|null
*/
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
}

View File

@@ -18,7 +18,7 @@ interface ConcurrentRegion extends Region
*
* @param CacheKey $key The key of the item to lock.
*
* @return Lock A lock instance or NULL if the lock already exists.
* @return Lock|null A lock instance or NULL if the lock already exists.
*
* @throws LockException Indicates a problem accessing the region.
*/
@@ -30,7 +30,7 @@ interface ConcurrentRegion extends Region
* @param CacheKey $key The key of the item to unlock.
* @param Lock $lock The lock previously obtained from {@link readLock}
*
* @return void
* @return bool
*
* @throws LockException Indicates a problem accessing the region.
*/

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache;
use Doctrine\Common\Cache\Cache as CacheAdapter;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\Common\Cache\Cache as LegacyCache;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister;
@@ -14,7 +14,6 @@ use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister;
use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister;
use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
use Doctrine\ORM\Cache\Region\DefaultMultiGetRegion;
use Doctrine\ORM\Cache\Region\DefaultRegion;
use Doctrine\ORM\Cache\Region\FileLockRegion;
use Doctrine\ORM\Cache\Region\UpdateTimestampCache;
@@ -24,15 +23,19 @@ use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use InvalidArgumentException;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use TypeError;
use function assert;
use function get_debug_type;
use function sprintf;
use const DIRECTORY_SEPARATOR;
class DefaultCacheFactory implements CacheFactory
{
/** @var CacheAdapter */
private $cache;
/** @var CacheItemPoolInterface */
private $cacheItemPool;
/** @var RegionsConfiguration */
private $regionsConfig;
@@ -46,9 +49,33 @@ class DefaultCacheFactory implements CacheFactory
/** @var string|null */
private $fileLockRegionDirectory;
public function __construct(RegionsConfiguration $cacheConfig, CacheAdapter $cache)
/**
* @param CacheItemPoolInterface $cacheItemPool
*/
public function __construct(RegionsConfiguration $cacheConfig, $cacheItemPool)
{
$this->cache = $cache;
if ($cacheItemPool instanceof LegacyCache) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9322',
'Passing an instance of %s to %s is deprecated, pass a %s instead.',
get_debug_type($cacheItemPool),
__METHOD__,
CacheItemPoolInterface::class
);
$this->cacheItemPool = CacheAdapter::wrap($cacheItemPool);
} elseif (! $cacheItemPool instanceof CacheItemPoolInterface) {
throw new TypeError(sprintf(
'%s: Parameter #2 is expected to be an instance of %s, got %s.',
__METHOD__,
CacheItemPoolInterface::class,
get_debug_type($cacheItemPool)
));
} else {
$this->cacheItemPool = $cacheItemPool;
}
$this->regionsConfig = $cacheConfig;
}
@@ -91,6 +118,7 @@ class DefaultCacheFactory implements CacheFactory
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata)
{
assert($metadata->cache !== null);
$region = $this->getRegion($metadata->cache);
$usage = $metadata->cache['usage'];
@@ -181,13 +209,9 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']];
}
$name = $cache['region'];
$cacheAdapter = $this->createRegionCache($name);
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
$region = $cacheAdapter instanceof MultiGetCache
? new DefaultMultiGetRegion($name, $cacheAdapter, $lifetime)
: new DefaultRegion($name, $cacheAdapter, $lifetime);
$name = $cache['region'];
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
$region = new DefaultRegion($name, $this->cacheItemPool, $lifetime);
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
if (
@@ -207,25 +231,6 @@ class DefaultCacheFactory implements CacheFactory
return $this->regions[$cache['region']] = $region;
}
private function createRegionCache(string $name): CacheAdapter
{
$cacheAdapter = clone $this->cache;
if (! $cacheAdapter instanceof CacheProvider) {
return $cacheAdapter;
}
$namespace = $cacheAdapter->getNamespace();
if ($namespace !== '') {
$namespace .= ':';
}
$cacheAdapter->setNamespace($namespace . $name);
return $cacheAdapter;
}
/**
* {@inheritdoc}
*/
@@ -235,7 +240,7 @@ class DefaultCacheFactory implements CacheFactory
$name = Cache::DEFAULT_TIMESTAMP_REGION_NAME;
$lifetime = $this->regionsConfig->getLifetime($name);
$this->timestampRegion = new UpdateTimestampCache($name, clone $this->cache, $lifetime);
$this->timestampRegion = new UpdateTimestampCache($name, $this->cacheItemPool, $lifetime);
}
return $this->timestampRegion;
@@ -244,8 +249,8 @@ class DefaultCacheFactory implements CacheFactory
/**
* {@inheritdoc}
*/
public function createCache(EntityManagerInterface $em)
public function createCache(EntityManagerInterface $entityManager)
{
return new DefaultCache($em);
return new DefaultCache($entityManager);
}
}

View File

@@ -11,7 +11,6 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use function array_walk;
use function assert;
/**

View File

@@ -55,8 +55,16 @@ class DefaultEntityHydrator implements EntityHydrator
$data = $this->uow->getOriginalEntityData($entity);
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
if ($metadata->isVersioned) {
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
if ($metadata->requiresFetchAfterChange) {
if ($metadata->isVersioned) {
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
}
foreach ($metadata->fieldMappings as $name => $fieldMapping) {
if (isset($fieldMapping['generated'])) {
$data[$name] = $metadata->getFieldValue($entity, $name);
}
}
}
foreach ($metadata->associationMappings as $name => $assoc) {

View File

@@ -18,7 +18,6 @@ use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use function array_key_exists;
use function array_map;
use function array_shift;
use function array_unshift;
@@ -45,7 +44,7 @@ class DefaultQueryCache implements QueryCache
/** @var QueryCacheValidator */
private $validator;
/** @var CacheLogger */
/** @var CacheLogger|null */
protected $cacheLogger;
/** @var array<string,mixed> */

View File

@@ -14,15 +14,13 @@ use function array_map;
class EntityCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var array<string,mixed> The entity map data
*/
public $data;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The entity class name
* @psalm-var class-string
*/

View File

@@ -15,15 +15,13 @@ use function strtolower;
class EntityCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var array<string, mixed> The entity identifier
*/
public $identifier;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var string The entity class name
*/
public $entityClass;

View File

@@ -25,6 +25,8 @@ interface EntityHydrator
* @param EntityCacheKey $key The entity cache key.
* @param EntityCacheEntry $entry The entity cache entry.
* @param object $entity The entity to load the cache into. If not specified, a new entity is created.
*
* @return object|null
*/
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null);
}

View File

@@ -6,8 +6,6 @@ namespace Doctrine\ORM\Cache\Exception;
use Doctrine\ORM\Cache\CacheException as BaseCacheException;
use function sprintf;
/**
* Exception for cache.
*/

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
use function sprintf;
class CannotUpdateReadOnlyCollection extends CacheException

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
use function sprintf;
class CannotUpdateReadOnlyEntity extends CacheException

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
class FeatureNotImplemented extends CacheException
{
public static function scalarResults(): self

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
final class MetadataCacheNotConfigured extends CacheException
{
public static function create(): self

View File

@@ -5,16 +5,15 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use Doctrine\Common\Cache\Cache;
use LogicException;
use function get_class;
use function get_debug_type;
final class MetadataCacheUsesNonPersistentCache extends CacheException
{
public static function fromDriver(Cache $cache): self
{
return new self(
'Metadata Cache uses a non-persistent cache driver, ' . get_class($cache) . '.'
'Metadata Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.'
);
}
}

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
use function sprintf;
class NonCacheableEntity extends CacheException

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
final class QueryCacheNotConfigured extends CacheException
{
public static function create(): self

View File

@@ -5,16 +5,15 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use Doctrine\Common\Cache\Cache;
use LogicException;
use function get_class;
use function get_debug_type;
final class QueryCacheUsesNonPersistentCache extends CacheException
{
public static function fromDriver(Cache $cache): self
{
return new self(
'Query Cache uses a non-persistent cache driver, ' . get_class($cache) . '.'
'Query Cache uses a non-persistent cache driver, ' . get_debug_type($cache) . '.'
);
}
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\ORM\Cache\CacheException;
use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyCollection;
use Doctrine\ORM\PersistentCollection;

View File

@@ -12,15 +12,13 @@ use function microtime;
class QueryCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var array<string, mixed> List of entity identifiers
*/
public $result;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var float Time creation of this cache entry
*/
public $time;

View File

@@ -12,26 +12,29 @@ use Doctrine\ORM\Cache;
class QueryCacheKey extends CacheKey
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var int Cache key lifetime
*/
public $lifetime;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
* Cache mode
*
* @var int Cache mode (Doctrine\ORM\Cache::MODE_*)
* @readonly Public only for performance reasons, it should be considered immutable.
* @var int
* @psalm-var Cache::MODE_*
*/
public $cacheMode;
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var TimestampCacheKey|null
*/
public $timestampKey;
/**
* @psalm-param Cache::MODE_* $cacheMode
*/
public function __construct(
string $cacheId,
int $lifetime = 0,

View File

@@ -45,6 +45,8 @@ interface Region extends MultiGetRegion
* @param CacheEntry $entry The entry to cache.
* @param Lock|null $lock The lock previously obtained.
*
* @return bool
*
* @throws CacheException Indicates a problem accessing the region.
*/
public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null);
@@ -54,6 +56,8 @@ interface Region extends MultiGetRegion
*
* @param CacheKey $key The key under which to cache the item.
*
* @return bool
*
* @throws CacheException Indicates a problem accessing the region.
*/
public function evict(CacheKey $key);
@@ -61,6 +65,8 @@ interface Region extends MultiGetRegion
/**
* Remove all contents of this particular cache region.
*
* @return bool
*
* @throws CacheException Indicates problem accessing the region.
*/
public function evictAll();

View File

@@ -4,64 +4,11 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Region;
use Doctrine\Common\Cache\Cache;
use Doctrine\Common\Cache\MultiGetCache;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\CollectionCacheEntry;
use function assert;
use function count;
/**
* A cache region that enables the retrieval of multiple elements with one call
*
* @deprecated Use {@link DefaultRegion} instead.
*/
class DefaultMultiGetRegion extends DefaultRegion
{
/**
* Note that the multiple type is due to doctrine/cache not integrating the MultiGetCache interface
* in its signature due to BC in 1.x
*
* @var MultiGetCache|Cache
*/
protected $cache;
/**
* {@inheritDoc}
*
* @param MultiGetCache $cache
*/
public function __construct($name, MultiGetCache $cache, $lifetime = 0)
{
assert($cache instanceof Cache);
parent::__construct($name, $cache, $lifetime);
}
/**
* {@inheritdoc}
*/
public function getMultiple(CollectionCacheEntry $collection)
{
$keysToRetrieve = [];
foreach ($collection->identifiers as $index => $key) {
$keysToRetrieve[$index] = $this->getCacheEntryKey($key);
}
$items = $this->cache->fetchMultiple($keysToRetrieve);
if (count($items) !== count($keysToRetrieve)) {
return null;
}
$returnableItems = [];
foreach ($keysToRetrieve as $index => $key) {
if (! $items[$key] instanceof CacheEntry) {
return null;
}
$returnableItems[$index] = $items[$key];
}
return $returnableItems;
}
}

View File

@@ -4,44 +4,94 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Region;
use BadMethodCallException;
use Doctrine\Common\Cache\Cache as CacheAdapter;
use Closure;
use Doctrine\Common\Cache\Cache as LegacyCache;
use Doctrine\Common\Cache\CacheProvider;
use Doctrine\Common\Cache\ClearableCache;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CacheEntry;
use Doctrine\ORM\Cache\CacheKey;
use Doctrine\ORM\Cache\CollectionCacheEntry;
use Doctrine\ORM\Cache\Lock;
use Doctrine\ORM\Cache\Region;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Traversable;
use TypeError;
use function get_class;
use function array_map;
use function get_debug_type;
use function iterator_to_array;
use function sprintf;
use function strtr;
/**
* The simplest cache region compatible with all doctrine-cache drivers.
*/
class DefaultRegion implements Region
{
/**
* @internal since 2.11, this constant will be private in 3.0.
*/
public const REGION_KEY_SEPARATOR = '_';
/** @var CacheAdapter */
protected $cache;
/** @var string */
protected $name;
/** @var int */
protected $lifetime = 0;
private const REGION_PREFIX = 'DC2_REGION_';
/**
* @param string $name
* @param int $lifetime
* @deprecated since 2.11, this property will be removed in 3.0.
*
* @var LegacyCache
*/
public function __construct($name, CacheAdapter $cache, $lifetime = 0)
protected $cache;
/**
* @internal since 2.11, this property will be private in 3.0.
*
* @var string
*/
protected $name;
/**
* @internal since 2.11, this property will be private in 3.0.
*
* @var int
*/
protected $lifetime = 0;
/** @var CacheItemPoolInterface */
private $cacheItemPool;
/**
* @param CacheItemPoolInterface $cacheItemPool
*/
public function __construct(string $name, $cacheItemPool, int $lifetime = 0)
{
$this->cache = $cache;
$this->name = (string) $name;
$this->lifetime = (int) $lifetime;
if ($cacheItemPool instanceof LegacyCache) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9322',
'Passing an instance of %s to %s is deprecated, pass a %s instead.',
get_debug_type($cacheItemPool),
__METHOD__,
CacheItemPoolInterface::class
);
$this->cache = $cacheItemPool;
$this->cacheItemPool = CacheAdapter::wrap($cacheItemPool);
} elseif (! $cacheItemPool instanceof CacheItemPoolInterface) {
throw new TypeError(sprintf(
'%s: Parameter #2 is expected to be an instance of %s, got %s.',
__METHOD__,
CacheItemPoolInterface::class,
get_debug_type($cacheItemPool)
));
} else {
$this->cache = DoctrineProvider::wrap($cacheItemPool);
$this->cacheItemPool = $cacheItemPool;
}
$this->name = $name;
$this->lifetime = $lifetime;
}
/**
@@ -53,6 +103,8 @@ class DefaultRegion implements Region
}
/**
* @deprecated
*
* @return CacheProvider
*/
public function getCache()
@@ -65,7 +117,7 @@ class DefaultRegion implements Region
*/
public function contains(CacheKey $key)
{
return $this->cache->contains($this->getCacheEntryKey($key));
return $this->cacheItemPool->hasItem($this->getCacheEntryKey($key));
}
/**
@@ -73,7 +125,8 @@ class DefaultRegion implements Region
*/
public function get(CacheKey $key)
{
$entry = $this->cache->fetch($this->getCacheEntryKey($key));
$item = $this->cacheItemPool->getItem($this->getCacheEntryKey($key));
$entry = $item->isHit() ? $item->get() : null;
if (! $entry instanceof CacheEntry) {
return null;
@@ -87,30 +140,33 @@ class DefaultRegion implements Region
*/
public function getMultiple(CollectionCacheEntry $collection)
{
$keys = array_map(
Closure::fromCallable([$this, 'getCacheEntryKey']),
$collection->identifiers
);
/** @var iterable<string, CacheItemInterface> $items */
$items = $this->cacheItemPool->getItems($keys);
if ($items instanceof Traversable) {
$items = iterator_to_array($items);
}
$result = [];
foreach ($collection->identifiers as $key) {
$entryKey = $this->getCacheEntryKey($key);
$entryValue = $this->cache->fetch($entryKey);
if (! $entryValue instanceof CacheEntry) {
foreach ($keys as $arrayKey => $cacheKey) {
if (! isset($items[$cacheKey]) || ! $items[$cacheKey]->isHit()) {
return null;
}
$result[] = $entryValue;
$entry = $items[$cacheKey]->get();
if (! $entry instanceof CacheEntry) {
return null;
}
$result[$arrayKey] = $entry;
}
return $result;
}
/**
* @return string
*/
protected function getCacheEntryKey(CacheKey $key)
{
return $this->name . self::REGION_KEY_SEPARATOR . $key->hash;
}
/**
* {@inheritdoc}
*
@@ -118,7 +174,15 @@ class DefaultRegion implements Region
*/
public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null)
{
return $this->cache->save($this->getCacheEntryKey($key), $entry, $this->lifetime);
$item = $this->cacheItemPool
->getItem($this->getCacheEntryKey($key))
->set($entry);
if ($this->lifetime > 0) {
$item->expiresAfter($this->lifetime);
}
return $this->cacheItemPool->save($item);
}
/**
@@ -128,7 +192,7 @@ class DefaultRegion implements Region
*/
public function evict(CacheKey $key)
{
return $this->cache->delete($this->getCacheEntryKey($key));
return $this->cacheItemPool->deleteItem($this->getCacheEntryKey($key));
}
/**
@@ -138,13 +202,16 @@ class DefaultRegion implements Region
*/
public function evictAll()
{
if (! $this->cache instanceof ClearableCache) {
throw new BadMethodCallException(sprintf(
'Clearing all cache entries is not supported by the supplied cache adapter of type %s',
get_class($this->cache)
));
}
return $this->cacheItemPool->clear(self::REGION_PREFIX . $this->name);
}
return $this->cache->deleteAll();
/**
* @internal since 2.11, this method will be private in 3.0.
*
* @return string
*/
protected function getCacheEntryKey(CacheKey $key)
{
return self::REGION_PREFIX . $this->name . self::REGION_KEY_SEPARATOR . strtr($key->hash, '{}()/\@:', '________');
}
}

View File

@@ -228,8 +228,6 @@ class FileLockRegion implements ConcurrentRegion
/**
* {@inheritdoc}
*
* @return bool
*/
public function unlock(CacheKey $key, Lock $lock)
{

View File

@@ -12,8 +12,7 @@ use function microtime;
class TimestampCacheEntry implements CacheEntry
{
/**
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @readonly Public only for performance reasons, it should be considered immutable.
* @var float
*/
public $time;

View File

@@ -25,6 +25,7 @@ use Doctrine\ORM\Exception\NamedNativeQueryNotFound;
use Doctrine\ORM\Exception\NamedQueryNotFound;
use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating;
use Doctrine\ORM\Exception\UnknownEntityNamespace;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
@@ -256,7 +257,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getResultCache(): ?CacheItemPoolInterface
{
// Compatibility with DBAL < 3.2
// Compatibility with DBAL 2
if (! method_exists(parent::class, 'getResultCache')) {
$cacheImpl = $this->getResultCacheImpl();
@@ -271,7 +272,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setResultCache(CacheItemPoolInterface $cache): void
{
// Compatibility with DBAL < 3.2
// Compatibility with DBAL 2
if (! method_exists(parent::class, 'setResultCache')) {
$this->setResultCacheImpl(DoctrineProvider::wrap($cache));
@@ -510,6 +511,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Ensures that this Configuration instance contains settings that are
* suitable for a production environment.
*
* @deprecated
*
* @return void
*
* @throws ProxyClassesAlwaysRegenerating
@@ -518,6 +521,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function ensureProductionSettings()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9074',
'%s is deprecated',
__METHOD__
);
$queryCacheImpl = $this->getQueryCacheImpl();
if (! $queryCacheImpl) {
@@ -703,7 +713,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the custom hydrator modes in one pass.
*
* @param array<string, class-string> $modes An array of ($modeName => $hydrator).
* @param array<string, class-string<AbstractHydrator>> $modes An array of ($modeName => $hydrator).
*
* @return void
*/
@@ -722,7 +732,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $modeName The hydration mode name.
*
* @return string|null The hydrator class name.
* @psalm-return ?class-string
* @psalm-return class-string<AbstractHydrator>|null
*/
public function getCustomHydrationMode($modeName)
{
@@ -734,7 +744,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $modeName The hydration mode name.
* @param string $hydrator The hydrator class name.
* @psalm-param class-string $hydrator
* @psalm-param class-string<AbstractHydrator> $hydrator
*
* @return void
*/
@@ -1003,4 +1013,24 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$this->_attributes['defaultQueryHints'][$name] = $value;
}
/**
* Gets a list of entity class names to be ignored by the SchemaTool
*
* @return list<class-string>
*/
public function getSchemaIgnoreClasses(): array
{
return $this->_attributes['schemaIgnoreClasses'] ?? [];
}
/**
* Sets a list of entity class names to be ignored by the SchemaTool
*
* @param list<class-string> $schemaIgnoreClasses List of entity class names
*/
public function setSchemaIgnoreClasses(array $schemaIgnoreClasses): void
{
$this->_attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
}
}

View File

@@ -8,7 +8,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ObjectManagerDecorator;
use function get_class;
use function get_debug_type;
use function method_exists;
use function sprintf;
use function trigger_error;
@@ -17,12 +17,11 @@ use const E_USER_NOTICE;
/**
* Base class for EntityManager decorators
*
* @extends ObjectManagerDecorator<EntityManagerInterface>
*/
abstract class EntityManagerDecorator extends ObjectManagerDecorator implements EntityManagerInterface
{
/** @var EntityManagerInterface */
protected $wrapped;
public function __construct(EntityManagerInterface $wrapped)
{
$this->wrapped = $wrapped;
@@ -67,7 +66,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
{
if (! method_exists($this->wrapped, 'wrapInTransaction')) {
trigger_error(
sprintf('Calling `transactional()` instead of `wrapInTransaction()` which is not implemented on %s', get_class($this->wrapped)),
sprintf('Calling `transactional()` instead of `wrapInTransaction()` which is not implemented on %s', get_debug_type($this->wrapped)),
E_USER_NOTICE
);

View File

@@ -32,7 +32,7 @@ use Throwable;
use function array_keys;
use function call_user_func;
use function get_class;
use function get_debug_type;
use function gettype;
use function is_array;
use function is_callable;
@@ -120,7 +120,7 @@ use function sprintf;
/**
* The expression builder instance used to generate query expressions.
*
* @var Expr
* @var Expr|null
*/
private $expressionBuilder;
@@ -134,11 +134,15 @@ use function sprintf;
/**
* Collection of query filters.
*
* @var FilterCollection
* @var FilterCollection|null
*/
private $filterCollection;
/** @var Cache The second level cache regions API. */
/**
* The second level cache regions API.
*
* @var Cache|null
*/
private $cache;
/**
@@ -399,6 +403,7 @@ use function sprintf;
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @psalm-param class-string<T> $className
* @psalm-param LockMode::*|null $lockMode
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @psalm-return ?T
@@ -944,7 +949,7 @@ use function sprintf;
throw new InvalidArgumentException(
sprintf(
'Invalid $connection argument of type %s given%s.',
is_object($connection) ? get_class($connection) : gettype($connection),
get_debug_type($connection),
is_object($connection) ? '' : ': "' . $connection . '"'
)
);
@@ -986,6 +991,8 @@ use function sprintf;
}
/**
* @psalm-param LockMode::* $lockMode
*
* @throws OptimisticLockException
* @throws TransactionRequiredException
*/

View File

@@ -8,6 +8,7 @@ use BadMethodCallException;
use DateTimeInterface;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\Expr;
@@ -237,6 +238,7 @@ interface EntityManagerInterface extends ObjectManager
* @param object $entity
* @param int $lockMode
* @param int|DateTimeInterface|null $lockVersion
* @psalm-param LockMode::* $lockMode
*
* @return void
*
@@ -282,6 +284,7 @@ interface EntityManagerInterface extends ObjectManager
* @deprecated
*
* @param string|int $hydrationMode
* @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode
*
* @return AbstractHydrator
*/
@@ -291,6 +294,7 @@ interface EntityManagerInterface extends ObjectManager
* Create a new instance for the given hydration mode.
*
* @param string|int $hydrationMode
* @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode
*
* @return AbstractHydrator
*

View File

@@ -37,8 +37,6 @@ class EntityNotFoundException extends ORMException
/**
* Instance for which no identifier can be found
*
* @psalm-param class-string $className
*/
public static function noIdentifierFound(string $className): self
{

View File

@@ -8,13 +8,13 @@ use BadMethodCallException;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Doctrine\DBAL\LockMode;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\ORM\Repository\Exception\InvalidMagicMethodCall;
use Doctrine\ORM\Repository\InvalidFindByCall;
use Doctrine\Persistence\ObjectRepository;
use function array_slice;
@@ -39,7 +39,7 @@ class EntityRepository implements ObjectRepository, Selectable
/** @var string */
protected $_entityName;
/** @var EntityManager */
/** @var EntityManagerInterface */
protected $_em;
/** @var ClassMetadata */
@@ -165,6 +165,7 @@ class EntityRepository implements ObjectRepository, Selectable
* or NULL if no specific lock mode should be used
* during the search.
* @param int|null $lockVersion The lock version.
* @psalm-param LockMode::*|null $lockMode
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @psalm-return ?T
@@ -281,7 +282,7 @@ class EntityRepository implements ObjectRepository, Selectable
}
/**
* @return EntityManager
* @return EntityManagerInterface
*/
protected function getEntityManager()
{

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
/**
@@ -28,7 +28,7 @@ class LifecycleEventArgs extends BaseLifecycleEventArgs
/**
* Retrieves associated EntityManager.
*
* @return EntityManager
* @return EntityManagerInterface
*/
public function getEntityManager()
{

View File

@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace Doctrine\ORM\Event;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClassMetadataEventArgs;
/**
* Class that holds event arguments for a loadMetadata event.
*
* @method __construct(ClassMetadata $classMetadata, EntityManager $objectManager)
* @method __construct(ClassMetadata $classMetadata, EntityManagerInterface $objectManager)
* @method ClassMetadata getClassMetadata()
*/
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
@@ -19,7 +19,7 @@ class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
/**
* Retrieve associated EntityManager.
*
* @return EntityManager
* @return EntityManagerInterface
*/
public function getEntityManager()
{

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
@@ -15,7 +14,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/
class PostFlushEventArgs extends EventArgs
{
/** @var EntityManager */
/** @var EntityManagerInterface */
private $em;
public function __construct(EntityManagerInterface $em)
@@ -26,7 +25,7 @@ class PostFlushEventArgs extends EventArgs
/**
* Retrieves associated EntityManager.
*
* @return EntityManager
* @return EntityManagerInterface
*/
public function getEntityManager()
{

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
@@ -15,7 +14,7 @@ use Doctrine\ORM\EntityManagerInterface;
*/
class PreFlushEventArgs extends EventArgs
{
/** @var EntityManager */
/** @var EntityManagerInterface */
private $em;
public function __construct(EntityManagerInterface $em)
@@ -24,7 +23,7 @@ class PreFlushEventArgs extends EventArgs
}
/**
* @return EntityManager
* @return EntityManagerInterface
*/
public function getEntityManager()
{

View File

@@ -8,7 +8,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\PersistentCollection;
use InvalidArgumentException;
use function get_class;
use function get_debug_type;
use function sprintf;
/**
@@ -108,7 +108,7 @@ class PreUpdateEventArgs extends LifecycleEventArgs
throw new InvalidArgumentException(sprintf(
'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.',
$field,
get_class($this->getEntity())
get_debug_type($this->getEntity())
));
}
}

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use RuntimeException;
final class EntityManagerClosed extends ORMException implements ManagerException
{
public static function create(): self

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use function get_class;
use function get_debug_type;
final class EntityMissingAssignedId extends ORMException
{
@@ -13,7 +13,7 @@ final class EntityMissingAssignedId extends ORMException
*/
public static function forField($entity, string $field): self
{
return new self('Entity of type ' . get_class($entity) . " is missing an assigned ID for field '" . $field . "'. " .
return new self('Entity of type ' . get_debug_type($entity) . " is missing an assigned ID for field '" . $field . "'. " .
'The identifier generation strategy for this entity requires the ID field to be populated before ' .
'EntityManager#persist() is called. If you want automatically generated identifiers instead ' .
'you need to adjust the metadata mapping accordingly.');

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use Doctrine\Persistence\ObjectRepository;
use LogicException;
final class InvalidEntityRepository extends ORMException implements ConfigurationException
{

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
final class MissingIdentifierField extends ORMException implements ManagerException

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
final class MissingMappingDriverImplementation extends ORMException implements ManagerException
{
public static function create(): self

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
final class NamedQueryNotFound extends ORMException implements ConfigurationException

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
final class ProxyClassesAlwaysRegenerating extends ORMException implements ConfigurationException
{
public static function create(): self

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
final class UnknownEntityNamespace extends ORMException implements ConfigurationException

View File

@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function implode;
use function sprintf;

View File

@@ -4,10 +4,54 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use LogicException;
use function get_debug_type;
use function sprintf;
abstract class AbstractIdGenerator
{
/** @var bool */
private $alreadyDelegatedToGenerateId = false;
/**
* Generates an identifier for an entity.
*
* @deprecated Call {@see generateId()} instead.
*
* @param object|null $entity
*
* @return mixed
*/
public function generate(EntityManager $em, $entity)
{
if ($this->alreadyDelegatedToGenerateId) {
throw new LogicException(sprintf(
'Endless recursion detected in %s. Please implement generateId() without calling the parent implementation.',
get_debug_type($this)
));
}
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9325',
'%s::generate() is deprecated, call generateId() instead.',
get_debug_type($this)
);
$this->alreadyDelegatedToGenerateId = true;
try {
return $this->generateId($em, $entity);
} finally {
$this->alreadyDelegatedToGenerateId = false;
}
}
/**
* Generates an identifier for an entity.
*
@@ -15,11 +59,26 @@ abstract class AbstractIdGenerator
*
* @return mixed
*/
abstract public function generate(EntityManager $em, $entity);
public function generateId(EntityManagerInterface $em, $entity)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9325',
'Not implementing %s in %s is deprecated.',
__FUNCTION__,
get_debug_type($this)
);
if (! $em instanceof EntityManager) {
throw new InvalidArgumentException('Unsupported entity manager implementation.');
}
return $this->generate($em, $entity);
}
/**
* Gets whether this generator is a post-insert generator which means that
* {@link generate()} must be called after the entity has been inserted
* {@link generateId()} must be called after the entity has been inserted
* into the database.
*
* By default, this method returns FALSE. Generators that have this requirement

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\EntityMissingAssignedId;
use function get_class;
@@ -17,11 +17,11 @@ class AssignedGenerator extends AbstractIdGenerator
/**
* Returns the identifier assigned to the given entity.
*
* {@inheritDoc}
* {@inheritdoc}
*
* @throws EntityMissingAssignedId
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
$class = $em->getClassMetadata(get_class($entity));
$idFields = $class->getIdentifierFieldNames();

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
@@ -16,7 +16,7 @@ class BigIntegerIdentityGenerator extends AbstractIdGenerator
/**
* The name of the sequence to pass to lastInsertId(), if any.
*
* @var string
* @var string|null
*/
private $sequenceName;
@@ -33,7 +33,7 @@ class BigIntegerIdentityGenerator extends AbstractIdGenerator
/**
* {@inheritDoc}
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
return (string) $em->getConnection()->lastInsertId($this->sequenceName);
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
* Id generator that obtains IDs from special "identity" columns. These are columns
@@ -33,7 +33,7 @@ class IdentityGenerator extends AbstractIdGenerator
/**
* {@inheritDoc}
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
return (int) $em->getConnection()->lastInsertId($this->sequenceName);
}

View File

@@ -4,7 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\ORM\EntityManagerInterface;
use Serializable;
use function serialize;
@@ -50,15 +51,18 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
/**
* {@inheritDoc}
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue === $this->_maxValue) {
// Allocate new values
$conn = $em->getConnection();
$sql = $conn->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
$connection = $em->getConnection();
$sql = $connection->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
// Using `query` to force usage of the master server in MasterSlaveConnection
$this->_nextValue = (int) $conn->executeQuery($sql)->fetchOne();
if ($connection instanceof PrimaryReadReplicaConnection) {
$connection->ensureConnectedToPrimary();
}
$this->_nextValue = (int) $connection->executeQuery($sql)->fetchOne();
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
}

View File

@@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
* Id generator that uses a single-row database table and a hi/lo algorithm.
@@ -43,8 +43,8 @@ class TableGenerator extends AbstractIdGenerator
/**
* {@inheritDoc}
*/
public function generate(
EntityManager $em,
public function generateId(
EntityManagerInterface $em,
$entity
) {
if ($this->_maxValue === null || $this->_nextValue === $this->_maxValue) {

View File

@@ -4,9 +4,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\NotSupported;
use function method_exists;
@@ -37,11 +38,15 @@ class UuidGenerator extends AbstractIdGenerator
*
* @throws NotSupported
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
$conn = $em->getConnection();
$sql = 'SELECT ' . $conn->getDatabasePlatform()->getGuidExpression();
$connection = $em->getConnection();
$sql = 'SELECT ' . $connection->getDatabasePlatform()->getGuidExpression();
return $conn->executeQuery($sql)->fetchOne();
if ($connection instanceof PrimaryReadReplicaConnection) {
$connection->ensureConnectedToPrimary();
}
return $connection->executeQuery($sql)->fetchOne();
}
}

View File

@@ -228,7 +228,7 @@ abstract class AbstractHydrator
* Hydrates all rows returned by the passed statement instance at once.
*
* @param Result|ResultStatement $stmt
* @param object $resultSetMapping
* @param ResultSetMapping $resultSetMapping
* @psalm-param array<string, string> $hints
*
* @return mixed[]
@@ -277,10 +277,19 @@ abstract class AbstractHydrator
* Hydrates a single row returned by the current statement instance during
* row-by-row hydration with {@link iterate()} or {@link toIterable()}.
*
* @deprecated
*
* @return mixed[]|false
*/
public function hydrateRow()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9072',
'%s is deprecated.',
__METHOD__
);
$row = $this->statement()->fetchAssociative();
if ($row === false) {

View File

@@ -32,12 +32,12 @@ class SingleScalarHydrator extends AbstractHydrator
throw new NonUniqueResultException('The query returned multiple rows. Change the query or use a different result function like getScalarResult().');
}
if (count($data[key($data)]) > 1) {
$result = $this->gatherScalarRowData($data[key($data)]);
if (count($result) > 1) {
throw new NonUniqueResultException('The query returned a row containing multiple columns. Change the query or use a different result function like getScalarResult().');
}
$result = $this->gatherScalarRowData($data[key($data)]);
return array_shift($result);
}
}

View File

@@ -7,7 +7,6 @@ namespace Doctrine\ORM\Internal;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use function method_exists;
@@ -25,7 +24,7 @@ trait SQLResultCasing
return strtoupper($column);
}
if ($platform instanceof PostgreSQL94Platform || $platform instanceof PostgreSQLPlatform) {
if ($platform instanceof PostgreSQLPlatform) {
return strtolower($column);
}

View File

@@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use ReturnTypeWillChange;
/**
* A lazy collection that allows a fast count when using criteria object
@@ -38,6 +39,7 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
*
* @return int
*/
#[ReturnTypeWillChange]
public function count()
{
if ($this->isInitialized()) {

View File

@@ -8,6 +8,7 @@ namespace Doctrine\ORM\Mapping;
* This annotation is used to override association mapping of property for an entity relationship.
*
* @Annotation
* @NamedArgumentConstructor
* @Target("ANNOTATION")
*/
final class AssociationOverride implements Annotation
@@ -22,29 +23,64 @@ final class AssociationOverride implements Annotation
/**
* The join column that is being mapped to the persistent attribute.
*
* @var array<\Doctrine\ORM\Mapping\JoinColumn>
* @var array<\Doctrine\ORM\Mapping\JoinColumn>|null
*/
public $joinColumns;
/**
* The join column that is being mapped to the persistent attribute.
*
* @var array<\Doctrine\ORM\Mapping\JoinColumn>|null
*/
public $inverseJoinColumns;
/**
* The join table that maps the relationship.
*
* @var \Doctrine\ORM\Mapping\JoinTable
* @var \Doctrine\ORM\Mapping\JoinTable|null
*/
public $joinTable;
/**
* The name of the association-field on the inverse-side.
*
* @var string
* @var ?string
*/
public $inversedBy;
/**
* The fetching strategy to use for the association.
*
* @var string
* @var ?string
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
*/
public $fetch;
/**
* @param JoinColumn|array<JoinColumn> $joinColumns
* @param JoinColumn|array<JoinColumn> $inverseJoinColumns
*/
public function __construct(
string $name,
$joinColumns = null,
$inverseJoinColumns = null,
?JoinTable $joinTable = null,
?string $inversedBy = null,
?string $fetch = null
) {
if ($joinColumns instanceof JoinColumn) {
$joinColumns = [$joinColumns];
}
if ($inverseJoinColumns instanceof JoinColumn) {
$inverseJoinColumns = [$inverseJoinColumns];
}
$this->name = $name;
$this->joinColumns = $joinColumns;
$this->inverseJoinColumns = $inverseJoinColumns;
$this->joinTable = $joinTable;
$this->inversedBy = $inversedBy;
$this->fetch = $fetch;
}
}

View File

@@ -4,18 +4,42 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Attribute;
use function is_array;
/**
* This annotation is used to override association mappings of relationship properties.
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class AssociationOverrides implements Annotation
{
/**
* Mapping overrides of relationship properties.
*
* @var array<\Doctrine\ORM\Mapping\AssociationOverride>
* @var array<AssociationOverride>
*/
public $value;
public $overrides = [];
/**
* @param array<AssociationOverride>|AssociationOverride $overrides
*/
public function __construct($overrides)
{
if (! is_array($overrides)) {
$overrides = [$overrides];
}
foreach ($overrides as $override) {
if (! ($override instanceof AssociationOverride)) {
throw MappingException::invalidOverrideType('AssociationOverride', $override);
}
$this->overrides[] = $override;
}
}
}

View File

@@ -4,15 +4,13 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Attribute;
/**
* This annotation is used to override the mapping of a entity property.
*
* @Annotation
* @NamedArgumentConstructor
* @Target("ANNOTATION")
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class AttributeOverride implements Annotation
{
/**
@@ -28,4 +26,10 @@ final class AttributeOverride implements Annotation
* @var \Doctrine\ORM\Mapping\Column
*/
public $column;
public function __construct(string $name, Column $column)
{
$this->name = $name;
$this->column = $column;
}
}

View File

@@ -4,18 +4,42 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Attribute;
use function is_array;
/**
* This annotation is used to override the mapping of a entity property.
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class AttributeOverrides implements Annotation
{
/**
* One or more field or property mapping overrides.
*
* @var array<\Doctrine\ORM\Mapping\AttributeOverride>
* @var array<AttributeOverride>
*/
public $value;
public $overrides = [];
/**
* @param array<AttributeOverride>|AttributeOverride $overrides
*/
public function __construct($overrides)
{
if (! is_array($overrides)) {
$overrides = [$overrides];
}
foreach ($overrides as $override) {
if (! ($override instanceof AttributeOverride)) {
throw MappingException::invalidOverrideType('AttributeOverride', $override);
}
$this->overrides[] = $override;
}
}
}

View File

@@ -110,6 +110,34 @@ class FieldBuilder
return $this;
}
/**
* Sets insertable.
*
* @return $this
*/
public function insertable(bool $flag = true): self
{
if (! $flag) {
$this->mapping['notInsertable'] = true;
}
return $this;
}
/**
* Sets updatable.
*
* @return $this
*/
public function updatable(bool $flag = true): self
{
if (! $flag) {
$this->mapping['notUpdatable'] = true;
}
return $this;
}
/**
* Sets scale.
*

View File

@@ -20,7 +20,6 @@ use Doctrine\ORM\Id\SequenceGenerator;
use Doctrine\ORM\Id\UuidGenerator;
use Doctrine\ORM\Mapping\Exception\CannotGenerateIds;
use Doctrine\ORM\Mapping\Exception\InvalidCustomGenerator;
use Doctrine\ORM\Mapping\Exception\TableGeneratorNotImplementedYet;
use Doctrine\ORM\Mapping\Exception\UnknownGeneratorType;
use Doctrine\Persistence\Mapping\AbstractClassMetadataFactory;
use Doctrine\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
@@ -46,9 +45,7 @@ use function substr;
* metadata mapping information of a class which describes how a class should be mapped
* to a relational database.
*
* @method ClassMetadata[] getAllMetadata()
* @method ClassMetadata[] getLoadedMetadata()
* @method ClassMetadata getMetadataFor($className)
* @extends AbstractClassMetadataFactory<ClassMetadata>
*/
class ClassMetadataFactory extends AbstractClassMetadataFactory
{
@@ -91,14 +88,16 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
protected function onNotFoundMetadata($className)
{
if (! $this->evm->hasListeners(Events::onClassMetadataNotFound)) {
return;
return null;
}
$eventArgs = new OnClassMetadataNotFoundEventArgs($className, $this->em);
$this->evm->dispatchEvent(Events::onClassMetadataNotFound, $eventArgs);
$classMetadata = $eventArgs->getFoundMetadata();
assert($classMetadata instanceof ClassMetadata || $classMetadata === null);
return $eventArgs->getFoundMetadata();
return $classMetadata;
}
/**
@@ -643,11 +642,13 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
/**
* @psalm-return ClassMetadata::GENERATOR_TYPE_SEQUENCE|ClassMetadata::GENERATOR_TYPE_IDENTITY
*/
private function determineIdGeneratorStrategy(AbstractPlatform $platform): int
{
if (
$platform instanceof Platforms\OraclePlatform
|| $platform instanceof Platforms\PostgreSQL94Platform
|| $platform instanceof Platforms\PostgreSQLPlatform
) {
return ClassMetadata::GENERATOR_TYPE_SEQUENCE;
@@ -667,7 +668,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private function truncateSequenceName(string $schemaElementName): string
{
$platform = $this->getTargetPlatform();
if (! in_array($platform->getName(), ['oracle', 'sqlanywhere'], true)) {
if (! $platform instanceof Platforms\OraclePlatform && ! $platform instanceof Platforms\SQLAnywherePlatform) {
return $schemaElementName;
}
@@ -738,7 +739,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
*/
protected function isEntity(ClassMetadataInterface $class)
{
return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
return ! $class->isMappedSuperclass;
}
private function getTargetPlatform(): Platforms\AbstractPlatform

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use BackedEnum;
use BadMethodCallException;
use DateInterval;
use DateTime;
@@ -14,14 +15,15 @@ use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Instantiator\Instantiator;
use Doctrine\Instantiator\InstantiatorInterface;
use Doctrine\ORM\Cache\Exception\CacheException;
use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\ReflectionService;
use InvalidArgumentException;
use LogicException;
use ReflectionClass;
use ReflectionEnum;
use ReflectionNamedType;
use ReflectionProperty;
use RuntimeException;
@@ -37,6 +39,7 @@ use function array_values;
use function assert;
use function class_exists;
use function count;
use function enum_exists;
use function explode;
use function gettype;
use function in_array;
@@ -73,10 +76,14 @@ use const PHP_VERSION_ID;
* @psalm-type FieldMapping = array{
* type: string,
* fieldName: string,
* columnName?: string,
* columnName: string,
* length?: int,
* id?: bool,
* nullable?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* generated?: string,
* enumType?: class-string<BackedEnum>,
* columnDefinition?: string,
* precision?: int,
* scale?: int,
@@ -254,6 +261,21 @@ class ClassMetadataInfo implements ClassMetadata
*/
public const CACHE_USAGE_READ_WRITE = 3;
/**
* The value of this column is never generated by the database.
*/
public const GENERATED_NEVER = 0;
/**
* The value of this column is generated by the database on INSERT, but not on UPDATE.
*/
public const GENERATED_INSERT = 1;
/**
* The value of this column is generated by the database on both INSERT and UDPATE statements.
*/
public const GENERATED_ALWAYS = 2;
/**
* READ-ONLY: The name of the entity class.
*
@@ -301,7 +323,7 @@ class ClassMetadataInfo implements ClassMetadata
* (Optional).
*
* @var string|null
* @psalm-var ?class-string
* @psalm-var ?class-string<EntityRepository>
*/
public $customRepositoryClassName;
@@ -396,7 +418,7 @@ class ClassMetadataInfo implements ClassMetadata
* READ-ONLY: The inheritance mapping type used by the class.
*
* @var int
* @psalm-var self::$INHERITANCE_TYPE_*
* @psalm-var self::INHERITANCE_TYPE_*
*/
public $inheritanceType = self::INHERITANCE_TYPE_NONE;
@@ -404,6 +426,7 @@ class ClassMetadataInfo implements ClassMetadata
* READ-ONLY: The Id generator type used by the class.
*
* @var int
* @psalm-var self::GENERATOR_TYPE_*
*/
public $generatorType = self::GENERATOR_TYPE_NONE;
@@ -434,6 +457,12 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>nullable</b> (boolean, optional)
* Whether the column is nullable. Defaults to FALSE.
*
* - <b>'notInsertable'</b> (boolean, optional)
* Whether the column is not insertable. Optional. Is only set if value is TRUE.
*
* - <b>'notUpdatable'</b> (boolean, optional)
* Whether the column is updatable. Optional. Is only set if value is TRUE.
*
* - <b>columnDefinition</b> (string, optional, schema-only)
* The SQL fragment that is used when generating the DDL for the column.
*
@@ -654,13 +683,21 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
/**
* READ-ONLY: A Flag indicating whether one or more columns of this class
* have to be reloaded after insert / update operations.
*
* @var bool
*/
public $requiresFetchAfterChange = false;
/**
* READ-ONLY: A flag for whether or not instances of this class are to be versioned
* with optimistic locking.
*
* @var bool
*/
public $isVersioned;
public $isVersioned = false;
/**
* READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
@@ -669,8 +706,8 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $versionField;
/** @var mixed[] */
public $cache = null;
/** @var mixed[]|null */
public $cache;
/**
* The ReflectionClass instance of the mapped class.
@@ -700,7 +737,7 @@ class ClassMetadataInfo implements ClassMetadata
/**
* The ReflectionProperty instances of the mapped class.
*
* @var ReflectionProperty[]|null[]
* @var array<string, ReflectionProperty|null>
*/
public $reflFields = [];
@@ -958,6 +995,10 @@ class ClassMetadataInfo implements ClassMetadata
$serialized[] = 'cache';
}
if ($this->requiresFetchAfterChange) {
$serialized[] = 'requiresFetchAfterChange';
}
return $serialized;
}
@@ -988,7 +1029,8 @@ class ClassMetadataInfo implements ClassMetadata
foreach ($this->embeddedClasses as $property => $embeddedClass) {
if (isset($embeddedClass['declaredField'])) {
$childProperty = $reflService->getAccessibleProperty(
$childProperty = $this->getAccessibleProperty(
$reflService,
$this->embeddedClasses[$embeddedClass['declaredField']]['class'],
$embeddedClass['originalField']
);
@@ -1002,7 +1044,8 @@ class ClassMetadataInfo implements ClassMetadata
continue;
}
$fieldRefl = $reflService->getAccessibleProperty(
$fieldRefl = $this->getAccessibleProperty(
$reflService,
$embeddedClass['declared'] ?? $this->name,
$property
);
@@ -1015,21 +1058,28 @@ class ClassMetadataInfo implements ClassMetadata
if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
$this->reflFields[$field] = new ReflectionEmbeddedProperty(
$parentReflFields[$mapping['declaredField']],
$reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
$this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField']),
$mapping['originalClass']
);
continue;
}
$this->reflFields[$field] = isset($mapping['declared'])
? $reflService->getAccessibleProperty($mapping['declared'], $field)
: $reflService->getAccessibleProperty($this->name, $field);
? $this->getAccessibleProperty($reflService, $mapping['declared'], $field)
: $this->getAccessibleProperty($reflService, $this->name, $field);
if (isset($mapping['enumType']) && $this->reflFields[$field] !== null) {
$this->reflFields[$field] = new ReflectionEnumProperty(
$this->reflFields[$field],
$mapping['enumType']
);
}
}
foreach ($this->associationMappings as $field => $mapping) {
$this->reflFields[$field] = isset($mapping['declared'])
? $reflService->getAccessibleProperty($mapping['declared'], $field)
: $reflService->getAccessibleProperty($this->name, $field);
? $this->getAccessibleProperty($reflService, $mapping['declared'], $field)
: $this->getAccessibleProperty($reflService, $this->name, $field);
}
}
@@ -1467,6 +1517,15 @@ class ClassMetadataInfo implements ClassMetadata
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName(), false)) {
$mapping['enumType'] = $type->getName();
$reflection = new ReflectionEnum($type->getName());
$type = $reflection->getBackingType();
assert($type instanceof ReflectionNamedType);
}
switch ($type->getName()) {
case DateInterval::class:
$mapping['type'] = Types::DATEINTERVAL;
@@ -1588,6 +1647,26 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['requireSQLConversion'] = true;
}
if (isset($mapping['generated'])) {
if (! in_array($mapping['generated'], [self::GENERATED_NEVER, self::GENERATED_INSERT, self::GENERATED_ALWAYS])) {
throw MappingException::invalidGeneratedMode($mapping['generated']);
}
if ($mapping['generated'] === self::GENERATED_NEVER) {
unset($mapping['generated']);
}
}
if (isset($mapping['enumType'])) {
if (PHP_VERSION_ID < 80100) {
throw MappingException::enumsRequirePhp81($this->name, $mapping['fieldName']);
}
if (! enum_exists($mapping['enumType'])) {
throw MappingException::nonEnumTypeMapped($this->name, $mapping['fieldName'], $mapping['enumType']);
}
}
return $mapping;
}
@@ -2151,6 +2230,7 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the type of Id generator to use for the mapped class.
*
* @param int $generatorType
* @psalm-param self::GENERATOR_TYPE_* $generatorType
*
* @return void
*/
@@ -2377,6 +2457,7 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the inheritance type used by the class and its subclasses.
*
* @param int $type
* @psalm-param self::INHERITANCE_TYPE_* $type
*
* @return void
*
@@ -2640,6 +2721,10 @@ class ClassMetadataInfo implements ClassMetadata
$mapping = $this->validateAndCompleteFieldMapping($mapping);
$this->assertFieldNotMapped($mapping['fieldName']);
if (isset($mapping['generated'])) {
$this->requiresFetchAfterChange = true;
}
$this->fieldMappings[$mapping['fieldName']] = $mapping;
}
@@ -2922,8 +3007,8 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Registers a custom repository class for the entity class.
*
* @param string $repositoryClassName The class name of the custom mapper.
* @psalm-param class-string $repositoryClassName
* @param string|null $repositoryClassName The class name of the custom mapper.
* @psalm-param class-string<EntityRepository>|null $repositoryClassName
*
* @return void
*/
@@ -3370,8 +3455,9 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function setVersionMapping(array &$mapping)
{
$this->isVersioned = true;
$this->versionField = $mapping['fieldName'];
$this->isVersioned = true;
$this->versionField = $mapping['fieldName'];
$this->requiresFetchAfterChange = true;
if (! isset($mapping['default'])) {
if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) {
@@ -3394,6 +3480,10 @@ class ClassMetadataInfo implements ClassMetadata
public function setVersioned($bool)
{
$this->isVersioned = $bool;
if ($bool) {
$this->requiresFetchAfterChange = true;
}
}
/**
@@ -3590,9 +3680,10 @@ class ClassMetadataInfo implements ClassMetadata
/**
* @param string|null $className
* @psalm-param ?class-string $className
* @psalm-param string|class-string|null $className
*
* @return string|null null if the input value is null
* @psalm-return class-string|null
*/
public function fullyQualifiedClassName($className)
{
@@ -3600,7 +3691,7 @@ class ClassMetadataInfo implements ClassMetadata
return $className;
}
if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
if (strpos($className, '\\') === false && $this->namespace) {
return $this->namespace . '\\' . $className;
}
@@ -3745,4 +3836,17 @@ class ClassMetadataInfo implements ClassMetadata
throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
}
}
/**
* @psalm-param class-string $class
*/
private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ?ReflectionProperty
{
$reflectionProperty = $reflService->getAccessibleProperty($class, $field);
if ($reflectionProperty !== null && PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) {
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
}
return $reflectionProperty;
}
}

View File

@@ -44,6 +44,15 @@ final class Column implements Annotation
/** @var bool */
public $nullable = false;
/** @var bool */
public $insertable = true;
/** @var bool */
public $updatable = true;
/** @var class-string<\BackedEnum>|null */
public $enumType = null;
/** @var array<string,mixed> */
public $options = [];
@@ -51,7 +60,16 @@ final class Column implements Annotation
public $columnDefinition;
/**
* @param array<string,mixed> $options
* @var string|null
* @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null
* @Enum({"NEVER", "INSERT", "ALWAYS"})
*/
public $generated;
/**
* @param class-string<\BackedEnum>|null $enumType
* @param array<string,mixed> $options
* @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated
*/
public function __construct(
?string $name = null,
@@ -61,8 +79,12 @@ final class Column implements Annotation
?int $scale = null,
bool $unique = false,
bool $nullable = false,
bool $insertable = true,
bool $updatable = true,
?string $enumType = null,
array $options = [],
?string $columnDefinition = null
?string $columnDefinition = null,
?string $generated = null
) {
$this->name = $name;
$this->type = $type;
@@ -71,7 +93,11 @@ final class Column implements Annotation
$this->scale = $scale;
$this->unique = $unique;
$this->nullable = $nullable;
$this->insertable = $insertable;
$this->updatable = $updatable;
$this->enumType = $enumType;
$this->options = $options;
$this->columnDefinition = $columnDefinition;
$this->generated = $generated;
}
}

View File

@@ -15,10 +15,16 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
#[Attribute(Attribute::TARGET_CLASS)]
final class DiscriminatorMap implements Annotation
{
/** @var array<string> */
/**
* @var array<string, string>
* @psalm-var array<string, class-string>
*/
public $value;
/** @param array<string> $value */
/**
* @param array<string, string> $value
* @psalm-param array<string, class-string> $value
*/
public function __construct(array $value)
{
$this->value = $value;

View File

@@ -5,14 +5,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\Reader;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Annotation;
use Doctrine\ORM\Cache\Exception\CacheException;
use Doctrine\ORM\Events;
use Doctrine\ORM\Id\TableGenerator;
use Doctrine\ORM\Mapping;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
@@ -21,6 +17,7 @@ use ReflectionMethod;
use ReflectionProperty;
use UnexpectedValueException;
use function assert;
use function class_exists;
use function constant;
use function count;
@@ -48,29 +45,25 @@ class AnnotationDriver extends AbstractAnnotationDriver
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$class = $metadata->getReflectionClass();
if (! $class) {
assert($metadata instanceof Mapping\ClassMetadata);
$class = $metadata->getReflectionClass()
// this happens when running annotation driver in combination with
// static reflection services. This is not the nicest fix
$class = new ReflectionClass($metadata->name);
}
?? new ReflectionClass($metadata->name);
$classAnnotations = $this->reader->getClassAnnotations($class);
if ($classAnnotations) {
foreach ($classAnnotations as $key => $annot) {
if (! is_numeric($key)) {
continue;
}
$classAnnotations[get_class($annot)] = $annot;
foreach ($classAnnotations as $key => $annot) {
if (! is_numeric($key)) {
continue;
}
$classAnnotations[get_class($annot)] = $annot;
}
// Evaluate Entity annotation
if (isset($classAnnotations[Mapping\Entity::class])) {
$entityAnnot = $classAnnotations[Mapping\Entity::class];
assert($entityAnnot instanceof Mapping\Entity);
if ($entityAnnot->repositoryClass !== null) {
$metadata->setCustomRepositoryClass($entityAnnot->repositoryClass);
}
@@ -80,6 +73,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
}
} elseif (isset($classAnnotations[Mapping\MappedSuperclass::class])) {
$mappedSuperclassAnnot = $classAnnotations[Mapping\MappedSuperclass::class];
assert($mappedSuperclassAnnot instanceof Mapping\MappedSuperclass);
$metadata->setCustomRepositoryClass($mappedSuperclassAnnot->repositoryClass);
$metadata->isMappedSuperclass = true;
@@ -91,87 +85,84 @@ class AnnotationDriver extends AbstractAnnotationDriver
// Evaluate Table annotation
if (isset($classAnnotations[Mapping\Table::class])) {
$tableAnnot = $classAnnotations[Mapping\Table::class];
$tableAnnot = $classAnnotations[Mapping\Table::class];
assert($tableAnnot instanceof Mapping\Table);
$primaryTable = [
'name' => $tableAnnot->name,
'schema' => $tableAnnot->schema,
];
if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$index = [];
foreach ($tableAnnot->indexes ?? [] as $indexAnnot) {
$index = [];
if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}
if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}
if (! empty($indexAnnot->fields)) {
$index['fields'] = $indexAnnot->fields;
}
if (! empty($indexAnnot->fields)) {
$index['fields'] = $indexAnnot->fields;
}
if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexAnnot->name ?? count($primaryTable['indexes']))
);
}
if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexAnnot->name ?? count($primaryTable['indexes']))
);
}
if (! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
}
if (! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
}
if (! empty($indexAnnot->options)) {
$index['options'] = $indexAnnot->options;
}
if (! empty($indexAnnot->options)) {
$index['options'] = $indexAnnot->options;
}
if (! empty($indexAnnot->name)) {
$primaryTable['indexes'][$indexAnnot->name] = $index;
} else {
$primaryTable['indexes'][] = $index;
}
if (! empty($indexAnnot->name)) {
$primaryTable['indexes'][$indexAnnot->name] = $index;
} else {
$primaryTable['indexes'][] = $index;
}
}
if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
$uniqueConstraint = [];
foreach ($tableAnnot->uniqueConstraints ?? [] as $uniqueConstraintAnnot) {
$uniqueConstraint = [];
if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}
if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}
if (! empty($uniqueConstraintAnnot->fields)) {
$uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
}
if (! empty($uniqueConstraintAnnot->fields)) {
$uniqueConstraint['fields'] = $uniqueConstraintAnnot->fields;
}
if (
isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
|| (
! isset($uniqueConstraint['columns'])
&& ! isset($uniqueConstraint['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueConstraintAnnot->name ?? count($primaryTable['uniqueConstraints']))
);
}
if (
isset($uniqueConstraint['columns'], $uniqueConstraint['fields'])
|| (
! isset($uniqueConstraint['columns'])
&& ! isset($uniqueConstraint['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueConstraintAnnot->name ?? count($primaryTable['uniqueConstraints']))
);
}
if (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
}
if (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
}
if (! empty($uniqueConstraintAnnot->name)) {
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
} else {
$primaryTable['uniqueConstraints'][] = $uniqueConstraint;
}
if (! empty($uniqueConstraintAnnot->name)) {
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
} else {
$primaryTable['uniqueConstraints'][] = $uniqueConstraint;
}
}
@@ -274,15 +265,17 @@ class AnnotationDriver extends AbstractAnnotationDriver
// Evaluate InheritanceType annotation
if (isset($classAnnotations[Mapping\InheritanceType::class])) {
$inheritanceTypeAnnot = $classAnnotations[Mapping\InheritanceType::class];
assert($inheritanceTypeAnnot instanceof Mapping\InheritanceType);
$metadata->setInheritanceType(
constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAnnot->value)
);
if ($metadata->inheritanceType !== Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
if ($metadata->inheritanceType !== ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn annotation
if (isset($classAnnotations[Mapping\DiscriminatorColumn::class])) {
$discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class];
assert($discrColumnAnnot instanceof Mapping\DiscriminatorColumn);
$metadata->setDiscriminatorColumn(
[
@@ -299,6 +292,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
// Evaluate DiscriminatorMap annotation
if (isset($classAnnotations[Mapping\DiscriminatorMap::class])) {
$discrMapAnnot = $classAnnotations[Mapping\DiscriminatorMap::class];
assert($discrMapAnnot instanceof Mapping\DiscriminatorMap);
$metadata->setDiscriminatorMap($discrMapAnnot->value);
}
}
@@ -307,6 +301,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
// Evaluate DoctrineChangeTrackingPolicy annotation
if (isset($classAnnotations[Mapping\ChangeTrackingPolicy::class])) {
$changeTrackingAnnot = $classAnnotations[Mapping\ChangeTrackingPolicy::class];
assert($changeTrackingAnnot instanceof Mapping\ChangeTrackingPolicy);
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAnnot->value));
}
@@ -410,8 +405,9 @@ class AnnotationDriver extends AbstractAnnotationDriver
// Evaluate AssociationOverrides annotation
if (isset($classAnnotations[Mapping\AssociationOverrides::class])) {
$associationOverridesAnnot = $classAnnotations[Mapping\AssociationOverrides::class];
assert($associationOverridesAnnot instanceof Mapping\AssociationOverrides);
foreach ($associationOverridesAnnot->value as $associationOverride) {
foreach ($associationOverridesAnnot->overrides as $associationOverride) {
$override = [];
$fieldName = $associationOverride->name;
@@ -462,8 +458,9 @@ class AnnotationDriver extends AbstractAnnotationDriver
// Evaluate AttributeOverrides annotation
if (isset($classAnnotations[Mapping\AttributeOverrides::class])) {
$attributeOverridesAnnot = $classAnnotations[Mapping\AttributeOverrides::class];
assert($attributeOverridesAnnot instanceof Mapping\AttributeOverrides);
foreach ($attributeOverridesAnnot->value as $attributeOverrideAnnot) {
foreach ($attributeOverridesAnnot->overrides as $attributeOverrideAnnot) {
$attributeOverride = $this->columnToArray($attributeOverrideAnnot->name, $attributeOverrideAnnot->column);
$metadata->setAttributeOverride($attributeOverrideAnnot->name, $attributeOverride);
@@ -473,6 +470,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
// Evaluate EntityListeners annotation
if (isset($classAnnotations[Mapping\EntityListeners::class])) {
$entityListenersAnnot = $classAnnotations[Mapping\EntityListeners::class];
assert($entityListenersAnnot instanceof Mapping\EntityListeners);
foreach ($entityListenersAnnot->value as $item) {
$listenerClassName = $metadata->fullyQualifiedClassName($item);
@@ -635,6 +633,22 @@ class AnnotationDriver extends AbstractAnnotationDriver
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
}
/**
* Attempts to resolve the generated mode.
*
* @psalm-return ClassMetadataInfo::GENERATED_*
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getGeneratedMode(string $generatedMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
throw MappingException::invalidGeneratedMode($generatedMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
}
/**
* Parses the given method.
*
@@ -720,6 +734,10 @@ class AnnotationDriver extends AbstractAnnotationDriver
* unique: bool,
* nullable: bool,
* precision: int,
* notInsertable?: bool,
* notUpdateble?: bool,
* generated?: ClassMetadataInfo::GENERATED_*,
* enumType?: class-string,
* options?: mixed[],
* columnName?: string,
* columnDefinition?: string
@@ -728,15 +746,27 @@ class AnnotationDriver extends AbstractAnnotationDriver
private function columnToArray(string $fieldName, Mapping\Column $column): array
{
$mapping = [
'fieldName' => $fieldName,
'type' => $column->type,
'scale' => $column->scale,
'length' => $column->length,
'unique' => $column->unique,
'nullable' => $column->nullable,
'precision' => $column->precision,
'fieldName' => $fieldName,
'type' => $column->type,
'scale' => $column->scale,
'length' => $column->length,
'unique' => $column->unique,
'nullable' => $column->nullable,
'precision' => $column->precision,
];
if (! $column->insertable) {
$mapping['notInsertable'] = true;
}
if (! $column->updatable) {
$mapping['notUpdatable'] = true;
}
if ($column->generated) {
$mapping['generated'] = $this->getGeneratedMode($column->generated);
}
if ($column->options) {
$mapping['options'] = $column->options;
}
@@ -749,6 +779,10 @@ class AnnotationDriver extends AbstractAnnotationDriver
$mapping['columnDefinition'] = $column->columnDefinition;
}
if ($column->enumType !== null) {
$mapping['enumType'] = $column->enumType;
}
return $mapping;
}

View File

@@ -397,12 +397,72 @@ class AttributeDriver extends AnnotationDriver
}
}
// Evaluate AttributeOverrides annotation
if (isset($classAttributes[Mapping\AttributeOverride::class])) {
foreach ($classAttributes[Mapping\AttributeOverride::class] as $attributeOverrideAttribute) {
$attributeOverride = $this->columnToArray($attributeOverrideAttribute->name, $attributeOverrideAttribute->column);
// Evaluate AssociationOverrides attribute
if (isset($classAttributes[Mapping\AssociationOverrides::class])) {
$associationOverride = $classAttributes[Mapping\AssociationOverrides::class];
$metadata->setAttributeOverride($attributeOverrideAttribute->name, $attributeOverride);
foreach ($associationOverride->overrides as $associationOverride) {
$override = [];
$fieldName = $associationOverride->name;
// Check for JoinColumn/JoinColumns attributes
if ($associationOverride->joinColumns) {
$joinColumns = [];
foreach ($associationOverride->joinColumns as $joinColumn) {
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
$override['joinColumns'] = $joinColumns;
}
if ($associationOverride->inverseJoinColumns) {
$joinColumns = [];
foreach ($associationOverride->inverseJoinColumns as $joinColumn) {
$joinColumns[] = $this->joinColumnToArray($joinColumn);
}
$override['inverseJoinColumns'] = $joinColumns;
}
// Check for JoinTable attributes
if ($associationOverride->joinTable) {
$joinTableAnnot = $associationOverride->joinTable;
$joinTable = [
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema,
'joinColumns' => $override['joinColumns'] ?? [],
'inverseJoinColumns' => $override['inverseJoinColumns'] ?? [],
];
unset($override['joinColumns'], $override['inverseJoinColumns']);
$override['joinTable'] = $joinTable;
}
// Check for inversedBy
if ($associationOverride->inversedBy) {
$override['inversedBy'] = $associationOverride->inversedBy;
}
// Check for `fetch`
if ($associationOverride->fetch) {
$override['fetch'] = constant(Mapping\ClassMetadata::class . '::FETCH_' . $associationOverride->fetch);
}
$metadata->setAssociationOverride($fieldName, $override);
}
}
// Evaluate AttributeOverrides annotation
if (isset($classAttributes[Mapping\AttributeOverrides::class])) {
$attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class];
foreach ($attributeOverridesAnnot->overrides as $attributeOverride) {
$mapping = $this->columnToArray($attributeOverride->name, $attributeOverride->column);
$metadata->setAttributeOverride($attributeOverride->name, $mapping);
}
}
@@ -468,6 +528,20 @@ class AttributeDriver extends AnnotationDriver
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
}
/**
* Attempts to resolve the generated mode.
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getGeneratedMode(string $generatedMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode)) {
throw MappingException::invalidGeneratedMode($generatedMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $generatedMode);
}
/**
* Parses the given method.
*
@@ -554,6 +628,7 @@ class AttributeDriver extends AnnotationDriver
* unique: bool,
* nullable: bool,
* precision: int,
* enumType?: class-string,
* options?: mixed[],
* columnName?: string,
* columnDefinition?: string
@@ -583,6 +658,22 @@ class AttributeDriver extends AnnotationDriver
$mapping['columnDefinition'] = $column->columnDefinition;
}
if ($column->updatable === false) {
$mapping['notUpdatable'] = true;
}
if ($column->insertable === false) {
$mapping['notInsertable'] = true;
}
if ($column->generated !== null) {
$mapping['generated'] = $this->getGeneratedMode($column->generated);
}
if ($column->enumType) {
$mapping['enumType'] = $column->enumType;
}
return $mapping;
}
}

View File

@@ -4,13 +4,28 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Persistence\Mapping\Driver\FileLocator;
use Doctrine\Persistence\Mapping\Driver\PHPDriver as CommonPHPDriver;
/**
* {@inheritDoc}
*
* @deprecated this driver will be removed. Use Doctrine\Persistence\Mapping\Driver\PHPDriver instead
* @deprecated this driver will be removed, use StaticPHPDriver or other mapping drivers instead.
*/
class PHPDriver extends CommonPHPDriver
{
/**
* @param string|string[]|FileLocator $locator
*/
public function __construct($locator)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/9277',
'PHPDriver is deprecated, use StaticPHPDriver or other mapping drivers instead.'
);
parent::__construct($locator);
}
}

View File

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

View File

@@ -800,6 +800,9 @@ class XmlDriver extends FileDriver
* scale?: int,
* unique?: bool,
* nullable?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* enumType?: string,
* version?: bool,
* columnDefinition?: string,
* options?: array
@@ -839,6 +842,18 @@ class XmlDriver extends FileDriver
$mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']);
}
if (isset($fieldMapping['insertable']) && ! $this->evaluateBoolean($fieldMapping['insertable'])) {
$mapping['notInsertable'] = true;
}
if (isset($fieldMapping['updatable']) && ! $this->evaluateBoolean($fieldMapping['updatable'])) {
$mapping['notUpdatable'] = true;
}
if (isset($fieldMapping['generated'])) {
$mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . (string) $fieldMapping['generated']);
}
if (isset($fieldMapping['version']) && $fieldMapping['version']) {
$mapping['version'] = $this->evaluateBoolean($fieldMapping['version']);
}
@@ -847,6 +862,10 @@ class XmlDriver extends FileDriver
$mapping['columnDefinition'] = (string) $fieldMapping['column-definition'];
}
if (isset($fieldMapping['enum-type'])) {
$mapping['enumType'] = (string) $fieldMapping['enum-type'];
}
if (isset($fieldMapping->options)) {
$mapping['options'] = $this->parseOptions($fieldMapping->options->children());
}

View File

@@ -42,7 +42,7 @@ class YamlDriver extends FileDriver
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8465',
'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to annotation or XML driver.'
'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to attribute or XML driver.'
);
parent::__construct($locator, $fileExtension);
@@ -786,6 +786,10 @@ class YamlDriver extends FileDriver
* unique?: mixed,
* options?: mixed,
* nullable?: mixed,
* insertable?: mixed,
* updatable?: mixed,
* generated?: mixed,
* enumType?: class-string,
* version?: mixed,
* columnDefinition?: mixed
* }|null $column
@@ -801,6 +805,10 @@ class YamlDriver extends FileDriver
* unique?: bool,
* options?: mixed,
* nullable?: mixed,
* notInsertable?: mixed,
* notUpdatable?: mixed,
* generated?: mixed,
* enumType?: class-string,
* version?: mixed,
* columnDefinition?: mixed
* }
@@ -848,6 +856,18 @@ class YamlDriver extends FileDriver
$mapping['nullable'] = $column['nullable'];
}
if (isset($column['insertable']) && ! (bool) $column['insertable']) {
$mapping['notInsertable'] = true;
}
if (isset($column['updatable']) && ! (bool) $column['updatable']) {
$mapping['notUpdatable'] = true;
}
if (isset($column['generated'])) {
$mapping['generated'] = constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATED_' . $column['generated']);
}
if (isset($column['version']) && $column['version']) {
$mapping['version'] = $column['version'];
}
@@ -856,6 +876,10 @@ class YamlDriver extends FileDriver
$mapping['columnDefinition'] = $column['columnDefinition'];
}
if (isset($column['enumType'])) {
$mapping['enumType'] = $column['enumType'];
}
return $mapping;
}

View File

@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\ORM\EntityRepository;
/**
* @Annotation
@@ -15,12 +16,18 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
#[Attribute(Attribute::TARGET_CLASS)]
final class Entity implements Annotation
{
/** @var string|null */
/**
* @var string|null
* @psalm-var class-string<EntityRepository>|null
*/
public $repositoryClass;
/** @var bool */
public $readOnly = false;
/**
* @psalm-param class-string<EntityRepository>|null $repositoryClass
*/
public function __construct(?string $repositoryClass = null, bool $readOnly = false)
{
$this->repositoryClass = $repositoryClass;

View File

@@ -6,9 +6,8 @@ namespace Doctrine\ORM\Mapping\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\ORM\Exception\ORMException;
use LogicException;
use function get_class;
use function get_debug_type;
use function sprintf;
final class CannotGenerateIds extends ORMException
@@ -17,7 +16,7 @@ final class CannotGenerateIds extends ORMException
{
return new self(sprintf(
'Platform %s does not support generating identifiers',
get_class($platform)
get_debug_type($platform)
));
}
}

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