Compare commits

...

122 Commits

Author SHA1 Message Date
Claudio Zizza 4b88ce787d Introduce __unserialize behaviour in docs (#9390)
* Introduce __unserialize behaviour in docs

* Mention deprecation of Serializable interface

* Add link to __unserialize method
2022-01-30 22:47:06 +01:00
Grégoire Paris f9c3470a8d Adapt test logic to PHP and SQLite II (#9442)
In a88242ee6c, testDateSub() was modified
in order to satisfy different opinions to the question "What is now
minus one month", that has different answers for different pieces of
software on March 30th.

Today, we are January 30th, we need to do the same for testDateAdd().
Hopefully this is the last time we hear about this.
2022-01-30 17:48:10 +01:00
Grégoire Paris c1b131b67e Merge pull request #9440 from sir-kain/php-8.1-ci
Added php 8.1 to CI
2022-01-30 09:55:04 +01:00
Grégoire Paris 16b82ea061 Use the identify generator strategy
It is a better default, and should fix tests for PostgreSQL
2022-01-29 11:33:13 +01:00
Waly f8f370ace6 Added php 8.1 to CI 2022-01-28 22:55:25 +00:00
Alexander M. Turek d5c69fb73f Psalm 4.19.0, PHPStan 1.4.3 (#9438) 2022-01-28 21:54:10 +00:00
Alexander M. Turek 93f9eb7af2 Ignore PHPUnit result cache everywhere (#9425) 2022-01-24 12:35:44 +01:00
HypeMC 6d5da83c68 Add support for PHP 8.1 enums in embedded classes (#9419) 2022-01-23 23:56:36 +01:00
jworman 5f01dd8d09 Added class-string typehint on $targetEntity (#9415) 2022-01-23 20:09:41 +01:00
Benjamin Cremer b596e6a665 Allow DiscriminatorColumn with length=0 (#9410) 2022-01-21 10:27:29 +01:00
Alexander M. Turek 79d3cf5880 Move UnderscoreNamingStrategyTest to correct namespace (#9414) 2022-01-20 20:49:11 +01:00
Alexander M. Turek d7b7c28ae5 Fix type on loadCacheEntry (#9398) 2022-01-18 22:49:52 +01:00
Alexander M. Turek d6fd510c49 Update baselines for DBAL 3.3 (#9393) 2022-01-18 09:13:14 +01:00
olsavmic a2a7d5bb01 Accessing private properties and methods from the same class is forbidden (#9311)
Resolves issue https://github.com/doctrine/common/issues/934

Update docs/en/cookbook/accessing-private-properties-of-the-same-class-from-different-instance.rst

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

Update docs/en/cookbook/accessing-private-properties-of-the-same-class-from-different-instance.rst

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

Fix review issues
2022-01-17 23:15:31 +01:00
Vadim Borodavko 223b2650c4 Expose enumType to DBAL to make native DB Enum possible (#9382) 2022-01-17 10:39:16 +01:00
Vadim Borodavko 01c1644d9c Allow using Enum from different namespace than Entity (#9384) 2022-01-16 13:08:30 +01:00
Sukhdev Mohan 3eff2d4b3f Corrected ORM version and added missing dependency (#9386)
* Corrected ORM version and added missing dependency

Noticed that the version wasn't updated, pointing to 2.11.0 instead of 2.10.2. 
Also when following this tutotial ran into missing dependency for "doctrine/annotation" so added that too.

* Tutorial: Bump DBAL, YAML and Cache

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-01-16 02:28:30 +01:00
Alexander M. Turek 9ddf8b96f8 PHPStan 1.4.0 (#9385) 2022-01-16 01:22:41 +01:00
Benjamin Eberlei 3d00fa817a [GH-9380] Bugfix: Delegate ReflectionEnumProperty::getAttributes(). (#9381)
* [GH-9380] Bugfix: Delegate ReflectionEnumProperty::getAttributes().

* [GH-9380] Add test for retrieving attributes via enum property.

* [GH-9380] Add test for retrieving attributes via enum property.

* [GH-9380] Call parent ReflectionProperty ctor for best behavior.

* Update tests/Doctrine/Tests/ORM/Functional/EnumTest.php

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

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2022-01-16 00:06:59 +01:00
Andrii Dembitskyi c0a1404e4c Add detach as of list cascade-all operations (#9357) 2022-01-12 22:33:11 +01:00
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
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 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
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
Alexander M. Turek 56e0ac02af Merge 2.10.x into 2.11.x (#9248) 2021-12-13 22:21:00 +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 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
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
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
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 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
478 changed files with 4928 additions and 2929 deletions
+12 -6
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",
+2 -2
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"
+3 -3
View File
@@ -78,7 +78,7 @@ jobs:
strategy:
matrix:
php-version:
- "7.4"
- "8.1"
dbal-version:
- "default"
postgres-version:
@@ -139,7 +139,7 @@ jobs:
strategy:
matrix:
php-version:
- "7.4"
- "8.1"
dbal-version:
- "default"
mariadb-version:
@@ -205,7 +205,7 @@ jobs:
strategy:
matrix:
php-version:
- "7.4"
- "8.1"
dbal-version:
- "default"
mysql-version:
@@ -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 }}
+2 -9
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:
+1 -1
View File
@@ -15,5 +15,5 @@ vendor/
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock
/.phpunit.result.cache
.phpunit.result.cache
/*.phpunit.xml
+7 -7
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
+54 -2
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
+10 -6
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.4.3",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
"squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^4.4 || ^5.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.15.0"
"vimeo/psalm": "4.19.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 2.0"
@@ -0,0 +1,74 @@
Accessing private/protected properties/methods of the same class from different instance
========================================================================================
.. sectionauthor:: Michael Olsavsky (olsavmic)
As explained in the :doc:`restrictions for entity classes in the manual <../reference/architecture>`,
it is dangerous to access private/protected properties of different entity instance of the same class because of lazy loading.
The proxy instance that's injected instead of the real entity may not be initialized yet
and therefore not contain expected data which may result in unexpected behavior.
That's a limitation of current proxy implementation - only public methods automatically initialize proxies.
It is usually preferable to use a public interface to manipulate the object from outside the `$this`
context but it may not be convenient in some cases. The following example shows how to do it safely.
Safely accessing private properties from different instance of the same class
-----------------------------------------------------------------------------
To safely access private property of different instance of the same class, make sure to initialise
the proxy before use manually as follows:
.. code-block:: php
<?php
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Entity
{
// ...
/**
* @ORM\ManyToOne(targetEntity="Entity")
* @ORM\JoinColumn(nullable=false)
*/
private self $parent;
/**
* @ORM\Column(type="string", nullable=false)
*/
private string $name;
// ...
public function doSomethingWithParent()
{
// Always initializing the proxy before use
if ($this->parent instanceof Proxy) {
$this->parent->__load();
}
// Accessing the `$this->parent->name` property without loading the proxy first
// may throw error in case the Proxy has not been initialized yet.
$this->parent->name;
}
public function doSomethingWithAnotherInstance(self $instance)
{
// Always initializing the proxy before use
if ($instance instanceof Proxy) {
$instance->__load();
}
// Accessing the `$instance->name` property without loading the proxy first
// may throw error in case the Proxy has not been initialized yet.
$instance->name;
}
// ...
}
@@ -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
----------------------
@@ -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
+10 -4
View File
@@ -82,9 +82,13 @@ be any regular PHP class observing the following restrictions:
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
- An entity class must not implement ``__wakeup`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
Also consider implementing
`Serializable <https://php.net/manual/en/class.serializable.php>`_
instead.
You can also consider implementing
`Serializable <https://php.net/manual/en/class.serializable.php>`_,
but be aware that it is deprecated since PHP 8.1. We do not recommend its usage.
- PHP 7.4 introduces :doc:`the new magic method <https://php.net/manual/en/language.oop5.magic.php#object.unserialize>`
``__unserialize``, which changes the execution priority between
``__wakeup`` and itself when used. This can cause unexpected behaviour in
an Entity.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
@@ -93,6 +97,7 @@ be any regular PHP class observing the following restrictions:
- An entity cannot make use of func_get_args() to implement variable parameters.
Generated proxies do not support this for performance reasons and your code might
actually fail to work when violating this restriction.
- Entity cannot access private/protected properties/methods of another entity of the same class or :doc:`do so safely <../cookbook/accessing-private-properties-of-the-same-class-from-different-instance>`.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
@@ -161,7 +166,8 @@ possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us).
did not find a way yet, if you did, please contact us). The
``Serializable`` interface is also deprecated beginning with PHP 8.1.
The EntityManager
~~~~~~~~~~~~~~~~~
+113 -2
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]
@@ -89,6 +178,18 @@ Optional parameters:
- **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,6 +260,15 @@ 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]
@@ -546,7 +656,8 @@ 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 parameters:
@@ -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:
+9
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
@@ -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
~~~~~~~~~~~~~~~
+94 -137
View File
@@ -121,16 +121,19 @@ 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
---------------
@@ -165,7 +168,7 @@ Events Overview
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No | `_PostFlushEventArgs` |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| ``onClear`` | ``$em->clear()`` | No | `_OnClearEventArgs` |
| :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
@@ -300,10 +215,8 @@ specific to a particular entity class's lifecycle.
use Doctrine\DBAL\Types\Types;
use Doctrine\Persistence\Event\LifecycleEventArgs;
/**
* #[Entity]
* #[HasLifecycleCallbacks]
*/
#[Entity]
#[HasLifecycleCallbacks]
class User
{
// ...
@@ -538,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
@@ -560,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
@@ -579,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
@@ -603,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
@@ -616,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
@@ -651,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
@@ -668,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
@@ -690,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.
@@ -759,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.
@@ -776,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
----------------
@@ -1013,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
@@ -1038,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
-----------------
+5
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``:
+1 -1
View File
@@ -172,7 +172,7 @@ To enable the second-level-cache, you should provide a cache factory.
<?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);
@@ -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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+6
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.
@@ -689,6 +694,7 @@ specified by their respective tags:
- ``<cascade-merge />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
- ``<cascade-detach />``
Join Column Element
~~~~~~~~~~~~~~~~~~~
+5 -4
View File
@@ -81,10 +81,11 @@ that directory with the following contents:
{
"require": {
"doctrine/orm": "^2.10.2",
"doctrine/dbal": "^3.1.1",
"symfony/yaml": "2.*",
"symfony/cache": "^5.3"
"doctrine/orm": "^2.11.0",
"doctrine/dbal": "^3.2",
"doctrine/annotations": "1.13.2",
"symfony/yaml": "^5.4",
"symfony/cache": "^5.4"
},
"autoload": {
"psr-0": {"": "src/"}
+14
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" />
@@ -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" />
+44 -13
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)) {
@@ -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;
+1 -2
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;
@@ -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;
@@ -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;
@@ -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);
}
+2 -2
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.
*/
+42 -37
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);
}
}
@@ -11,7 +11,6 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use function array_walk;
use function assert;
/**
@@ -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) {
+1 -2
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> */
+2 -4
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
*/
+2 -4
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;
+3 -1
View File
@@ -24,7 +24,9 @@ interface EntityHydrator
* @param ClassMetadata $metadata The entity metadata.
* @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.
* @param object|null $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);
}
@@ -6,8 +6,6 @@ namespace Doctrine\ORM\Cache\Exception;
use Doctrine\ORM\Cache\CacheException as BaseCacheException;
use function sprintf;
/**
* Exception for cache.
*/
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
use function sprintf;
class CannotUpdateReadOnlyCollection extends CacheException
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
use function sprintf;
class CannotUpdateReadOnlyEntity extends CacheException
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
class FeatureNotImplemented extends CacheException
{
public static function scalarResults(): self
@@ -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
@@ -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) . '.'
);
}
}
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Exception;
use LogicException;
use function sprintf;
class NonCacheableEntity extends CacheException
@@ -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
@@ -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) . '.'
);
}
}
@@ -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;
+2 -4
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;
+9 -6
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,
+6
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();
@@ -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;
}
}
+112 -45
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, '{}()/\@:', '________');
}
}
@@ -228,8 +228,6 @@ class FileLockRegion implements ConcurrentRegion
/**
* {@inheritdoc}
*
* @return bool
*/
public function unlock(CacheKey $key, Lock $lock)
{
@@ -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;
+35 -5
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;
}
}
@@ -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
);
+12 -5
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
*/
@@ -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
*
@@ -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
{
+4 -3
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()
{
@@ -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()
{
@@ -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()
{
@@ -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()
{
+2 -3
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()
{
@@ -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())
));
}
}
@@ -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
@@ -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.');
@@ -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
{
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
final class MissingIdentifierField extends ORMException implements ManagerException
@@ -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
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
final class NamedQueryNotFound extends ORMException implements ConfigurationException
@@ -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
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function sprintf;
final class UnknownEntityNamespace extends ORMException implements ConfigurationException
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use LogicException;
use function implode;
use function sprintf;
+61 -2
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
+3 -3
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();
@@ -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);
}
+2 -2
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);
}
+2 -2
View File
@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Serializable;
use function serialize;
@@ -51,7 +51,7 @@ 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
+3 -3
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) {
+2 -2
View File
@@ -7,7 +7,7 @@ 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;
@@ -38,7 +38,7 @@ class UuidGenerator extends AbstractIdGenerator
*
* @throws NotSupported
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
$connection = $em->getConnection();
$sql = 'SELECT ' . $connection->getDatabasePlatform()->getGuidExpression();
@@ -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) {
@@ -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);
}
}
@@ -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);
}
@@ -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()) {
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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.
*
@@ -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
+135 -21
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
);
@@ -1013,23 +1056,40 @@ class ClassMetadataInfo implements ClassMetadata
foreach ($this->fieldMappings as $field => $mapping) {
if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
$childProperty = $this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField']);
assert($childProperty !== null);
if (isset($mapping['enumType'])) {
$childProperty = new ReflectionEnumProperty(
$childProperty,
$mapping['enumType']
);
}
$this->reflFields[$field] = new ReflectionEmbeddedProperty(
$parentReflFields[$mapping['declaredField']],
$reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
$childProperty,
$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 +1527,15 @@ class ClassMetadataInfo implements ClassMetadata
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
$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 +1657,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 +2240,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 +2467,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 +2731,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 +3017,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 +3465,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 +3490,10 @@ class ClassMetadataInfo implements ClassMetadata
public function setVersioned($bool)
{
$this->isVersioned = $bool;
if ($bool) {
$this->requiresFetchAfterChange = true;
}
}
/**
@@ -3590,9 +3690,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 +3701,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 +3846,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;
}
}
+28 -2
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;
}
}
@@ -15,13 +15,13 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
#[Attribute(Attribute::TARGET_CLASS)]
final class DiscriminatorColumn implements Annotation
{
/** @var string */
/** @var string|null */
public $name;
/** @var string */
/** @var string|null */
public $type;
/** @var int */
/** @var int|null */
public $length;
/**
@@ -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;
@@ -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,21 +265,23 @@ 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(
[
'name' => $discrColumnAnnot->name,
'type' => $discrColumnAnnot->type ?: 'string',
'length' => $discrColumnAnnot->length ?: 255,
'length' => $discrColumnAnnot->length ?? 255,
'columnDefinition' => $discrColumnAnnot->columnDefinition,
]
);
@@ -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;
}
@@ -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;
}
}
+16 -1
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);
}
}
@@ -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
{
@@ -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());
}

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