Compare commits

..

164 Commits
2.8.3 ... 2.9.2

Author SHA1 Message Date
Yup
75b4b88c5b Add automatic type detection for Embedded. (#8724)
* Add automatic type detection for Embedded.

* Inline statement.

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2021-05-31 11:53:14 +02:00
Benjamin Eberlei
d9e59d6862 [GH-8723] Remove use of nullability to automatically detect nullable status (#8732)
* [GH-8723] Remove use of nullability to automatically detect nullable status.

* [GH-8723] Make Column::$nullable default to false again, fix tests.
2021-05-31 10:19:16 +02:00
Yup
5fa94969de Adapt flush($argument) in documentation as it's deprecated. (#8728) 2021-05-29 22:22:19 +02:00
Juan Iglesias
f2c3ddac97 Add note about performance and inheritance mapping (#8704)
Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2021-05-27 08:27:55 +02:00
Grégoire Paris
46f0da9ffa Merge pull request #8710 from franmomu/recompute
Handle generic parameters in UnitOfWork
2021-05-25 13:29:21 +02:00
Fran Moreno
1e832a6782 Add generics to parameters 2021-05-25 13:01:47 +02:00
Grégoire Paris
56bdb44efd Merge pull request #8722 from alcaeus/fix-metadata-cache-clear 2021-05-25 12:55:00 +02:00
Andreas Braun
fffac44991 Bump doctrine/cache patch dependency to fix build with lowest deps 2021-05-25 11:58:11 +02:00
Andreas Braun
e42b3d6584 Fix metadata cache compatibility layer 2021-05-25 10:38:00 +02:00
Grégoire Paris
7ab2c3abbd Merge pull request #8708 from VincentLanglet/patch-2
Fix ClassMetadaInfo template inference
2021-05-25 08:49:50 +02:00
Grégoire Paris
498c816b65 Merge pull request #8717 from greg0ire/update-branch-metadata
Mark 2.8.x as unmaintained, and 2.9.x as current
2021-05-25 00:01:21 +02:00
Vincent Langlet
eec740079d Fix ClassMetadataInfo template inference 2021-05-24 21:52:40 +02:00
Grégoire Paris
c359715a97 Mark 2.8.x as unmaintained, and 2.9.x as current 2021-05-24 21:32:26 +02:00
Benjamin Eberlei
f3e55fae9f Update .doctrine-project.json to include 2.9 stable and 2.10 upcoming 2021-05-24 17:54:12 +02:00
Michael Babker
91c3bd4121 Fix links to attribute sections (#8714) 2021-05-24 17:52:30 +02:00
Peter Gribanov
e6cf12c66f remove usage Webmozart (#8713) 2021-05-24 17:50:15 +02:00
Grégoire Paris
99d67cb77d Merge pull request #8705 from greg0ire/2.9.x
Merge 2.8.x into 2.9.x
2021-05-21 09:15:28 +02:00
Grégoire Paris
43f66d5808 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-05-21 09:04:27 +02:00
Grégoire Paris
a6577b89a2 Merge pull request #8701 from jderusse/symfony6
Allow Symfony 6.0
2021-05-20 07:58:49 +02:00
Jérémy Derussé
0ca87566a9 Allow Symfony 6.0 2021-05-20 07:48:21 +02:00
Grégoire Paris
5d01f94a36 Merge pull request #8699 from greg0ire/fix-psalm
Fix some static analysis issues
2021-05-20 07:43:12 +02:00
Grégoire Paris
3d02b02636 Update static analysis baseline files
These issues were not introduced with new code, but with upgrades.
2021-05-20 00:03:39 +02:00
Grégoire Paris
6de321cb09 Address Psalm issues introduced by persistence 2021-05-20 00:03:39 +02:00
Grégoire Paris
535bc92dc8 Merge pull request #8700 from deguif/fix-undefined-offset
Fix undefined index
2021-05-18 12:20:38 +02:00
François-Xavier de Guillebon
ebb5d03f7a Fix undefined offset 2021-05-18 10:00:19 +02:00
Grégoire Paris
8e13369621 Merge pull request #8698 from deguif/cache-deprecation
Fix cache deprecation
2021-05-17 22:15:13 +02:00
François-Xavier de Guillebon
8eff4b775a Fix cache deprecation 2021-05-17 21:33:52 +02:00
Grégoire Paris
b85403d0a2 Merge pull request #8691 from alcaeus/check-deprecations
Check for use of deprecated API with Psalm
2021-05-15 18:50:30 +02:00
Andreas Braun
22ce3adfce Move to psalm level 2
This converts more issues to errors, most notably around deprecations. This can be used to later remove deprecated API.
2021-05-15 17:01:43 +02:00
tweet9ra
3a194ad699 SimpleObjectHydrator: skip unsuit custom type before converting it (#8566)
When using inheritance, it is possible to map the same column to properties of
different child classes. This can result in the same column being selecting several
times with different aliases in one SQL query, and only one aliased field needs
to be hydrated per row.
We now check that such an aliased value is mapped to the class we are hydrated
before attempting to convert it as it might result in an error when using a custom
type that does not get the expected data to initialize php value.

Co-authored-by: Sergey Naumov <s.naumov@lamoda.ru>
2021-05-15 09:37:16 +02:00
Andreas Braun
d52dab54dd Merge pull request #8672 from alcaeus/allow-cache-2.0 2021-05-14 20:21:56 +02:00
Andreas Braun
b5ac7714bc Remove ignored phpstan errors related to doctrine/cache 2.0 2021-05-13 20:38:21 +02:00
Andreas Braun
590551d5c3 Fix setup tool tests 2021-05-13 20:16:52 +02:00
Andreas Braun
c9fb9fdb40 Improve BC layer for getMetadataCacheImpl 2021-05-13 20:16:52 +02:00
Andreas Braun
965926dcc8 Update phpstan baseline to account for doctrine/cache deprecation 2021-05-13 20:16:52 +02:00
Andreas Braun
a6e30c5f4c Fix checkstyle violations 2021-05-13 20:16:52 +02:00
Andreas Braun
30ab6f4cea Add upgrade note for cache changes 2021-05-13 20:16:52 +02:00
Andreas Braun
5e5a44dce2 Suggest using symfony/cache in setup tool 2021-05-13 20:16:52 +02:00
Andreas Braun
d7bf30b291 Fix setup tool tests 2021-05-13 20:16:51 +02:00
Andreas Braun
ce8da6623f Stop using doctrine/cache 2021-05-13 20:16:51 +02:00
Andreas Braun
2ecec0c5d6 Remove reliance on doctrine/cache implementations in tests 2021-05-13 20:16:51 +02:00
Andreas Braun
6f128e4515 Allow installing doctrine/cache 2.0 2021-05-13 20:11:14 +02:00
Benjamin Eberlei
e24b0f0be7 [GH-8589] A new approach to non-nullable typed associations for BC (#8678)
* [GH-8589] A new approach to non-nullable typed associations for BC

* Address review comment about keeping the target entity type detection.
2021-05-10 19:08:16 +02:00
Benjamin Eberlei
6753b26f73 [GH-8676] Allow nested annotations to work without parents as attributes (#8677)
* [GH-8676] Allow nested annotations to work without parents as attributes.

* Housekeeping
2021-05-09 19:58:44 +02:00
Grégoire Paris
4ccc4e19fc Merge pull request #8600 from VincentLanglet/computeChangeset
Remove internal tag from computeChangeSet
2021-05-05 19:27:39 +02:00
Simon Podlipsky
4e2009433b Reflect that default EntityManager is not always named default (#8671)
Follows up #8646
2021-05-05 18:53:34 +02:00
Grégoire Paris
c25b822217 Merge pull request #8673 from Seldaek/patch-1
Add hint for ->iterate() deprecation
2021-05-05 13:31:30 +02:00
Jordi Boggiano
c3dcc5af91 Add hint for ->iterate() deprecation 2021-05-05 10:31:33 +02:00
Grégoire Paris
b2f404b25f Merge pull request #8651 from alcaeus/deprecate-doctrine-metadata-cache
Introduce PSR-6 for metadata caching
2021-05-01 14:53:46 +02:00
Alexandr Li
d141f27875 ConvertDoctrine1Schema: Fix Doctrine 1 notnull field import (#8649)
* ConvertDoctrine1Schema: Fix Doctrine 1 `notnull` field import

* cs fix

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-05-01 13:56:41 +02:00
Andreas Braun
4691839201 Extend DoctrineTestCase to fix missing methods 2021-04-29 12:54:23 +02:00
Andreas Braun
91387382b7 Allow symfony/cache 4.4 to provide PHP 7.1 support 2021-04-29 09:48:27 +02:00
Andreas Braun
f634c64b7a Use stubs over mocks in tests
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-04-29 09:42:18 +02:00
Andreas Braun
7ba9c980b5 Introduce PSR-6 for metadata caching 2021-04-29 09:42:18 +02:00
Grégoire Paris
dacdcf2c7b Merge pull request #8662 from greg0ire/2.9.x
Merge 2.8.x into 2.9.x
2021-04-29 09:27:40 +02:00
Grégoire Paris
f296fee9e4 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-04-29 09:20:26 +02:00
Grégoire Paris
8555fc1d34 Merge pull request #8659 from greg0ire/make-tests-independent
Make tests independent
2021-04-29 09:07:26 +02:00
Grégoire Paris
b0826fd746 Merge pull request #8201 from oojacoboo/2.8.x
Throw Exception when unable to locate identifier
2021-04-28 15:57:50 +02:00
Jacob Thomason
fe93c2e9d5 Throw Exception that includes name of entity when unable to locate identifier 2021-04-28 15:33:03 +02:00
Grégoire Paris
850d57827f Make tests independent
It seems like IdentityMapTest cannot be run on its own when the second
level cache is enabled (with ENABLE_SECOND_LEVEL_CACHE=1).
It does work when running the whole test suite because
ExtraLazyCollectionTest disables part of that cache in its setUp()
method.
In this patch, we restore the class metadata as it was before running
setUp() and put the test in IdentityMapTest inside the group that is
excluded when running with ENABLE_SECOND_LEVEL_CACHE=1 on the CI.
2021-04-28 14:12:31 +02:00
Benjamin Eberlei
e1388fa986 [GH-8327] Make EntityManagerProvider compatible with expected DoctrineBundle usage (#8646)
* [GH-8327] Make EntityManagerProvider compatible with expected DoctrineBundle usage.

* phpcs

* [Gh-8327] Delegate to EntityManagerProvider::getDefaultManager in ConnectionFromManagerProvider
2021-04-25 19:44:12 +02:00
Grégoire Paris
9a48450481 Merge pull request #7608 from mavroprovato/patch-1
Avoid unnecessary flush after processing first row
2021-04-24 14:23:09 +02:00
Kostas Kokkoros
cff8b96dd6 Avoid unnecessary flush after processing first row
The code as is needlessly flushes after just one row is updated or
removed. It makes more sense to update after ``$batchSize`` elements are
updated or removed, just as it is in the insert case.
2021-04-24 13:01:33 +02:00
Grégoire Paris
996c1c74b3 Merge pull request #8644 from greg0ire/more-accurate-return-type
Describe return types more accurately
2021-04-22 22:31:05 +02:00
Grégoire Paris
48612e6dc6 Merge pull request #8641 from Jean85/remove-deprecated-proxy-usage
Replace deprecated Proxy usages with parent interface to reduce baseline
2021-04-19 23:37:22 +02:00
Benjamin Eberlei
ddfee26f80 Support for Array parameters in SQL filters (#8375)
* #1168 Add support for array parameters on the SQLFilter

* DDC-1168 Add support for array parameters on the SQLFilter

* [GH-2624] Rework array support to use new getParameterList()

* [DDC-1952] Change at() mocking to using returnCallback()

* [DDC-1952] Make arrays of values explicit with new setParameterList

* Adjust tests to use country as list and locale as single value.

* [DDC-1952] Add tests for new exxeption conditions.

* Apply suggestions from code review

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

Co-authored-by: Manuel Nogales <nogales.manuel@gmail.com>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-04-19 19:46:22 +02:00
Grégoire Paris
eb860a704e Change incorrect DBAL return types 2021-04-19 19:27:00 +02:00
Grégoire Paris
51ffcb4891 Describe return types more accurately
This fixes an SA regression introduced when using stricter types in
SchemaTool.

Fixes #8642
2021-04-19 19:09:10 +02:00
Grégoire Paris
72f500318a Merge pull request #8544 from greg0ire/bc-type-declarations
Add type declarations where backwards-compatible
2021-04-19 14:02:17 +02:00
Grégoire Paris
55f030f66b Add type declarations where backwards-compatible
This includes:
- private methods
- return type declarations of final protected methods
- return type declarations of public and protected methods of final classes

Parameter type declarations are a more delicate matter and should
probably be handled separately to make it easier to catch issues during
code review.

Type declarations can be more trusted than simple phpdoc when it
comes to static analysis, having them means we can infer the phpdoc of
calling methods with confidence.

Note that it seems that some of the phpdoc I initially inferred these
declarations from were apparently wrong, in particular some mentioning
Doctrine\Dbal\Statement when was is really passed around is
Doctrine\Dbal\Driver\Statement.
2021-04-19 13:51:00 +02:00
orklah
95af30eb72 FileLockRegion::__construct 3rd param is meant to be an int (#8640) 2021-04-19 13:29:43 +02:00
Alessandro Lai
9ea0769d78 Replace deprecated Proxy usages with parent interface to reduce baseline 2021-04-19 10:43:49 +02:00
Benjamin Eberlei
22413453da Reintroduce PHP 7.1 support (#8613)
* Reintroduce PHP 7.1 support

* phpcs

* Another object typehint

* More compatibility

* phpcs

* Reduce doctrine/inflector versions again since 1.4.4 is released.

* Housekeeping: phpcbf

* Simplify PHPUnit Polyfill abstraction.

* Missing assertDoesNotMatchRegularExpression

* phpcs

* Add 7.1 on Github actions, since dependencies now supported.

* Simplify code to work with renamed phpunit assertions and document when to remove.

* phpcs

* Downgrade target phpstan version to 7.1.0

* Run --prefer-lowest with PHP 7.1 not 7.3
2021-04-18 21:24:51 +02:00
Grégoire Paris
06fadcdd8c Merge pull request #8630 from Jean85/reduce-baseline
Reduce baseline
2021-04-18 18:50:59 +02:00
Alessandro Lai
7c56aa2141 Reduce baseline with a nullable return where needed 2021-04-18 18:39:20 +02:00
Alessandro Lai
4cdcb5f760 Reduce baseline for AbstractCollectionPersister 2021-04-18 18:39:20 +02:00
Alessandro Lai
b542b36e45 Remove baseline for DefaultCacheFactory 2021-04-18 18:39:20 +02:00
Alessandro Lai
e5a7a13e1e Remove single baseline rule from DefaultCache 2021-04-18 18:39:20 +02:00
Alessandro Lai
8336dd3779 Remove baseline for AbstractQuery 2021-04-18 18:39:14 +02:00
Grégoire Paris
b04d7a62ae Merge pull request #8548 from orklah/test2
Adding details to types in PHPDoc
2021-04-18 18:09:28 +02:00
Grégoire Paris
a959a474fd Merge pull request #8636 from greg0ire/update-gitattributes
Update ignore rules to reflect current situation
2021-04-18 17:56:47 +02:00
Benjamin Eberlei
ce128e742b [GH-5202] Implement Query::HINT_READ_ONLY flag (#7936)
* Rebase QueryHintReadOnly on 2.9.x

* Housekeeping: phpcs

* [GH-5202] Dont mark known entities as read only.

* [GH-5202] Not mark objects read only when proxy before.

* phpcs
2021-04-18 16:45:25 +02:00
Benjamin Eberlei
dac87dae06 [GH-8327] Deprecate EntityManagerHelper for a provider abstraction. (#8524)
* cli config

* [GH-8327] Deprecate EntityManager HelperSet for a provider abstraction.

* Housekeeping: phpcs

* [GH-8327] Refactor tests towards use of SingleManagerProvider instead of HelperSet.

* [GH-8327] Refactor tests towards use of SingleManagerProvider instead of HelperSet.

* [GH-8327] Refactor tests towards use of SingleManagerProvider instead of HelperSet.

* Housekeeping: cs

* Update tests/Doctrine/Tests/ORM/Tools/Console/ConsoleRunnerTest.php

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

* [GH-8327] Change option from entity-manager to em for consistency with DoctrineBundle.

* Add final to new methods and classes

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

* [GH-8327] Bugfix: phpstan detected stricter type checks needed.

* phpcs

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-04-18 14:32:42 +02:00
Jakub Caban
a2230485b2 Fix typed properties for default metadata (#7939) (#8589)
* Make Column::$type, Column::$nullable and JoinColumn::$nullable nullable by default

* Add tests for mapped typed properties (type and nullable)

* Fix Yaml driver tests and remove driver exceptions thrown too early

* Fix PHP driver tests

* Fix static PHP driver tests

* Fix XML driver tests

* Coding Standards

* Deprecate unused MappingException method

* Add manyToOne test and check nullable at the right place

* Coding Standards

* Bugfix: Temporarily change association join columns in CascadeRemoveOrderTest to circumvent new CommitOrderCalculator bug.

* phpcs

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2021-04-18 14:26:41 +02:00
Benjamin Eberlei
a68aa580c5 [GH-8345] Fields for unique constraints (#8629)
* Add possibility to use fields instead of column for unique constraint and indexes (#8345)

* Document changes in annotation reference

* phpcs

* Ensure exactly one of fields/columns is set for index/uniqueConstraint

* Adapt docs to fields/columns changes

* phpcs

* Implement fields in Attribute driver and fix mapping classes constructors.

* Coding Standard

* Apply suggestions from code review

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

* phpcs

Co-authored-by: Jakub Caban <kuba.iluvatar@gmail.com>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-04-18 14:06:30 +02:00
Grégoire Paris
5ee71c54d4 Update ignore rules to reflect current situation
We no longer use Travis, we do not use git submodules as far as I know,
and we now use baseline files as well as project metadata.
2021-04-18 10:49:48 +02:00
orklah
dc37c2cd2f psalm fixes 2021-04-17 17:11:04 +02:00
Grégoire Paris
261a405970 Merge pull request #8635 from greg0ire/2.9.x
Manually merge 2.8.x into 2.9.x
2021-04-17 16:42:07 +02:00
Grégoire Paris
1ea51d88c4 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-04-17 16:31:14 +02:00
Grégoire Paris
da3a9fa361 Merge pull request #8634 from orklah/static-upgrade
upgrade static tools
2021-04-17 15:41:51 +02:00
orklah
4fd81d26ff upgrade static tools 2021-04-17 13:12:35 +02:00
Benjamin Eberlei
f8e06ad31e [GH-6396] Allow custom hydrators access to meta columns via Query::HINT_INCLUDE_META_COLUMNS hint. (#8382) 2021-04-16 21:27:29 +02:00
Grégoire Paris
559c1ba806 Merge pull request #8628 from greg0ire/2.9.x
Merge 2.8.x up into 2.9.x
2021-04-16 20:08:51 +02:00
Grégoire Paris
4665758c44 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-04-16 19:59:27 +02:00
Grégoire Paris
e2e9f8fa97 Merge pull request #8627 from greg0ire/add-baselines
Add baseline files for static analyzers
2021-04-16 19:04:04 +02:00
Grégoire Paris
f7249ec709 Declare return type
This helps SA tools figure out that it is fine to call count on the
return value of that method.
As a side-effect, using $metadata->name is not really an option since it
is not part of the ClassMetadata interface.
2021-04-16 13:14:54 +02:00
Grégoire Paris
87dbcca454 Add baseline files for static analyzers
There are many CS and SA-related changes in the ORM as we pursue better
code quality, and easier contributions. These often involve huge
changes, which can be hard to review and inevitably lead to some
regressions. I know some of those could have been avoided if we were
using stricter levels for PHPStan and Psalm.

By adding baselines, we ensure new code is at level 5 for both tools,
which should allow us to catch the most interesting issues.
2021-04-16 09:23:11 +02:00
Grégoire Paris
ceeea8ccd1 Merge pull request #8620 from greg0ire/2.9.x
Manually merge 2.8.x into 2.9.x
2021-04-13 19:23:10 +02:00
Grégoire Paris
6e16ef8c31 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-04-13 18:51:39 +02:00
Grégoire Paris
305e0d6664 Merge pull request #8617 from greg0ire/cs9
Upgrade to doctrine/coding-standard 9
2021-04-13 18:48:12 +02:00
Grégoire Paris
199be94e6d Upgrade to doctrine/coding-standard 9 2021-04-13 09:00:33 +02:00
Benjamin Eberlei
09a7d9f18a [GH-6578] Add validation that inherited entity class is mapped in discriminator. (#8378) 2021-04-10 18:13:31 +02:00
Grégoire Paris
f57f33b67f Merge pull request #8606 from greg0ire/2.9.x 2021-04-09 13:33:34 +02:00
Grégoire Paris
e86cddb360 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-04-09 13:21:30 +02:00
Grégoire Paris
fa588af3b1 Merge pull request #8604 from janatjak/2.8.x
Fix psalm param typehint for OneToManyAssociationBuilder::setOrderBy method
2021-04-09 12:57:12 +02:00
Grégoire Paris
d4741720fa Merge pull request #8605 from greg0ire/fix-phpdoc-lsp-violations 2021-04-09 11:59:28 +02:00
Grégoire Paris
343385d060 Pin squizlabs/php_codesniffer
We are referencing rules in phpcs.xml.dist, and may experience
unexpected BC-breaks because of that when they get renamed.
2021-04-09 09:27:44 +02:00
Grégoire Paris
6d04dced03 Address sniff rename
This sniff seems to have been renamed or split in the latest version of
phpcs.
2021-04-09 09:19:10 +02:00
Grégoire Paris
22fa3a8556 Document actual return types
Some executors may return integers, for instance executors that only
execute update or delete statements.
Also, in case an integer is not returned, what's actually returned is a
Doctrine\DBAL\Driver\ResultStatement, and not a Doctrine\DBAL\Driver\Statement
2021-04-09 08:52:56 +02:00
Jakub Janata
eb05756dc3 Fix psalm param typehint for OneToManyAssociationBuilder::setOrderBy method 2021-04-08 22:52:50 +02:00
Grégoire Paris
5bb7e20708 Merge pull request #8602 from NicoHaase/fix-8599
Adjusted return type annotation for getOriginalEntityData
2021-04-07 23:22:19 +02:00
Nico Haase
a9076313c7 Adjusted return type 2021-04-07 21:06:58 +02:00
Grégoire Paris
2a87821b28 Merge pull request #8552 from acoulton/maint-phpunit-upgrade 2021-04-07 15:33:06 +02:00
acoulton
da5877d60c Only polyfill older phpunit methods when required 2021-04-07 12:08:55 +01:00
Andrew Coulton
67dfe8e1af Simplify mock building calls
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-04-07 11:07:35 +01:00
Vincent Langlet
2dfe51b396 Remove internal tag 2021-04-07 11:41:34 +02:00
Grégoire Paris
5ac036de02 Merge pull request #8594 from greg0ire/make-sure-test-is-run 2021-04-06 17:47:17 +02:00
Grégoire Paris
fda0d7b440 Merge pull request #8596 from doctrine/2.8.x-merge-up-into-2.9.x_606c485ba431f2.86881997 2021-04-06 15:06:02 +02:00
Grégoire Paris
23e1fd8ad6 Drop assertion about not being an instance of proxy
We do not want to enforce it as it is an internal details that seems to
vary from environment to environment.
2021-04-06 14:55:25 +02:00
Benjamin Eberlei
f8fa0fe069 [GH-8592] Deprecated Named (Native) Queries in Metadata/EntityRepository (#8593)
* [GH-8592] Deprecated Named (Native) Queries in Metadata and EntityRepository.

* [GH-8592] Add deprecation notice for named queries in docs [ci-skip]
2021-04-05 21:59:08 +02:00
Grégoire Paris
a588555ecd Merge pull request #8586 from KartaviK/patch-3
Additional psalm param typehint for orderBy argument in findBy method
2021-04-05 20:38:36 +02:00
Grégoire Paris
501057da83 Ensure test is suffixed with Test 2021-04-05 14:42:17 +02:00
Grégoire Paris
7de84537f6 Merge pull request #8591 from DmitriiBezborodnikov/case_insensive_parenthesis
Return case insensitive check
2021-04-05 14:40:51 +02:00
Grégoire Paris
97f8325dad Make sure tests are suffixed with Test
They will not be taken into account when running vendor/bin/phpunit
otherwise.
2021-04-05 14:32:40 +02:00
Grégoire Paris
0ebd7052d7 Drop create table at shutdown
It makes tests more isolated from each other: another test relying on
some tables including some of the ones created here may fail creating
the tables it needs because they already exist.
2021-04-05 14:03:49 +02:00
Dmitrii Bezborodnikov
5d73378b92 Return case insensitive check 2021-04-05 14:03:49 +02:00
Grégoire Paris
10572ec441 Merge pull request #8590 from VincentLanglet/patch-2
Fix phpdoc of ClassMetadataInfo::getIdentifierValues
2021-04-04 23:47:09 +02:00
Vincent Langlet
76278d801d Fix phpdoc 2021-04-04 21:19:54 +02:00
Roman Varkuta
ca80830b26 Describe $orderBy parameter as a hash
A list of string is incorrect, it actually looks like this:
['someField' => 'DESC', 'someOtherField' => 'ASC'…]`
2021-04-03 12:45:24 +02:00
Grégoire Paris
1ed89c756a Merge pull request #8582 from doctrine/2.8.x-merge-up-into-2.9.x_6066399d9875f8.96494390
Merge release 2.8.3 into 2.9.x
2021-04-02 09:12:58 +02:00
Grégoire Paris
bb078b5cb7 Merge remote-tracking branch 'origin/2.8.x' into 2.8.x-merge-up-into-2.9.x_6066399d9875f8.96494390 2021-04-02 09:00:35 +02:00
Grégoire Paris
bcb4889a2c Merge pull request #8583 from greg0ire/sync-static-analysis-workflows
Synchronize static analysis jobs with upstream
2021-04-02 08:58:50 +02:00
Grégoire Paris
961da8b0cc Synchronize static analysis jobs with upstream 2021-04-01 23:32:04 +02:00
Benjamin Eberlei
c3f8996af5 Bump requirement to DBAL 2.13 (#8577) 2021-04-01 21:26:25 +02:00
Grégoire Paris
0655083e50 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-04-01 09:02:08 +02:00
Benjamin Eberlei
b6b3c97436 [GH-8265] Attribute Metadata Driver (#8266)
* [GH-8265] Prototype for Attribute Metadata Driver

* [GH-8265] Skip AttributeDriverTest on PHP 7.

* [GH-8265] Fill more test entities with Attribute declarations to pass tests.

* [GH-8265] More test entity attributes for passing AttributeDriverTest.

* [GH-8265] Final changes to get AttributeDriverTest passing with test entities.

* [GH-8265] automatically update cs for new code when possible.

* [GH-8265] exclude sniffs that break because of phpcs not knowing attributes.

* [GH-8265] Fix AttributeReader styles.

* [GH-8265] Fix AttributeReader styles.

* [GH-8265] Missing changes to AttributeDriver

* [GH-8265] Fix InverseJoinColumn attribute cs violations.

* [GH-8265] Fix AbstractMappingDriverTest::_loadDriver and other CS

* [GH-8265] Coding styles

* [GH-8265] Coding styles

* [GH-8265] Coding styles

* [GH-8265] Coding styles

* [GH-8265] Convert Cache, ChangeTrackingPolicy, Column to named annotations.

* [GH-8265] Convert all annotations to named constructor for attribute support.

* [GH-8265] Style after attribute changes.

* [GH-8265] more styles

* [GH-8265] Remove workaround code for attributes.

* More cs

* More cs

* More cs

* More cs

* Add Attribute Metadata driver reference.

* Housekeeping: phpcs

* More merge conflict resolutions

* phpcs

* fix broken merge

* Change NamedArgumentConstructorAnnotation interface to use NamedArgumentConstructor annotation instead.

* phpcs

* Housekeeping: cs

* Housekeeping: cs

* Update docs with review comments

* Improve attribute docs

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

* Rename AttributesDriver to AttributeDriver

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-03-23 13:30:57 +01:00
Benjamin Eberlei
8f6d146bc4 Bump doctrine/deprecations to at least v0.5.3 (#8553) 2021-03-22 23:23:03 +01:00
acoulton
1f4e6ebeeb Add a forward-compatibility wrapper for phpunit8 assertions
While doctrine still supports php7.2 the test cases need to run
under phpunit8 as well. However some assertion methods produce
deprecation warnings in the test output with phpunit >= 9.

This commit adds a thin forward compatiblity wrapper for the
new assertion method names so that they can be used with both
supported phpunit versions.
2021-03-18 11:25:18 +00:00
acoulton
a94db4f5c0 Fix remaining warnings from the phpunit9 upgrade
Some tests were still using deprecated assertion and mocking methods,
resulting in a long list of warnings in the phpunit output.

This commit resolves all the warnings:

* Fixes a couple of test names in `@depends` tags (presumably these
  tests were renamed at some point for coding standards).

* Changes how mocks are configured when asserting the same method
  is called multiple times with a sequence of arguments / sequence
  of return values. The old `->at` expectation is deprecated because
  it can be brittle and give unreliable results. Some of this
  mocking could probably still be refactored further, but my focus
  was on solving the deprecation with the existing setup.

* Removes one use of prophecy for mocking, in favour of using
  phpunit mock objects. Prophecy now requires an extra composer
  dependency and a trait which seems overkill given it was only
  used in one place.

* Updates to the new names for assertFileExists and assertRegExp
  (and their `not` versions) - these are functionally equivalent.

* Replaces the last few references to old PHPUnit_Framework_XXX
  classes with their namespaced equivalent. These were mostly in
  comments, or in native php `assert()` statements that were sanity
  checking mocked object types. These asserts are probably redundant
  (and are clearly not running in CI since the classes they referenced
  no longer exist).
2021-03-18 10:51:43 +00:00
Grégoire Paris
4fccec1322 Merge pull request #8545 from orklah/psalm-plugins-2
remove some empty() use when safe
2021-03-15 23:51:09 +01:00
orklah
0177133385 remove unwanted check with 0 2021-03-15 23:28:20 +01:00
orklah
8df5cb84fa remove some empty() use when safe 2021-03-15 23:28:19 +01:00
Grégoire Paris
3b7275e183 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-03-14 18:57:41 +01:00
Benjamin Eberlei
2685b65c2b [GH-6855] Trigger deprecation for unsupported lifecycle callback mapping on embedded classes.(#8381) 2021-03-04 22:08:11 +01:00
Benjamin Eberlei
3902a4eb6e [GH-8458] Properly deprecate ConvertDoctrine1Schema (#8517) 2021-03-02 09:38:09 +01:00
Jakub Caban
b3ed525d4d Use typed properties for default metadata for #7939 (#8439)
[GH-7939] Detect column and association types from typed properties.

* Use typed properties for default metadata for #7939

* Coding Standards

* Remove $name from CmsUserTypes and adapt tests

* Factor out conditions required for typed property

* Factor out typed validation and completion methods

* Move Typed tests model to separate namespace

* Don't pass by reference, return array

* Document changes to default mapping for typed properties

* Better wording in annotation reference

* Add missing targetEntity assertion on typed association

* Try to comply with CS

* USe constants instead of strings

* Use one-line comments for single line content

* phpcs

* phpcs

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2021-03-01 22:13:58 +01:00
Benjamin Eberlei
4cdc6b1a71 [GH-7128] Improve OneToManyRequiresMappedBy Exception message (#8380) 2021-03-01 21:42:57 +01:00
plfort
38ccbd8638 DDC-2076 - MEMBER OF - Remove useless join over target table of ManyToMany relationship (#8438)
Co-authored-by: Pierre-Louis FORT <pierre-louis.fort@theia.fr>
2021-02-28 23:37:40 +01:00
Benjamin Eberlei
b4e6530d2d [GH-8471] Deprecate Partial DQL syntax and forcing partial loads. (#8472)
* [GH-8471] Deprecate Partial DQL syntax and forcing partial loads.
2021-02-28 18:17:12 +01:00
Grégoire Paris
0ae53a6703 Merge pull request #8485 from doctrine/2.8.x-merge-up-into-2.9.x_602d59a0aa86b9.63081052
Merge release 2.8.2 into 2.9.x
2021-02-17 20:50:44 +01:00
Grégoire Paris
49864a7f57 Merge remote-tracking branch 'origin/2.8.x' into 2.8.x-merge-up-into-2.9.x_602d59a0aa86b9.63081052 2021-02-17 20:39:28 +01:00
Benjamin Eberlei
e2e59e94f5 [GH-8383] deprecate notify change tracking policy (#8473)
* [GH-8383] Deprecate notify change tracking policy.

* [GH-8383] Add warning to documentation about notify change tracking policy deprecation.
2021-02-13 23:14:28 +01:00
Benjamin Eberlei
6fe388a705 Introduce doctrine/deprecations (#8466)
* Introduce doctrine/deprecations, empty out VerifyDeprecations trait for now.

* Replace more usages of VerifyDeprecations (akwardkly for now)

* Update doctrine/deprecations to v0.2.0

* Remove ORM VerifyDeprecations trait and its use.

* Use doctrine/deprecatios VerifyDeprecations trait where useful

* Housekeeping: phpcs

* Fix reference link for toIterable/iterate deprecation
2021-02-12 18:14:34 +01:00
Vincent Langlet
172a8d9414 Restrict EntityManagerInterface::getRepository (#8417) 2021-02-12 17:44:22 +01:00
Grégoire Paris
d00dbf7e2d Merge pull request #8357 from snapshotpl/add-psalm-annotation
Add psalm annotation to ArrayCollection of Parameters
2021-02-08 21:25:58 +01:00
Witold Wasiczko
17012f1fea Improve psalm types 2021-02-08 21:16:56 +01:00
Witold Wasiczko
324ac3972f Add psalm annotation for parameters 2021-02-08 21:16:25 +01:00
Benjamin Eberlei
323469cdb7 Add /*.phpunit.xml to .gitignore 2021-02-07 19:38:52 +01:00
andrews05
835030297a Add support for INDEX BY an associated entity (2.9.x) (#7918)
* Add support for INDEX BY an associated entity

This allows specifying an association in the INDEX BY clause of a query
which will index by the association's join column.

Related to #7661.

* Reintroduce IndexBy#simpleStateFieldPathExpression as deprecated property.

* Housekeeping: phpcs

* Housekeeping: phpcs

Co-authored-by: Benjamin Eberlei <kontakt@beberlei.de>
2021-02-06 11:46:18 +01:00
Grégoire Paris
4cc78d9478 Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-02-05 23:40:59 +01:00
Grégoire Paris
68d24288ce Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-02-05 20:06:04 +01:00
Grégoire Paris
5abad7c0af Merge remote-tracking branch 'origin/2.8.x' into 2.9.x 2021-01-31 00:01:45 +01:00
orklah
f0ad5f72b2 bump psalm and fix some issues on level 6 (#8409) 2021-01-08 20:30:15 +01:00
Benjamin Eberlei
cbc252f3b7 Add Doctrine\ORM\Query\Expr::mod() (#8377)
* Add Doctrine\ORM\Query\Expr::mod()

Co-authored-by: Menno Holtkamp <menno.holtkamp@shopforce.nl>

* [GH-6739] Add entry to documentation

Co-authored-by: Menno Holtkamp <menno.holtkamp@shopforce.nl>
2020-12-06 23:09:20 +01:00
333 changed files with 13247 additions and 2192 deletions

View File

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

5
.gitattributes vendored
View File

@@ -2,10 +2,9 @@
/tools export-ignore
/docs export-ignore
/.github export-ignore
.doctrine-project.json export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.gitmodules export-ignore
.travis.yml export-ignore
build.properties export-ignore
build.properties.dev export-ignore
build.xml export-ignore
@@ -15,4 +14,6 @@ run-all.sh export-ignore
phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
psalm.xml export-ignore
psalm-baseline.xml export-ignore

View File

@@ -19,11 +19,6 @@ jobs:
- "7.3"
- "7.4"
- "8.0"
deps:
- "highest"
include:
- deps: "lowest"
php-version: "7.3"
steps:
- name: "Checkout"
@@ -41,8 +36,6 @@ jobs:
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
with:
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage-no-cache.xml"
@@ -57,7 +50,7 @@ jobs:
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
with:
name: "phpunit-sqlite-${{ matrix.deps }}-${{ matrix.php-version }}-coverage"
name: "phpunit-sqlite-${{ matrix.php-version }}-coverage"
path: "coverage*.xml"
@@ -228,6 +221,38 @@ jobs:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-coverage"
path: "coverage*.xml"
phpunit-lower-php-versions:
name: "PHPUnit with SQLite"
runs-on: "ubuntu-20.04"
strategy:
matrix:
php-version:
- "7.1"
deps:
- "highest"
- "lowest"
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
with:
fetch-depth: 2
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
ini-values: "zend.assertions=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
with:
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml"
upload_coverage:
name: "Upload coverage to Codecov"
runs-on: "ubuntu-20.04"

View File

@@ -1,12 +1,18 @@
name: Static Analysis
name: "Static Analysis"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
jobs:
static-analysis-phpstan:
name: "PHPStan"
runs-on: "ubuntu-latest"
name: "Static Analysis with PHPStan"
runs-on: "ubuntu-20.04"
strategy:
matrix:
@@ -22,17 +28,18 @@ jobs:
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
tools: cs2pr
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
with:
dependency-versions: "highest"
- name: "Run a static analysis with phpstan/phpstan"
run: "php vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr"
run: "vendor/bin/phpstan analyse"
static-analysis-psalm:
name: "Psalm"
runs-on: "ubuntu-latest"
name: "Static Analysis with Psalm"
runs-on: "ubuntu-20.04"
strategy:
matrix:
@@ -51,6 +58,8 @@ jobs:
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
with:
dependency-versions: "highest"
- name: "Run a static analysis with vimeo/psalm"
run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)"

1
.gitignore vendored
View File

@@ -16,3 +16,4 @@ vendor/
/.phpcs-cache
composer.lock
/.phpunit.result.cache
/*.phpunit.xml

View File

@@ -1,3 +1,31 @@
# Upgrade to 2.9
## Minor BC BREAK: Setup tool needs cache implementation
With the deprecation of doctrine/cache, the setup tool might no longer work as expected without a different cache
implementation. To work around this:
* Install symfony/cache: `composer require symfony/cache`. This will keep previous behaviour without any changes
* Instantiate caches yourself: to use a different cache implementation, pass a cache instance when calling any
configuration factory in the setup tool:
```diff
- $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir);
+ $cache = \Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap($anyPsr6Implementation);
+ $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir, $cache);
```
* As a quick workaround, you can lock the doctrine/cache dependency to work around this: `composer require doctrine/cache ^1.11`.
Note that this is only recommended as a bandaid fix, as future versions of ORM will no longer work with doctrine/cache
1.11.
## Deprecated: doctrine/cache for metadata caching
The `Doctrine\ORM\Configuration#setMetadataCacheImpl()` method is deprecated and should no longer be used. Please use
`Doctrine\ORM\Configuration#setMetadataCache()` with any PSR-6 cache adapter instead.
## Removed: flushing metadata cache
To support PSR-6 caches, the `--flush` option for the `orm:clear-cache:metadata` command is ignored. Metadata cache is
now always cleared regardless of the cache adapter being used.
# Upgrade to 2.8
## Minor BC BREAK: Failed commit now throw OptimisticLockException

View File

@@ -16,29 +16,34 @@
"sort-packages": true
},
"require": {
"php": "^7.2|^8.0",
"php": "^7.1|^8.0",
"ext-pdo": "*",
"composer/package-versions-deprecated": "^1.8",
"doctrine/annotations": "^1.11.1",
"doctrine/cache": "^1.9.1",
"doctrine/annotations": "^1.13",
"doctrine/cache": "^1.11.3|^2.0.3",
"doctrine/collections": "^1.5",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.10.0",
"doctrine/dbal": "^2.13.0",
"doctrine/deprecations": "^0.5.3",
"doctrine/event-manager": "^1.1",
"doctrine/inflector": "^1.4|^2.0",
"doctrine/instantiator": "^1.3",
"doctrine/lexer": "^1.0",
"doctrine/persistence": "^2.0",
"symfony/console": "^3.0|^4.0|^5.0"
"doctrine/persistence": "^2.2",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^3.0|^4.0|^5.0|^6.0"
},
"require-dev": {
"doctrine/coding-standard": "^8.0",
"phpstan/phpstan": "^0.12.18",
"phpunit/phpunit": "^8.5|^9.4",
"symfony/yaml": "^3.4|^4.0|^5.0",
"vimeo/psalm": "4.1.1"
"doctrine/coding-standard": "^9.0",
"phpstan/phpstan": "^0.12.83",
"phpunit/phpunit": "^7.5|^8.5|^9.4",
"squizlabs/php_codesniffer": "3.6.0",
"symfony/cache": "^4.4|^5.2",
"symfony/yaml": "^3.4|^4.0|^5.0|^6.0",
"vimeo/psalm": "4.7.0"
},
"suggest": {
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0",
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {

View File

@@ -10,6 +10,11 @@ code should look like. We will implement it on a
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
for all our domain objects.
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
Implementing NotifyPropertyChanged
----------------------------------

View File

@@ -101,10 +101,11 @@ Gets or sets the metadata driver implementation that is used by
Doctrine to acquire the object-relational metadata for your
classes.
There are currently 4 available implementations:
There are currently 5 available implementations:
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
- ``Doctrine\ORM\Mapping\Driver\DriverChain``

View File

@@ -89,7 +89,7 @@ as part of the lifecycle of the instance variables entity-class.
Required attributes:
- **type**: Name of the Doctrine Type which is converted between PHP
and Database representation.
and Database representation. Default to ``string`` or :ref:`Type from PHP property type <reference-php-mapping-types>`
Optional attributes:
@@ -350,7 +350,7 @@ in order to specify that it is an embedded class.
Required attributes:
- **class**: The embeddable class
- **class**: The embeddable class. You can omit this value if you use a PHP property type instead.
.. code-block:: php
@@ -513,7 +513,8 @@ Required attributes:
- **name**: Name of the Index
- **columns**: Array of columns.
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
Optional attributes:
@@ -535,6 +536,19 @@ Basic example:
{
}
Basic example using fields:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",indexes={@Index(name="search_idx", fields={"name", "email"})})
*/
class ECommerceProduct
{
}
Example with partial indexes:
.. code-block:: php
@@ -715,6 +729,7 @@ Required attributes:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
You can omit this value if you use a PHP property type instead.
*IMPORTANT:* No leading backslash!
Optional attributes:
@@ -840,6 +855,11 @@ Example:
@NamedNativeQuery
~~~~~~~~~~~~~~~~~
.. note::
Named Native Queries are deprecated as of version 2.9 and will be removed in ORM 3.0
Is used to specify a native SQL named query.
The NamedNativeQuery annotation can be applied to an entity or mapped superclass.
@@ -923,6 +943,7 @@ Required attributes:
- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
When typed properties are used it is inherited from PHP type.
*IMPORTANT:* No leading backslash!
Optional attributes:
@@ -1263,7 +1284,8 @@ Required attributes:
- **name**: Name of the Index
- **columns**: Array of columns.
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
Optional attributes:
@@ -1285,6 +1307,19 @@ Basic example:
{
}
Basic example using fields:
.. code-block:: php
<?php
/**
* @Entity
* @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", fields={"name", "email"})})
*/
class ECommerceProduct
{
}
Example with partial indexes:
.. code-block:: php

View File

@@ -22,9 +22,9 @@ One tip for working with relations is to read the relation from left to right, w
- ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity.
- OneToOne - One instance of the current Entity refers to One instance of the referred Entity.
See below for all the possible relations.
See below for all the possible relations.
An association is considered to be unidirectional if only one side of the association has
An association is considered to be unidirectional if only one side of the association has
a property referring to the other side.
To gain a full understanding of associations you should also read about :doc:`owning and
@@ -1061,6 +1061,70 @@ join columns default to the simple, unqualified class name of the
targeted class followed by "\_id". The referencedColumnName always
defaults to "id", just as in one-to-one or many-to-one mappings.
Additionally, when using typed properties with Doctrine 2.9 or newer
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
associations as they will be set based on type. Also ``nullable``
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
.. configuration-block::
.. code-block:: php
<?php
/** @OneToOne */
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" />
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToOne:
shipment: ~
Is essentially the same as following:
.. configuration-block::
.. code-block:: php
<?php
/**
* One Product has One Shipment.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
*/
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
<entity class="Product">
<one-to-one field="shipment" target-entity="Shipment">
<join-column name="shipment_id" referenced-column-name="id" nulable=false />
</one-to-one>
</entity>
</doctrine-mapping>
.. code-block:: yaml
Product:
type: entity
oneToOne:
shipment:
targetEntity: Shipment
joinColumn:
name: shipment_id
referencedColumnName: id
nullable: false
If you accept these defaults, you can reduce the mapping code to a
minimum.

File diff suppressed because it is too large Load Diff

View File

@@ -211,6 +211,25 @@ list:
- ``options``: (optional) Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
.. _reference-php-mapping-types:
PHP Types Mapping
_________________
Since version 2.9 Doctrine can determine usable defaults from property types
on entity classes. When property type is nullable the default for ``nullable``
Column attribute is set to TRUE. Additionally, Doctrine will map PHP types
to ``type`` attribute as follows:
- ``DateInterval``: ``dateinterval``
- ``DateTime``: ``datetime``
- ``DateTimeImmutable``: ``datetime_immutable``
- ``array``: ``json``
- ``bool``: ``boolean``
- ``float``: ``float``
- ``int``: ``integer``
- ``string`` or any other type: ``string``
.. _reference-mapping-types:
Doctrine Mapping Types
@@ -328,7 +347,7 @@ annotation.
In most cases using the automatic generator strategy (``@GeneratedValue``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
and Oracle and so on.
Identifier Generation Strategies

View File

@@ -89,11 +89,11 @@ with the batching strategy that was already used for bulk inserts:
foreach ($q->toIterable() as $user) {
$user->increaseCredit();
$user->calculateNewBonuses();
++$i;
if (($i % $batchSize) === 0) {
$em->flush(); // Executes all updates.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
$em->flush();
@@ -147,11 +147,11 @@ The following example shows how to do this:
$q = $em->createQuery('select u from MyProject\Model\User u');
foreach($q->toIterable() as $row) {
$em->remove($row);
++$i;
if (($i % $batchSize) === 0) {
$em->flush(); // Executes all deletions.
$em->clear(); // Detaches all objects from Doctrine!
}
++$i;
}
$em->flush();

View File

@@ -61,6 +61,11 @@ This policy can be configured as follows:
Notify
~~~~~~
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
This policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that
purpose, a class that wants to use this policy needs to implement

View File

@@ -603,6 +603,13 @@ then phonenumber-id:
...
'nameUpper' => string 'JWAGE' (length=5)
You can also index by a to-one association, which will use the id of
the associated entity (the join column) as the key in the result set:
.. code-block:: sql
SELECT p, u FROM Participant INDEX BY p.user JOIN p.user u WHERE p.event = 3
UPDATE queries
--------------
@@ -1370,7 +1377,8 @@ userland:
that contain char or binary data. Doctrine has no way of implicitly
reloading this data. Partially loaded objects have to be passed to
``EntityManager::refresh()`` if they are to be reloaded fully from
the database.
the database. This query hint is deprecated and will be removed
in the future (`Details <https://github.com/doctrine/orm/issues/8471>`_)
- Query::HINT\_REFRESH - This query is used internally by
``EntityManager::refresh()`` and can be used in userland as well.
If you specify this hint and a query returns the data for an entity
@@ -1615,7 +1623,7 @@ From, Join and Index by
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
IndexBy ::= "INDEX" "BY" StateFieldPathExpression
IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
Select Expressions
~~~~~~~~~~~~~~~~~~

View File

@@ -53,6 +53,9 @@ proper quoting of parameters.
}
}
If the parameter is an array and should be quoted as a list of values for an IN query
this is possible with the alternative ``SQLFilter#setParameterList()`` and
``SQLFilter#getParameterList()`` functions.
Configuration
-------------

View File

@@ -44,13 +44,35 @@ Read-Only Entities
------------------
You can mark entities as read only (See metadata mapping
references for details). This means that the entity marked as read only is never considered
for updates, which means when you call flush on the EntityManager these entities are skipped
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
ones, they are just not considered for updates.
references for details).
This means that the entity marked as read only is never considered for updates.
During flush on the EntityManager these entities are skipped even if properties
changed.
Read-Only allows to persist new entities of a kind and remove existing ones,
they are just not considered for updates.
See :ref:`annref_entity`
You can also explicitly mark individual entities read only directly on the
UnitOfWork via a call to ``markReadOnly()``:
.. code-block:: php
$user = $entityManager->find(User::class, $id);
$entityManager->getUnitOfWork()->markReadOnly($user);
Or you can set all objects that are the result of a query hydration to be
marked as read only with the following query hint:
.. code-block:: php
$query = $entityManager->createQuery('SELECT u FROM App\\Entity\\User u');
$query->setHint(Query::HINT_READ_ONLY, true);
$users = $query->getResult();
Extra-Lazy Collections
----------------------

View File

@@ -274,6 +274,9 @@ be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
There is also another important performance consideration that it is *NOT POSSIBLE*
to query for the base entity without any LEFT JOINs to the sub-types.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -14,6 +14,7 @@ metadata:
- **XML files** (XmlDriver)
- **Class DocBlock Annotations** (AnnotationDriver)
- **Attributes** (AttributeDriver)
- **YAML files** (YamlDriver)
- **PHP Code in files or static functions** (PhpDriver)

View File

@@ -71,7 +71,7 @@ with inheritance hierarchies.
use Doctrine\ORM\Query\ResultSetMappingBuilder;
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
"FROM users u INNER JOIN address a ON u.address_id = a.id";
$rsm = new ResultSetMappingBuilder($entityManager);
@@ -265,7 +265,7 @@ detail:
<?php
/**
* Adds a meta column (foreign key or discriminator column) to the result set.
*
*
* @param string $alias
* @param string $columnAlias
* @param string $columnName
@@ -320,10 +320,10 @@ entity.
$rsm->addEntityResult('User', 'u');
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
The result would look like this:
@@ -356,10 +356,10 @@ thus owns the foreign key.
$rsm->addFieldResult('u', 'id', 'id');
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'address_id', 'address_id');
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Foreign keys are used by Doctrine for lazy-loading purposes when
@@ -385,12 +385,12 @@ associations that are lazy.
$rsm->addFieldResult('a', 'address_id', 'id');
$rsm->addFieldResult('a', 'street', 'street');
$rsm->addFieldResult('a', 'city', 'city');
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
$query = $this->_em->createNativeQuery($sql, $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
In this case the nested entity ``Address`` is registered with the
@@ -420,10 +420,10 @@ to map the hierarchy (both use a discriminator column).
$rsm->addFieldResult('u', 'name', 'name');
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
$rsm->setDiscriminatorColumn('u', 'discr');
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Note that in the case of Class Table Inheritance, an example as
@@ -435,6 +435,10 @@ strategy but with native SQL it is your responsibility.
Named Native Query
------------------
.. note::
Named Native Queries are deprecated as of version 2.9 and will be removed in ORM 3.0
You can also map a native query using a named native query mapping.
To achieve that, you must describe the SQL resultset structure
@@ -789,7 +793,7 @@ followed by a dot ("."), followed by the name or the field or property of the pr
6:
name: address.country
column: a_country
If you retrieve a single entity and if you use the default mapping,

View File

@@ -1,6 +1,14 @@
Partial Objects
===============
.. note::
Creating Partial Objects through DQL is deprecated and
will be removed in the future, use data transfer object
support in DQL instead. (`Details
<https://github.com/doctrine/orm/issues/8471>`_)
A partial object is an object whose state is not fully initialized
after being reconstituted from the database and that is
disconnected from the rest of its data. The following section will

View File

@@ -520,6 +520,9 @@ complete list of supported helper methods available:
// Example - $qb->expr()->sqrt('u.currentBalance')
public function sqrt($x); // Returns Expr\Func
// Example - $qb->expr()->mod('u.currentBalance', '10')
public function mod($x); // Returns Expr\Func
// Example - $qb->expr()->count('u.firstname')
public function count($x); // Returns Expr\Func

View File

@@ -134,6 +134,10 @@ optimize the performance of the Flush Operation:
explicit strategies of notifying the UnitOfWork what objects/properties
changed.
.. note::
Flush only a single entity with ``$entityManager->flush($entity)`` is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8459>`_)
Query Internals
---------------

View File

@@ -309,7 +309,7 @@
<xs:complexType name="embedded">
<xs:attribute name="name" type="xs:string" use="required" />
<xs:attribute name="class" type="orm:fqcn" use="required" />
<xs:attribute name="class" type="orm:fqcn" use="optional" />
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
</xs:complexType>
@@ -332,7 +332,8 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="columns" type="xs:string" use="optional"/>
<xs:attribute name="fields" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -351,6 +352,7 @@
</xs:sequence>
<xs:attribute name="name" type="xs:NMTOKEN" use="optional"/>
<xs:attribute name="columns" type="xs:string" use="required"/>
<xs:attribute name="fields" type="xs:string" use="optional"/>
<xs:attribute name="flags" type="xs:string" use="optional"/>
<xs:anyAttribute namespace="##other"/>
</xs:complexType>
@@ -539,7 +541,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="target-entity" type="xs:string" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
<xs:anyAttribute namespace="##other"/>
@@ -557,7 +559,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:sequence>
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="target-entity" type="xs:string" use="required" />
<xs:attribute name="target-entity" type="xs:string" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />

View File

@@ -24,7 +24,8 @@ use Countable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Cache\QueryCacheProfile;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\Logging\CacheLogger;
use Doctrine\ORM\Cache\QueryCacheKey;
use Doctrine\ORM\Cache\TimestampCacheKey;
@@ -49,9 +50,6 @@ use function ksort;
use function reset;
use function serialize;
use function sha1;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Base contract for ORM queries. Base class for Query and NativeQuery.
@@ -123,7 +121,7 @@ abstract class AbstractQuery
*/
protected $_hydrationMode = self::HYDRATE_OBJECT;
/** @var QueryCacheProfile */
/** @var QueryCacheProfile|null */
protected $_queryCacheProfile;
/**
@@ -289,7 +287,7 @@ abstract class AbstractQuery
/**
* Retrieves the associated EntityManager of this Query instance.
*
* @return EntityManager
* @return EntityManagerInterface
*/
public function getEntityManager()
{
@@ -314,6 +312,7 @@ abstract class AbstractQuery
* Get all defined parameters.
*
* @return ArrayCollection The defined query parameters.
* @psalm-return ArrayCollection<int, Parameter>
*/
public function getParameters()
{
@@ -325,7 +324,7 @@ abstract class AbstractQuery
*
* @param mixed $key The key (index or name) of the bound parameter.
*
* @return Query\Parameter|null The value of the bound parameter, or NULL if not available.
* @return Parameter|null The value of the bound parameter, or NULL if not available.
*/
public function getParameter($key)
{
@@ -346,10 +345,9 @@ abstract class AbstractQuery
* Sets a collection of query parameters.
*
* @param ArrayCollection|mixed[] $parameters
* @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
*
* @return static This query instance.
*
* @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
*/
public function setParameters($parameters)
{
@@ -402,10 +400,9 @@ abstract class AbstractQuery
* @param mixed $value
*
* @return mixed[]|string|int|float|bool
* @psalm-return array|scalar
*
* @throws ORMInvalidArgumentException
*
* @psalm-return array|scalar
*/
public function processParameterValue($value)
{
@@ -509,10 +506,8 @@ abstract class AbstractQuery
/**
* Allows to translate entity namespaces to full qualified names.
*
* @return void
*/
private function translateNamespaces(Query\ResultSetMapping $rsm)
private function translateNamespaces(Query\ResultSetMapping $rsm): void
{
$translate = function ($alias): string {
return $this->_em->getClassMetadata($alias)->getName();
@@ -593,6 +588,7 @@ abstract class AbstractQuery
*/
public function setResultCacheDriver($resultCacheDriver = null)
{
/** @phpstan-ignore-next-line */
if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
throw ORMException::invalidResultCacheDriver();
}
@@ -723,7 +719,7 @@ abstract class AbstractQuery
}
/**
* @return QueryCacheProfile
* @return QueryCacheProfile|null
*/
public function getQueryCacheProfile()
{
@@ -949,7 +945,7 @@ abstract class AbstractQuery
* Executes the query and returns an IterableResult that can be used to incrementally
* iterate over the result.
*
* @deprecated
* @deprecated 2.8 Use {@see toIterable} instead. See https://github.com/doctrine/orm/issues/8463
*
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
@@ -958,9 +954,11 @@ abstract class AbstractQuery
*/
public function iterate($parameters = null, $hydrationMode = null)
{
@trigger_error(
'Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
E_USER_DEPRECATED
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8463',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
__METHOD__
);
if ($hydrationMode !== null) {
@@ -981,8 +979,9 @@ abstract class AbstractQuery
* Executes the query and returns an iterable that can be used to incrementally
* iterate over the result.
*
* @param ArrayCollection|mixed[] $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @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
*
* @return iterable<mixed>
*/
@@ -1015,6 +1014,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
*
* @return mixed
*/
@@ -1032,6 +1032,7 @@ abstract class AbstractQuery
*
* @param ArrayCollection|mixed[]|null $parameters
* @param string|int|null $hydrationMode
* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
*
* @return mixed
*/
@@ -1045,7 +1046,7 @@ abstract class AbstractQuery
$this->setParameters($parameters);
}
$setCacheEntry = static function (): void {
$setCacheEntry = static function ($data): void {
};
if ($this->_hydrationCacheProfile !== null) {
@@ -1091,6 +1092,7 @@ abstract class AbstractQuery
*
* @param ArrayCollection|mixed[]|null $parameters
* @param string|int|null $hydrationMode
* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
*
* @return mixed
*/
@@ -1129,10 +1131,7 @@ abstract class AbstractQuery
return $result;
}
/**
* @return TimestampCacheKey|null
*/
private function getTimestampKey()
private function getTimestampKey(): ?TimestampCacheKey
{
$entityName = reset($this->_resultSetMapping->aliasMap);
@@ -1203,7 +1202,9 @@ abstract class AbstractQuery
/**
* Executes the query and returns a the resulting Statement object.
*
* @return Statement The executed database statement that holds the results.
* @return ResultStatement|int The executed database statement that holds
* the results, or an integer indicating how
* many rows were affected.
*/
abstract protected function _doExecute();

View File

@@ -63,6 +63,9 @@ class CacheConfiguration
return $this->cacheLogger;
}
/**
* @return void
*/
public function setCacheLogger(CacheLogger $logger)
{
$this->cacheLogger = $logger;
@@ -80,6 +83,9 @@ class CacheConfiguration
return $this->regionsConfig;
}
/**
* @return void
*/
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
{
$this->regionsConfig = $regionsConfig;
@@ -99,6 +105,9 @@ class CacheConfiguration
return $this->queryValidator;
}
/**
* @return void
*/
public function setQueryValidator(QueryCacheValidator $validator)
{
$this->queryValidator = $validator;

View File

@@ -30,9 +30,9 @@ use Doctrine\ORM\PersistentCollection;
interface CollectionHydrator
{
/**
* @param ClassMetadata $metadata The entity metadata.
* @param CollectionCacheKey $key The cached collection key.
* @param mixed[]|Collection $collection The collection.
* @param ClassMetadata $metadata The entity metadata.
* @param CollectionCacheKey $key The cached collection key.
* @param array|mixed[]|Collection $collection The collection.
*
* @return CollectionCacheEntry
*/

View File

@@ -48,7 +48,7 @@ class DefaultCache implements Cache
/** @var QueryCache[] */
private $queryCaches = [];
/** @var QueryCache */
/** @var QueryCache|null */
private $defaultQueryCache;
public function __construct(EntityManagerInterface $em)
@@ -278,10 +278,8 @@ class DefaultCache implements Cache
/**
* @param ClassMetadata $metadata The entity metadata.
* @param mixed $identifier The entity identifier.
*
* @return EntityCacheKey
*/
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier)
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier): EntityCacheKey
{
if (! is_array($identifier)) {
$identifier = $this->toIdentifierArray($metadata, $identifier);
@@ -294,11 +292,12 @@ class DefaultCache implements Cache
* @param ClassMetadata $metadata The entity metadata.
* @param string $association The field name that represents the association.
* @param mixed $ownerIdentifier The identifier of the owning entity.
*
* @return CollectionCacheKey
*/
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
{
private function buildCollectionCacheKey(
ClassMetadata $metadata,
string $association,
$ownerIdentifier
): CollectionCacheKey {
if (! is_array($ownerIdentifier)) {
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
}
@@ -312,7 +311,7 @@ class DefaultCache implements Cache
*
* @return array<string, mixed>
*/
private function toIdentifierArray(ClassMetadata $metadata, $identifier)
private function toIdentifierArray(ClassMetadata $metadata, $identifier): array
{
if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) {
$identifier = $this->uow->getSingleIdentifierValue($identifier);

View File

@@ -70,6 +70,8 @@ class DefaultCacheFactory implements CacheFactory
/**
* @param string $fileLockRegionDirectory
*
* @return void
*/
public function setFileLockRegionDirectory($fileLockRegionDirectory)
{
@@ -84,11 +86,17 @@ class DefaultCacheFactory implements CacheFactory
return $this->fileLockRegionDirectory;
}
/**
* @return void
*/
public function setRegion(Region $region)
{
$this->regions[$region->getName()] = $region;
}
/**
* @return void
*/
public function setTimestampRegion(TimestampRegion $region)
{
$this->timestampRegion = $region;
@@ -111,6 +119,10 @@ class DefaultCacheFactory implements CacheFactory
}
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
if (! $region instanceof ConcurrentRegion) {
throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage));
}
return new ReadWriteCachedEntityPersister($persister, $region, $em, $metadata);
}
@@ -134,6 +146,10 @@ class DefaultCacheFactory implements CacheFactory
}
if ($usage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
if (! $region instanceof ConcurrentRegion) {
throw new InvalidArgumentException(sprintf('Unable to use access strategy type of [%s] without a ConcurrentRegion', $usage));
}
return new ReadWriteCachedCollectionPersister($persister, $region, $em, $mapping);
}
@@ -201,18 +217,13 @@ class DefaultCacheFactory implements CacheFactory
}
$directory = $this->fileLockRegionDirectory . DIRECTORY_SEPARATOR . $cache['region'];
$region = new FileLockRegion($region, $directory, $this->regionsConfig->getLockLifetime($cache['region']));
$region = new FileLockRegion($region, $directory, (string) $this->regionsConfig->getLockLifetime($cache['region']));
}
return $this->regions[$cache['region']] = $region;
}
/**
* @param string $name
*
* @return CacheAdapter
*/
private function createRegionCache($name)
private function createRegionCache(string $name): CacheAdapter
{
$cacheAdapter = clone $this->cache;

View File

@@ -345,10 +345,9 @@ class DefaultQueryCache implements QueryCache
* @param mixed $assocValue
*
* @return mixed[]|null
*
* @psalm-return array{targetEntity: string, type: mixed, list?: array[], identifier?: array}|null
* @psalm-return array{targetEntity: class-string, type: mixed, list?: array[], identifier?: array}|null
*/
private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue): ?array
{
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
$assocMetadata = $assocPersister->getClassMetadata();
@@ -398,13 +397,15 @@ class DefaultQueryCache implements QueryCache
}
/**
* @param string $assocAlias
* @param object $entity
*
* @return array<object>|object
*/
private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
{
private function getAssociationValue(
ResultSetMapping $rsm,
string $assocAlias,
$entity
) {
$path = [];
$alias = $assocAlias;
@@ -441,7 +442,7 @@ class DefaultQueryCache implements QueryCache
return null;
}
if (empty($path)) {
if ($path === []) {
return $value;
}

View File

@@ -40,12 +40,14 @@ class EntityCacheEntry implements CacheEntry
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
*
* @var string The entity class name
* @psalm-var class-string
*/
public $class;
/**
* @param string $class The entity class.
* @param array<string,mixed> $data The entity data.
* @psalm-param class-string $class
*/
public function __construct($class, array $data)
{

View File

@@ -34,6 +34,8 @@ class CacheLoggerChain implements CacheLogger
/**
* @param string $name
*
* @return void
*/
public function setLogger($name, CacheLogger $logger)
{

View File

@@ -194,6 +194,8 @@ class StatisticsCacheLogger implements CacheLogger
* Clear region statistics
*
* @param string $regionName The name of the cache region.
*
* @return void
*/
public function clearRegionStats($regionName)
{
@@ -204,6 +206,8 @@ class StatisticsCacheLogger implements CacheLogger
/**
* Clear all statistics
*
* @return void
*/
public function clearStats()
{

View File

@@ -72,7 +72,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/** @var CollectionHydrator */
protected $hydrator;
/** @var CacheLogger */
/** @var CacheLogger|null */
protected $cacheLogger;
/**
@@ -239,6 +239,8 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* Clears cache entries related to the current collection
*
* @return void
*/
protected function evictCollectionCache(PersistentCollection $collection)
{
@@ -258,6 +260,8 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
/**
* @param string $targetEntity
* @param object $element
*
* @return void
*/
protected function evictElementCache($targetEntity, $element)
{

View File

@@ -52,7 +52,7 @@ interface CachedCollectionPersister extends CachedPersister, CollectionPersister
/**
* Stores a collection into cache
*
* @param mixed[]|Collection $elements
* @param array|mixed[]|Collection $elements
*
* @return void
*/

View File

@@ -75,7 +75,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/** @var Cache */
protected $cache;
/** @var CacheLogger */
/** @var CacheLogger|null */
protected $cacheLogger;
/** @var string */
@@ -226,7 +226,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* @param object $entity
*/
private function storeJoinedAssociations($entity)
private function storeJoinedAssociations($entity): void
{
if ($this->joinedAssociations === null) {
$associations = [];
@@ -344,7 +344,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = null)
{
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== null) {
if ($entity !== null || $assoc !== null || $hints !== [] || $lockMode !== null) {
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
}

View File

@@ -100,17 +100,14 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
/**
* @param object $entity
* @param bool $isChanged
*
* @return bool
*/
private function updateCache($entity, $isChanged)
private function updateCache($entity, bool $isChanged): bool
{
$class = $this->metadataFactory->getMetadataFor(get_class($entity));
$key = new EntityCacheKey($class->rootEntityName, $this->uow->getEntityIdentifier($entity));
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
$cached = $this->region->put($key, $entry);
$isChanged = $isChanged ?: $cached;
$isChanged = $isChanged || $cached;
if ($this->cacheLogger && $cached) {
$this->cacheLogger->entityCachePut($this->regionName, $key);

View File

@@ -129,6 +129,8 @@ class DefaultRegion implements Region
/**
* {@inheritdoc}
*
* @return bool
*/
public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null)
{
@@ -137,6 +139,8 @@ class DefaultRegion implements Region
/**
* {@inheritdoc}
*
* @return bool
*/
public function evict(CacheKey $key)
{
@@ -145,6 +149,8 @@ class DefaultRegion implements Region
/**
* {@inheritdoc}
*
* @return bool
*/
public function evictAll()
{

View File

@@ -63,8 +63,8 @@ class FileLockRegion implements ConcurrentRegion
private $lockLifetime;
/**
* @param string $directory
* @param string $lockLifetime
* @param string $directory
* @param numeric-string $lockLifetime
*
* @throws InvalidArgumentException
*/
@@ -83,10 +83,7 @@ class FileLockRegion implements ConcurrentRegion
$this->lockLifetime = $lockLifetime;
}
/**
* @return bool
*/
private function isLocked(CacheKey $key, ?Lock $lock = null)
private function isLocked(CacheKey $key, ?Lock $lock = null): bool
{
$filename = $this->getLockFileName($key);
@@ -117,30 +114,23 @@ class FileLockRegion implements ConcurrentRegion
return true;
}
/**
* @return string
*/
private function getLockFileName(CacheKey $key)
private function getLockFileName(CacheKey $key): string
{
return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
}
/**
* @param string $filename
*
* @return string
* @return string|false
*/
private function getLockContent($filename)
private function getLockContent(string $filename)
{
return @file_get_contents($filename);
}
/**
* @param string $filename
*
* @return int
* @return int|false
*/
private function getLockTime($filename)
private function getLockTime(string $filename)
{
return @fileatime($filename);
}

View File

@@ -57,6 +57,8 @@ class RegionsConfiguration
/**
* @param int $defaultLifetime
*
* @return void
*/
public function setDefaultLifetime($defaultLifetime)
{
@@ -73,6 +75,8 @@ class RegionsConfiguration
/**
* @param int $defaultLockLifetime
*
* @return void
*/
public function setDefaultLockLifetime($defaultLockLifetime)
{
@@ -92,6 +96,8 @@ class RegionsConfiguration
/**
* @param string $name
* @param int $lifetime
*
* @return void
*/
public function setLifetime($name, $lifetime)
{
@@ -111,6 +117,8 @@ class RegionsConfiguration
/**
* @param string $name
* @param int $lifetime
*
* @return void
*/
public function setLockLifetime($name, $lifetime)
{

View File

@@ -48,10 +48,7 @@ class TimestampQueryCacheValidator implements QueryCacheValidator
return $entry->time + $key->lifetime > microtime(true);
}
/**
* @return bool
*/
private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry)
private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry): bool
{
if ($key->timestampKey === null) {
return false;

View File

@@ -26,7 +26,10 @@ use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
@@ -41,8 +44,10 @@ use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\ObjectRepository;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;
use function class_exists;
use function strtolower;
use function trim;
@@ -151,10 +156,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported.
*
* @param bool $useSimpleAnnotationReader
* @psalm-param string|list<string> $paths
*
* @return AnnotationDriver
*
* @psalm-param string|list<string> $paths
*/
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
{
@@ -164,13 +168,16 @@ class Configuration extends \Doctrine\DBAL\Configuration
// Register the ORM Annotations in the AnnotationRegistry
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
$cachedReader = new CachedReader($reader, new ArrayCache());
} else {
$reader = new AnnotationReader();
}
return new AnnotationDriver($cachedReader, (array) $paths);
if (class_exists(ArrayCache::class)) {
$reader = new CachedReader($reader, new ArrayCache());
}
return new AnnotationDriver(
new CachedReader(new AnnotationReader(), new ArrayCache()),
$reader,
(array) $paths
);
}
@@ -209,9 +216,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the entity alias map.
*
* @return void
*
* @psalm-param array<string, string> $entityNamespaces
*
* @return void
*/
public function setEntityNamespaces(array $entityNamespaces)
{
@@ -283,21 +290,55 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the cache driver implementation that is used for metadata caching.
*
* @deprecated Deprecated in favor of getMetadataCache
*
* @return CacheDriver|null
*/
public function getMetadataCacheImpl()
{
return $this->_attributes['metadataCacheImpl'] ?? null;
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8650',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getMetadataCache() instead.',
__METHOD__
);
if (isset($this->_attributes['metadataCacheImpl'])) {
return $this->_attributes['metadataCacheImpl'];
}
return isset($this->_attributes['metadataCache']) ? DoctrineProvider::wrap($this->_attributes['metadataCache']) : null;
}
/**
* Sets the cache driver implementation that is used for metadata caching.
*
* @deprecated Deprecated in favor of setMetadataCache
*
* @return void
*/
public function setMetadataCacheImpl(CacheDriver $cacheImpl)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8650',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use setMetadataCache() instead.',
__METHOD__
);
$this->_attributes['metadataCacheImpl'] = $cacheImpl;
$this->_attributes['metadataCache'] = CacheAdapter::wrap($cacheImpl);
}
public function getMetadataCache(): ?CacheItemPoolInterface
{
return $this->_attributes['metadataCache'] ?? null;
}
public function setMetadataCache(CacheItemPoolInterface $cache): void
{
$this->_attributes['metadataCache'] = $cache;
$this->_attributes['metadataCacheImpl'] = DoctrineProvider::wrap($cache);
}
/**
@@ -350,13 +391,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name The name of the query.
*
* @throws ORMException
*
* @psalm-return array{string, ResultSetMapping} A tuple with the first
* element being the SQL
* string and the second
* element being the
* ResultSetMapping.
*
* @throws ORMException
*/
public function getNamedNativeQuery($name)
{
@@ -388,19 +429,19 @@ class Configuration extends \Doctrine\DBAL\Configuration
throw ORMException::queryCacheUsesNonPersistentCache($queryCacheImpl);
}
$metadataCacheImpl = $this->getMetadataCacheImpl();
if ($this->getAutoGenerateProxyClasses()) {
throw ORMException::proxyClassesAlwaysRegenerating();
}
if (! $metadataCacheImpl) {
if (! $this->getMetadataCache()) {
throw ORMException::metadataCacheNotConfigured();
}
$metadataCacheImpl = $this->getMetadataCacheImpl();
if ($metadataCacheImpl instanceof ArrayCache) {
throw ORMException::metadataCacheUsesNonPersistentCache($metadataCacheImpl);
}
if ($this->getAutoGenerateProxyClasses()) {
throw ORMException::proxyClassesAlwaysRegenerating();
}
}
/**
@@ -426,7 +467,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|null
*
* @psalm-return ?class-string
*/
public function getCustomStringFunction($name)
@@ -444,10 +484,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* Any previously added string functions are discarded.
*
* @return void
*
* @psalm-param array<string, class-string> $functions The map of custom
* DQL string functions.
*
* @return void
*/
public function setCustomStringFunctions(array $functions)
{
@@ -479,7 +519,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|null
*
* @psalm-return ?class-string
*/
public function getCustomNumericFunction($name)
@@ -497,10 +536,10 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* Any previously added numeric functions are discarded.
*
* @return void
*
* @psalm-param array<string, class-string> $functions The map of custom
* DQL numeric functions.
*
* @return void
*/
public function setCustomNumericFunctions(array $functions)
{
@@ -518,10 +557,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name Function name.
* @param string|callable $className Class name or a callable that returns the function.
* @psalm-param class-string|callable $className
*
* @return void
*
* @psalm-param class-string|callable $className
*/
public function addCustomDatetimeFunction($name, $className)
{
@@ -534,7 +572,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|null
*
* @psalm-return ?class-string $name
*/
public function getCustomDatetimeFunction($name)
@@ -553,10 +590,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Any previously added date/time functions are discarded.
*
* @param array $functions The map of custom DQL date/time functions.
* @psalm-param array<string, string> $functions
*
* @return void
*
* @psalm-param array<string, string> $functions
*/
public function setCustomDatetimeFunctions(array $functions)
{
@@ -587,7 +623,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $modeName The hydration mode name.
*
* @return string|null The hydrator class name.
*
* @psalm-return ?class-string
*/
public function getCustomHydrationMode($modeName)
@@ -599,10 +634,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Adds a custom hydration mode.
*
* @param string $modeName The hydration mode name.
* @psalm-param class-string $hydrator The hydrator class name.
*
* @return void
*
* @psalm-param class-string $hydrator The hydrator class name.
*/
public function addCustomHydrationMode($modeName, $hydrator)
{
@@ -613,10 +647,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets a class metadata factory.
*
* @param string $cmfName
* @psalm-param class-string $cmfName
*
* @return void
*
* @psalm-param class-string $cmfName
*/
public function setClassMetadataFactoryName($cmfName)
{
@@ -625,7 +658,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* @return string
*
* @psalm-return class-string
*/
public function getClassMetadataFactoryName()
@@ -642,6 +674,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name The name of the filter.
* @param string $className The class name of the filter.
*
* @return void
*/
public function addFilter($name, $className)
{
@@ -655,7 +689,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @return string|null The class name of the filter, or null if it is not
* defined.
*
* @psalm-return ?class-string
*/
public function getFilterClassName($name)
@@ -687,7 +720,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Get default repository class.
*
* @return string
*
* @psalm-return class-string
*/
public function getDefaultRepositoryClassName()
@@ -745,6 +777,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Set the entity listener resolver.
*
* @return void
*/
public function setEntityListenerResolver(EntityListenerResolver $resolver)
{
@@ -767,6 +801,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Set the entity repository factory.
*
* @return void
*/
public function setRepositoryFactory(RepositoryFactory $repositoryFactory)
{
@@ -835,6 +871,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Sets array of query hints, which will be applied to every query in application
*
* @psalm-param array<string, mixed> $defaultQueryHints
*
* @return void
*/
public function setDefaultQueryHints(array $defaultQueryHints)
{
@@ -858,6 +896,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $name The name of the hint.
* @param mixed $value The value of the hint.
*
* @return void
*/
public function setDefaultQueryHint($name, $value)
{

View File

@@ -21,11 +21,14 @@
namespace Doctrine\ORM;
use BadMethodCallException;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\Common\EventManager;
use Doctrine\Common\Util\ClassUtils;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\LockMode;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Proxy\ProxyFactory;
@@ -47,10 +50,8 @@ use function is_callable;
use function is_object;
use function is_string;
use function ltrim;
use function method_exists;
use function sprintf;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* The EntityManager is the central access point to ORM functionality.
@@ -166,7 +167,8 @@ use const E_USER_DEPRECATED;
$this->metadataFactory = new $metadataFactoryClassName();
$this->metadataFactory->setEntityManager($this);
$this->metadataFactory->setCacheDriver($this->config->getMetadataCacheImpl());
$this->configureMetadataCache();
$this->repositoryFactory = $config->getRepositoryFactory();
$this->unitOfWork = new UnitOfWork($this);
@@ -284,9 +286,7 @@ use const E_USER_DEPRECATED;
*
* Internal note: Performance-sensitive method.
*
* @param string $className
*
* @return ClassMetadata
* {@inheritDoc}
*/
public function getClassMetadata($className)
{
@@ -365,9 +365,11 @@ use const E_USER_DEPRECATED;
public function flush($entity = null)
{
if ($entity !== null) {
@trigger_error(
'Calling ' . __METHOD__ . '() with any arguments to flush specific entities is deprecated and will not be supported in Doctrine ORM 3.0.',
E_USER_DEPRECATED
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8459',
'Calling %s() with any arguments to flush specific entities is deprecated and will not be supported in Doctrine ORM 3.0.',
__METHOD__
);
}
@@ -386,8 +388,10 @@ use const E_USER_DEPRECATED;
* during the search.
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @psalm-param class-string<T> $className
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @psalm-return ?T
*
* @throws OptimisticLockException
* @throws ORMInvalidArgumentException
@@ -395,8 +399,6 @@ use const E_USER_DEPRECATED;
* @throws ORMException
*
* @template T
* @psalm-param class-string<T> $className
* @psalm-return ?T
*/
public function find($className, $id, $lockMode = null, $lockVersion = null)
{
@@ -575,9 +577,11 @@ use const E_USER_DEPRECATED;
}
if ($entityName !== null) {
@trigger_error(
'Calling ' . __METHOD__ . '() with any arguments to clear specific entities is deprecated and will not be supported in Doctrine ORM 3.0.',
E_USER_DEPRECATED
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8460',
'Calling %s() with any arguments to clear specific entities is deprecated and will not be supported in Doctrine ORM 3.0.',
__METHOD__
);
}
@@ -678,8 +682,6 @@ use const E_USER_DEPRECATED;
* Entities which previously referenced the detached entity will continue to
* reference it.
*
* @deprecated 2.7 This method is being removed from the ORM and won't have any replacement
*
* @param object $entity The entity to detach.
*
* @return void
@@ -688,8 +690,6 @@ use const E_USER_DEPRECATED;
*/
public function detach($entity)
{
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity);
}
@@ -713,7 +713,12 @@ use const E_USER_DEPRECATED;
*/
public function merge($entity)
{
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8461',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0.',
__METHOD__
);
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity);
@@ -729,7 +734,12 @@ use const E_USER_DEPRECATED;
*/
public function copy($entity, $deep = false)
{
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8462',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0.',
__METHOD__
);
throw new BadMethodCallException('Not implemented.');
}
@@ -746,12 +756,12 @@ use const E_USER_DEPRECATED;
* Gets the repository for an entity class.
*
* @param string $entityName The name of the entity.
* @psalm-param class-string<T> $entityName
*
* @return ObjectRepository|EntityRepository The repository class.
* @psalm-return EntityRepository<T>
*
* @template T
* @psalm-param class-string<T> $entityName
* @psalm-return EntityRepository<T>
*/
public function getRepository($entityName)
{
@@ -791,11 +801,9 @@ use const E_USER_DEPRECATED;
/**
* Throws an exception if the EntityManager is closed or currently not active.
*
* @return void
*
* @throws ORMException If the EntityManager is closed.
*/
private function errorIfClosed()
private function errorIfClosed(): void
{
if ($this->closed) {
throw ORMException::entityManagerClosed();
@@ -980,4 +988,42 @@ use const E_USER_DEPRECATED;
}
}
}
private function configureMetadataCache(): void
{
$metadataCache = $this->config->getMetadataCache();
if (! $metadataCache) {
$this->configureLegacyMetadataCache();
return;
}
// We have a PSR-6 compatible metadata factory. Use cache directly
if (method_exists($this->metadataFactory, 'setCache')) {
$this->metadataFactory->setCache($metadataCache);
return;
}
// Wrap PSR-6 cache to provide doctrine/cache interface
$this->metadataFactory->setCacheDriver(DoctrineProvider::wrap($metadataCache));
}
private function configureLegacyMetadataCache(): void
{
$metadataCache = $this->config->getMetadataCacheImpl();
if (! $metadataCache) {
return;
}
// Metadata factory is not PSR-6 compatible. Use cache directly
if (! method_exists($this->metadataFactory, 'setCache')) {
$this->metadataFactory->setCacheDriver($metadataCache);
return;
}
// Wrap doctrine/cache to provide PSR-6 interface
$this->metadataFactory->setCache(CacheAdapter::wrap($metadataCache));
}
}

View File

@@ -34,10 +34,21 @@ use Doctrine\Persistence\ObjectManager;
/**
* EntityManager interface
*
* @method Mapping\ClassMetadata getClassMetadata($className)
* @method Mapping\ClassMetadataFactory getMetadataFactory()
*/
interface EntityManagerInterface extends ObjectManager
{
/**
* {@inheritdoc}
*
* @psalm-param class-string<T> $className
*
* @psalm-return EntityRepository<T>
*
* @template T
*/
public function getRepository($className);
/**
* Returns the cache API for managing the second level cache regions or NULL if the cache is not enabled.
*
@@ -155,14 +166,14 @@ interface EntityManagerInterface extends ObjectManager
*
* @param string $entityName The name of the entity type.
* @param mixed $id The entity identifier.
* @psalm-param class-string<T> $entityName
*
* @return object|null The entity reference.
* @psalm-return ?T
*
* @throws ORMException
*
* @template T
* @psalm-param class-string<T> $entityName
* @psalm-return ?T
*/
public function getReference($entityName, $id);
@@ -305,4 +316,16 @@ interface EntityManagerInterface extends ObjectManager
* @return bool True, if the EM has a filter collection.
*/
public function hasFilters();
/**
* {@inheritDoc}
*
* @psalm-param string|class-string<T> $className
*
* @return Mapping\ClassMetadata
* @psalm-return Mapping\ClassMetadata<T>
*
* @template T of object
*/
public function getClassMetadata($className);
}

View File

@@ -21,6 +21,7 @@
namespace Doctrine\ORM;
use function implode;
use function sprintf;
/**
* Exception thrown when a Proxy fails to retrieve an Entity result.
@@ -47,4 +48,17 @@ class EntityNotFoundException extends ORMException
'Entity of type \'' . $className . '\'' . ($ids ? ' for IDs ' . implode(', ', $ids) : '') . ' was not found'
);
}
/**
* Instance for which no identifier can be found
*
* @psalm-param class-string $className
*/
public static function noIdentifierFound(string $className): self
{
return new self(sprintf(
'Unable to find "%s" entity identifier associated with the UnitOfWork',
$className
));
}
}

View File

@@ -24,6 +24,7 @@ use BadMethodCallException;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -35,9 +36,6 @@ use function lcfirst;
use function sprintf;
use function strpos;
use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* An EntityRepository serves as a repository for entities with generic as well as
@@ -111,24 +109,44 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Creates a new Query instance based on a predefined metadata named query.
*
* @deprecated
*
* @param string $queryName
*
* @return Query
*/
public function createNamedQuery($queryName)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8592',
'Named Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
$queryName,
$this->_class->name
);
return $this->_em->createQuery($this->_class->getNamedQuery($queryName));
}
/**
* Creates a native SQL query.
*
* @deprecated
*
* @param string $queryName
*
* @return NativeQuery
*/
public function createNativeNamedQuery($queryName)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8592',
'Named Native Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
$queryName,
$this->_class->name
);
$queryMapping = $this->_class->getNamedNativeQuery($queryName);
$rsm = new Query\ResultSetMappingBuilder($this->_em);
$rsm->addNamedNativeQueryMapping($this->_class, $queryMapping);
@@ -145,7 +163,12 @@ class EntityRepository implements ObjectRepository, Selectable
*/
public function clear()
{
@trigger_error('Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0.', E_USER_DEPRECATED);
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8460',
'Calling %s() is deprecated and will not be supported in Doctrine ORM 3.0.',
__METHOD__
);
$this->_em->clear($this->_class->rootEntityName);
}
@@ -160,7 +183,6 @@ class EntityRepository implements ObjectRepository, Selectable
* @param int|null $lockVersion The lock version.
*
* @return object|null The entity instance or NULL if the entity can not be found.
*
* @psalm-return ?T
*/
public function find($id, $lockMode = null, $lockVersion = null)
@@ -183,9 +205,9 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @param int|null $limit
* @param int|null $offset
*
* @psalm-param array<string, mixed> $criteria
* @psalm-param list<string>|null $orderBy
* @psalm-param array<string, string>|null $orderBy
*
* @psalm-return list<T> The objects.
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
@@ -198,10 +220,10 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Finds a single entity by a set of criteria.
*
* @return object|null The entity instance or NULL if the entity can not be found.
*
* @psalm-param array<string, mixed> $criteria
* @psalm-param array<string, string>|null $orderBy
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @psalm-return ?T
*/
public function findOneBy(array $criteria, ?array $orderBy = null)
@@ -214,9 +236,10 @@ class EntityRepository implements ObjectRepository, Selectable
/**
* Counts entities by a set of criteria.
*
* @psalm-param array<string, mixed> $criteria
*
* @return int The cardinality of the objects that match the given criteria.
*
* @psalm-param array<string, mixed> $criteria
* @todo Add this method to `ObjectRepository` interface in the next major release
*/
public function count(array $criteria)
@@ -228,13 +251,12 @@ class EntityRepository implements ObjectRepository, Selectable
* Adds support for magic method calls.
*
* @param string $method
* @psalm-param list<mixed> $arguments
*
* @return mixed The returned value from the resolved method.
*
* @throws ORMException
* @throws BadMethodCallException If the method called is invalid.
*
* @psalm-param list<mixed> $arguments
*/
public function __call($method, $arguments)
{
@@ -293,6 +315,7 @@ class EntityRepository implements ObjectRepository, Selectable
* Select all elements from a selectable that match the expression and
* return a new collection containing these elements.
*
* @return LazyCriteriaCollection
* @psalm-return Collection<int, T>
*/
public function matching(Criteria $criteria)
@@ -307,12 +330,11 @@ class EntityRepository implements ObjectRepository, Selectable
*
* @param string $method The method to call
* @param string $by The property name used as condition
* @psalm-param list<mixed> $arguments The arguments to pass at method call
*
* @return mixed
*
* @throws ORMException If the method called is invalid or the requested field/association does not exist.
*
* @psalm-param list<mixed> $arguments The arguments to pass at method call
*/
private function resolveMagicCall(string $method, string $by, array $arguments)
{

View File

@@ -90,6 +90,8 @@ class ListenersInvoker
* @param object $entity The Entity on which the event occurred.
* @param EventArgs $event The Event args.
* @param int $invoke Bitmask to invoke listeners.
*
* @return void
*/
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
{

View File

@@ -48,6 +48,9 @@ class OnClassMetadataNotFoundEventArgs extends ManagerEventArgs
parent::__construct($objectManager);
}
/**
* @return void
*/
public function setFoundMetadata(?ClassMetadata $classMetadata = null)
{
$this->foundMetadata = $classMetadata;

View File

@@ -21,7 +21,6 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
@@ -49,7 +48,7 @@ class OnClearEventArgs extends EventArgs
/**
* Retrieves associated EntityManager.
*
* @return EntityManager
* @return EntityManagerInterface
*/
public function getEntityManager()
{

View File

@@ -21,7 +21,6 @@
namespace Doctrine\ORM\Event;
use Doctrine\Common\EventArgs;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
/**
@@ -42,7 +41,7 @@ class OnFlushEventArgs extends EventArgs
/**
* Retrieve associated EntityManager.
*
* @return EntityManager
* @return EntityManagerInterface
*/
public function getEntityManager()
{

View File

@@ -113,13 +113,9 @@ class PreUpdateEventArgs extends LifecycleEventArgs
/**
* Asserts the field exists in changeset.
*
* @param string $field
*
* @return void
*
* @throws InvalidArgumentException
*/
private function assertValidField($field)
private function assertValidField(string $field): void
{
if (! isset($this->entityChangeSet[$field])) {
throw new InvalidArgumentException(sprintf(

View File

@@ -146,10 +146,8 @@ class CommitOrderCalculator
* Visit a given node definition for reordering.
*
* {@internal Highly performance-sensitive method.}
*
* @param stdClass $vertex
*/
private function visit($vertex)
private function visit(stdClass $vertex): void
{
$vertex->state = self::IN_PROGRESS;

View File

@@ -24,12 +24,14 @@ use Doctrine\DBAL\Driver\ResultStatement;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Tools\Pagination\LimitSubqueryWalker;
use Doctrine\ORM\UnitOfWork;
use Generator;
use PDO;
use ReflectionClass;
@@ -38,9 +40,6 @@ use function array_merge;
use function count;
use function end;
use function in_array;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Base class for all hydrators. A hydrator is a class that provides some form
@@ -123,16 +122,17 @@ abstract class AbstractHydrator
*
* @param object $stmt
* @param object $resultSetMapping
* @psalm-param array<string, mixed> $hints
*
* @return IterableResult
*
* @psalm-param array<string, mixed> $hints
*/
public function iterate($stmt, $resultSetMapping, array $hints = [])
{
@trigger_error(
'Method ' . __METHOD__ . '() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
E_USER_DEPRECATED
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8463',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
__METHOD__
);
$this->_stmt = $stmt;
@@ -151,9 +151,9 @@ abstract class AbstractHydrator
/**
* Initiates a row-by-row hydration.
*
* @return iterable<mixed>
*
* @psalm-param array<string, mixed> $hints
*
* @return Generator<int, mixed>
*/
public function toIterable(ResultStatement $stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable
{
@@ -195,10 +195,9 @@ abstract class AbstractHydrator
*
* @param object $stmt
* @param object $resultSetMapping
* @psalm-param array<string, string> $hints
*
* @return mixed[]
*
* @psalm-param array<string, string> $hints
*/
public function hydrateAll($stmt, $resultSetMapping, array $hints = [])
{
@@ -222,7 +221,7 @@ abstract class AbstractHydrator
* Hydrates a single row returned by the current statement instance during
* row-by-row hydration with {@link iterate()} or {@link toIterable()}.
*
* @return mixed
* @return mixed[]|false
*/
public function hydrateRow()
{
@@ -322,14 +321,13 @@ abstract class AbstractHydrator
* the values applied. Scalar values are kept in a specific key 'scalars'.
*
* @param mixed[] $data SQL Result Row.
* @psalm-param array<string, string> $id Dql-Alias => ID-Hash.
* @psalm-param array<string, bool> $nonemptyComponents Does this DQL-Alias has at least one non NULL value?
*
* @return array<string, array<string, mixed>> An array with all the fields
* (name => value) of the data
* row, grouped by their
* component alias.
*
* @psalm-param array<string, string> $id Dql-Alias => ID-Hash.
* @psalm-param array<string, bool> $nonemptyComponents Does this DQL-Alias has at least one non NULL value?
* @psalm-return array{
* data: array<array-key, array>,
* newObjects?: array<array-key, array{
@@ -415,6 +413,7 @@ abstract class AbstractHydrator
* of elements as before.
*
* @psalm-param array<string, mixed> $data
*
* @psalm-return array<string, mixed> The processed row.
*/
protected function gatherScalarRowData(&$data)
@@ -541,6 +540,7 @@ abstract class AbstractHydrator
/**
* @return string[]
* @psalm-return non-empty-list<string>
*/
private function getDiscriminatorValues(ClassMetadata $classMetadata): array
{

View File

@@ -259,15 +259,16 @@ class ArrayHydrator extends AbstractHydrator
* Updates the result pointer for an Entity. The result pointers point to the
* last seen instance of each Entity type. This is used for graph construction.
*
* @param mixed[] $coll The element.
* @param bool|int $index Index of the element in the collection.
* @param string $dqlAlias
* @param bool $oneToOne Whether it is a single-valued association or not.
*
* @return void
* @param mixed[]|null $coll The element.
* @param bool|int $index Index of the element in the collection.
* @param bool $oneToOne Whether it is a single-valued association or not.
*/
private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
{
private function updateResultPointer(
?array &$coll,
$index,
string $dqlAlias,
bool $oneToOne
): void {
if ($coll === null) {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228

View File

@@ -100,10 +100,9 @@ class HydrationException extends ORMException
/**
* @param string $discrValue
* @psalm-param array<string, string> $discrMap
*
* @return HydrationException
*
* @psalm-param array<string, string> $discrMap
*/
public static function invalidDiscriminatorValue($discrValue, $discrMap)
{

View File

@@ -39,7 +39,7 @@ class IterableResult implements Iterator
/** @var int */
private $_key = -1;
/** @var object|null */
/** @var mixed[]|null */
private $_current = null;
/**

View File

@@ -21,9 +21,9 @@
namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\Proxy;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use PDO;
@@ -171,15 +171,16 @@ class ObjectHydrator extends AbstractHydrator
/**
* Initializes a related collection.
*
* @param object $entity The entity to which the collection belongs.
* @param ClassMetadata $class
* @param string $fieldName The name of the field on the entity that holds the collection.
* @param string $parentDqlAlias Alias of the parent fetch joining this collection.
*
* @return PersistentCollection
* @param object $entity The entity to which the collection belongs.
* @param string $fieldName The name of the field on the entity that holds the collection.
* @param string $parentDqlAlias Alias of the parent fetch joining this collection.
*/
private function initRelatedCollection($entity, $class, $fieldName, $parentDqlAlias)
{
private function initRelatedCollection(
$entity,
ClassMetadata $class,
string $fieldName,
string $parentDqlAlias
): PersistentCollection {
$oid = spl_object_hash($entity);
$relation = $class->associationMappings[$fieldName];
$value = $class->reflFields[$fieldName]->getValue($entity);
@@ -223,14 +224,13 @@ class ObjectHydrator extends AbstractHydrator
* Gets an entity instance.
*
* @param string $dqlAlias The DQL alias of the entity's class.
* @psalm-param array<string, mixed> $data The instance data.
*
* @return object The entity.
* @return object
*
* @throws HydrationException
*
* @psalm-param array<string, mixed> $data The instance data.
*/
private function getEntity(array $data, $dqlAlias)
private function getEntity(array $data, string $dqlAlias)
{
$className = $this->_rsm->aliasMap[$dqlAlias];
@@ -273,13 +273,12 @@ class ObjectHydrator extends AbstractHydrator
}
/**
* @param string $className
* @psalm-param class-string $className
* @psalm-param array<string, mixed> $data
*
* @return mixed
*
* @psalm-param array<string, mixed> $data
*/
private function getEntityFromIdentityMap($className, array $data)
private function getEntityFromIdentityMap(string $className, array $data)
{
// TODO: Abstract this code and UnitOfWork::createEntity() equivalent?
$class = $this->_metadataCache[$className];
@@ -433,7 +432,7 @@ class ObjectHydrator extends AbstractHydrator
// PATH B: Single-valued association
$reflFieldValue = $reflField->getValue($parentObject);
if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && ! $reflFieldValue->__isInitialized__)) {
if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && ! $reflFieldValue->__isInitialized())) {
// we only need to take action if this value is null,
// we refresh the entity or its an uninitialized proxy.
if (isset($nonemptyComponents[$dqlAlias])) {

View File

@@ -133,6 +133,11 @@ class SimpleObjectHydrator extends AbstractHydrator
continue;
}
// If we have inheritance in resultset, make sure the field belongs to the correct class
if (isset($cacheKeyInfo['discriminatorValues']) && ! in_array((string) $discrColumnValue, $cacheKeyInfo['discriminatorValues'], true)) {
continue;
}
// Check if value is null before conversion (because some types convert null to something else)
$valueIsNull = $value === null;
@@ -146,11 +151,6 @@ class SimpleObjectHydrator extends AbstractHydrator
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
if (! isset($data[$fieldName]) || ! $valueIsNull) {
// If we have inheritance in resultset, make sure the field belongs to the correct class
if (isset($cacheKeyInfo['discriminatorValues']) && ! in_array((string) $discrColumnValue, $cacheKeyInfo['discriminatorValues'], true)) {
continue;
}
$data[$fieldName] = $value;
}
}

View File

@@ -55,7 +55,7 @@ final class HydrationCompleteHandler
*
* @param object $entity
*/
public function deferPostLoadInvoking(ClassMetadata $class, $entity)
public function deferPostLoadInvoking(ClassMetadata $class, $entity): void
{
$invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad);
@@ -71,7 +71,7 @@ final class HydrationCompleteHandler
*
* Method fires all deferred invocations of postLoad events
*/
public function hydrationComplete()
public function hydrationComplete(): void
{
$toInvoke = $this->deferredPostLoadInvocations;
$this->deferredPostLoadInvocations = [];

View File

@@ -20,12 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
/**
* This annotation is used to override the mapping of a entity property.
*
* @Annotation
* @Target("ANNOTATION")
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class AttributeOverride implements Annotation
{
/**

View File

@@ -39,7 +39,7 @@ class ClassMetadataBuilder
}
/**
* @return ClassMetadata
* @return ClassMetadataInfo
*/
public function getClassMetadata()
{
@@ -138,10 +138,9 @@ class ClassMetadataBuilder
* Adds Index.
*
* @param string $name
* @psalm-param list<string> $columns
*
* @return static
*
* @psalm-param list<string> $columns
*/
public function addIndex(array $columns, $name)
{
@@ -158,10 +157,9 @@ class ClassMetadataBuilder
* Adds Unique Constraint.
*
* @param string $name
* @psalm-param list<string> $columns
*
* @return static
*
* @psalm-param list<string> $columns
*/
public function addUniqueConstraint(array $columns, $name)
{
@@ -299,10 +297,9 @@ class ClassMetadataBuilder
*
* @param string $name
* @param string $type
* @psalm-param array<string, mixed> $mapping
*
* @return static
*
* @psalm-param array<string, mixed> $mapping
*/
public function addField($name, $type, array $mapping = [])
{

View File

@@ -50,6 +50,8 @@ class EntityListenerBuilder
* @param ClassMetadata $metadata The entity metadata.
* @param string $className The listener class name.
*
* @return void
*
* @throws MappingException When the listener class not found.
*/
public static function bindEntityListener(ClassMetadata $metadata, $className)

View File

@@ -28,9 +28,9 @@ namespace Doctrine\ORM\Mapping\Builder;
class OneToManyAssociationBuilder extends AssociationBuilder
{
/**
* @return static
* @psalm-param array<string, string> $fieldNames
*
* @psalm-param list<string> $fieldNames
* @return static
*/
public function setOrderBy(array $fieldNames)
{

View File

@@ -20,12 +20,17 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* Caching to an entity or a collection.
*
* @Annotation
* @NamedArgumentConstructor()
* @Target({"CLASS","PROPERTY"})
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
final class Cache implements Annotation
{
/**
@@ -36,4 +41,10 @@ final class Cache implements Annotation
/** @var string Cache region name. */
public $region;
public function __construct(string $usage = 'READ_ONLY', ?string $region = null)
{
$this->usage = $usage;
$this->region = $region;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class ChangeTrackingPolicy implements Annotation
{
/**
@@ -33,4 +38,9 @@ final class ChangeTrackingPolicy implements Annotation
* @Enum({"DEFERRED_IMPLICIT", "DEFERRED_EXPLICIT", "NOTIFY"})
*/
public $value;
public function __construct(string $value)
{
$this->value = $value;
}
}

View File

@@ -24,6 +24,8 @@ namespace Doctrine\ORM\Mapping;
* {@inheritDoc}
*
* @todo remove or rename ClassMetadataInfo to ClassMetadata
* @template T of object
* @template-extends ClassMetadataInfo<T>
*/
class ClassMetadata extends ClassMetadataInfo
{

View File

@@ -23,6 +23,7 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Platforms;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
@@ -54,13 +55,17 @@ use function strtolower;
* The ClassMetadataFactory is used to create ClassMetadata objects that contain all the
* metadata mapping information of a class which describes how a class should be mapped
* to a relational database.
*
* @method ClassMetadata[] getAllMetadata()
* @method ClassMetadata[] getLoadedMetadata()
* @method ClassMetadata getMetadataFor($className)
*/
class ClassMetadataFactory extends AbstractClassMetadataFactory
{
/** @var EntityManagerInterface|null */
private $em;
/** @var AbstractPlatform */
/** @var AbstractPlatform|null */
private $targetPlatform;
/** @var MappingDriver */
@@ -84,6 +89,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
return $loaded;
}
/**
* @return void
*/
public function setEntityManager(EntityManagerInterface $em)
{
$this->em = $em;
@@ -238,6 +246,15 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
if ($class->changeTrackingPolicy === ClassMetadataInfo::CHANGETRACKING_NOTIFY) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8383',
'NOTIFY Change Tracking policy used in "%s" is deprecated, use deferred explicit instead.',
$class->name
);
}
$this->validateRuntimeMetadata($class, $parent);
}
@@ -297,11 +314,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
* Populates the discriminator value of the given metadata (if not set) by iterating over discriminator
* map classes and looking for a fitting one.
*
* @return void
*
* @throws MappingException
*/
private function resolveDiscriminatorValue(ClassMetadata $metadata)
private function resolveDiscriminatorValue(ClassMetadata $metadata): void
{
if (
$metadata->discriminatorValue
@@ -346,7 +361,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
*
* @throws MappingException
*/
private function addDefaultDiscriminatorMap(ClassMetadata $class)
private function addDefaultDiscriminatorMap(ClassMetadata $class): void
{
$allClasses = $this->driver->getAllClassNames();
$fqcn = $class->getName();
@@ -375,11 +390,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Gets the lower-case short name of a class.
*
* @param string $className
*
* @return string
* @psalm-param class-string $className
*/
private function getShortName($className)
private function getShortName(string $className): string
{
if (strpos($className, '\\') === false) {
return strtolower($className);
@@ -392,10 +405,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Adds inherited fields to the subclass mapping.
*
* @return void
*/
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass)
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->fieldMappings as $mapping) {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
@@ -417,11 +428,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Adds inherited association mappings to the subclass mapping.
*
* @return void
*
* @throws MappingException
*/
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass)
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->associationMappings as $field => $mapping) {
if ($parentClass->isMappedSuperclass) {
@@ -445,7 +454,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
}
private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass)
private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->embeddedClasses as $field => $embeddedClass) {
if (! isset($embeddedClass['inherited']) && ! $parentClass->isMappedSuperclass) {
@@ -467,8 +476,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
* @param ClassMetadata $parentClass Parent class to add nested embedded classes metadata to.
* @param string $prefix Embedded classes' prefix to use for nested embedded classes field names.
*/
private function addNestedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass, $prefix)
{
private function addNestedEmbeddedClasses(
ClassMetadata $subClass,
ClassMetadata $parentClass,
string $prefix
): void {
foreach ($subClass->embeddedClasses as $property => $embeddableClass) {
if (isset($embeddableClass['inherited'])) {
continue;
@@ -492,10 +504,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Copy the table indices from the parent class superclass to the child class
*
* @return void
*/
private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass)
private function addInheritedIndexes(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
if (! $parentClass->isMappedSuperclass) {
return;
@@ -516,10 +526,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Adds inherited named queries to the subclass mapping.
*
* @return void
*/
private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
private function addInheritedNamedQueries(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->namedQueries as $name => $query) {
if (! isset($subClass->namedQueries[$name])) {
@@ -535,10 +543,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Adds inherited named native queries to the subclass mapping.
*
* @return void
*/
private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass)
private function addInheritedNamedNativeQueries(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->namedNativeQueries as $name => $query) {
if (! isset($subClass->namedNativeQueries[$name])) {
@@ -557,10 +563,8 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Adds inherited sql result set mappings to the subclass mapping.
*
* @return void
*/
private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass)
private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->sqlResultSetMappings as $name => $mapping) {
if (! isset($subClass->sqlResultSetMappings[$name])) {
@@ -589,11 +593,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
* Completes the ID generator mapping. If "auto" is specified we choose the generator
* most appropriate for the targeted database platform.
*
* @return void
*
* @throws ORMException
*/
private function completeIdGeneratorMapping(ClassMetadataInfo $class)
private function completeIdGeneratorMapping(ClassMetadataInfo $class): void
{
$idGenType = $class->generatorType;
if ($idGenType === ClassMetadata::GENERATOR_TYPE_AUTO) {
@@ -705,7 +707,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
/**
* Inherits the ID generator mapping from a parent class.
*/
private function inheritIdGeneratorMapping(ClassMetadataInfo $class, ClassMetadataInfo $parent)
private function inheritIdGeneratorMapping(ClassMetadataInfo $class, ClassMetadataInfo $parent): void
{
if ($parent->isIdGeneratorSequence()) {
$class->setSequenceGeneratorDefinition($parent->sequenceGeneratorDefinition);
@@ -745,6 +747,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
*/
protected function getFqcnFromAlias($namespaceAlias, $simpleClassName)
{
/** @psalm-var class-string */
return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
}
@@ -764,10 +767,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
return isset($class->isMappedSuperclass) && $class->isMappedSuperclass === false;
}
/**
* @return Platforms\AbstractPlatform
*/
private function getTargetPlatform()
private function getTargetPlatform(): Platforms\AbstractPlatform
{
if (! $this->targetPlatform) {
$this->targetPlatform = $this->em->getConnection()->getDatabasePlatform();

View File

@@ -21,8 +21,13 @@
namespace Doctrine\ORM\Mapping;
use BadMethodCallException;
use DateInterval;
use DateTime;
use DateTimeImmutable;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Instantiator\Instantiator;
use Doctrine\Instantiator\InstantiatorInterface;
use Doctrine\ORM\Cache\CacheException;
@@ -31,6 +36,7 @@ use Doctrine\Persistence\Mapping\ClassMetadata;
use Doctrine\Persistence\Mapping\ReflectionService;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionNamedType;
use ReflectionProperty;
use RuntimeException;
@@ -59,6 +65,8 @@ use function strtolower;
use function trait_exists;
use function trim;
use const PHP_VERSION_ID;
/**
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
* of an entity and its associations.
@@ -72,6 +80,9 @@ use function trim;
* 2) To drastically reduce the size of a serialized instance (private/protected members
* get the whole class name, namespace inclusive, prepended to every property in
* the serialized representation).
*
* @template T of object
* @template-implements ClassMetadata<T>
*/
class ClassMetadataInfo implements ClassMetadata
{
@@ -235,6 +246,7 @@ class ClassMetadataInfo implements ClassMetadata
* READ-ONLY: The name of the entity class.
*
* @var string
* @psalm-var class-string<T>
*/
public $name;
@@ -252,6 +264,7 @@ class ClassMetadataInfo implements ClassMetadata
* as {@link $name}.
*
* @var string
* @psalm-var class-string
*/
public $rootEntityName;
@@ -371,6 +384,7 @@ class ClassMetadataInfo implements ClassMetadata
* READ-ONLY: The inheritance mapping type used by the class.
*
* @var int
* @psalm-var self::$INHERITANCE_TYPE_*
*/
public $inheritanceType = self::INHERITANCE_TYPE_NONE;
@@ -420,6 +434,7 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>'unique'</b> (string, optional, schema-only)
* Whether a unique constraint should be generated for the column.
*
* @var mixed[]
* @psalm-var array<string, array{
* type: string,
* fieldName: string,
@@ -436,6 +451,8 @@ class ClassMetadataInfo implements ClassMetadata
* originalField?: string,
* quoted?: bool,
* requireSQLConversion?: bool,
* declaredField?: string,
* options: array<mixed>
* }>
*/
public $fieldMappings = [];
@@ -500,7 +517,8 @@ class ClassMetadataInfo implements ClassMetadata
* indexes => array
* uniqueConstraints => array
*
* @psalm-var array<string, mixed>
* @var mixed[]
* @psalm-var array{name: string, schema: string, indexes: array, uniqueConstraints: array}
*/
public $table;
@@ -612,7 +630,8 @@ class ClassMetadataInfo implements ClassMetadata
* )
* </code>
*
* @psalm-var array<string, mixed>
* @var mixed[]
* @psalm-var array{sequenceName: string, allocationSize: int, initialValue: int}
* @todo Merge with tableGeneratorDefinition into generic generatorDefinition
*/
public $sequenceGeneratorDefinition;
@@ -691,6 +710,7 @@ class ClassMetadataInfo implements ClassMetadata
* metadata of the class with the given name.
*
* @param string $entityName The name of the entity class the new instance is used for.
* @psalm-param class-string<T> $entityName
*/
public function __construct($entityName, ?NamingStrategy $namingStrategy = null)
{
@@ -704,7 +724,6 @@ class ClassMetadataInfo implements ClassMetadata
* Gets the ReflectionProperties of the mapped class.
*
* @return ReflectionProperty[]|null[] An array of ReflectionProperty instances.
*
* @psalm-return array<ReflectionProperty|null>
*/
public function getReflectionProperties()
@@ -748,7 +767,7 @@ class ClassMetadataInfo implements ClassMetadata
*
* @param object $entity
*
* @return array<string|int, mixed>
* @return array<string, mixed>
*/
public function getIdentifierValues($entity)
{
@@ -780,10 +799,10 @@ class ClassMetadataInfo implements ClassMetadata
* Populates the entity identifier of an entity.
*
* @param object $entity
* @psalm-param array<string, mixed> $id
*
* @return void
*
* @psalm-param array<string, mixed> $id
* @todo Rename to assignIdentifier()
*/
public function setIdentifierValues($entity, array $id)
@@ -1102,9 +1121,9 @@ class ClassMetadataInfo implements ClassMetadata
}
/**
* @return void
*
* @psalm-param array{usage?: mixed, region?: mixed} $cache
*
* @return void
*/
public function enableCache(array $cache)
{
@@ -1121,10 +1140,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* @param string $fieldName
* @psalm-param array{usage?: int, region?: string} $cache
*
* @return void
*
* @psalm-param array{usage?: mixed, region?: mixed} $cache
*/
public function enableAssociationCache($fieldName, array $cache)
{
@@ -1134,11 +1152,10 @@ class ClassMetadataInfo implements ClassMetadata
/**
* @param string $fieldName
* @param array $cache
* @psalm-param array{usage?: int, region?: string} $cache
*
* @return mixed[]
*
* @psalm-param array{usage?: mixed, region?: mixed} $cache
* @psalm-return array{usage: mixed, region: mixed}
* @return int[]|string[]
* @psalm-return array{usage: int, region: string|null}
*/
public function getAssociationCacheDefaults($fieldName, array $cache)
{
@@ -1264,9 +1281,21 @@ class ClassMetadataInfo implements ClassMetadata
*
* @param string $fieldName The field name.
*
* @throws MappingException
* @return mixed[] The field mapping.
* @psalm-return array{
* type: string,
* fieldName: string,
* columnName?: string,
* inherited?: class-string,
* nullable?: bool,
* originalClass?: class-string,
* originalField?: string,
* scale?: int,
* precision?: int,
* length?: int
* }
*
* @psalm-return array<string, mixed> The field mapping.
* @throws MappingException
*/
public function getFieldMapping($fieldName)
{
@@ -1285,9 +1314,9 @@ class ClassMetadataInfo implements ClassMetadata
* @param string $fieldName The field name that represents the association in
* the object model.
*
* @throws MappingException
*
* @psalm-return array<string, mixed> The mapping.
*
* @throws MappingException
*/
public function getAssociationMapping($fieldName)
{
@@ -1344,7 +1373,8 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Gets all named queries of the class.
*
* @psalm-return array<string, mixed>
* @return mixed[][]
* @psalm-return array<string, array<string, mixed>>
*/
public function getNamedQueries()
{
@@ -1358,9 +1388,9 @@ class ClassMetadataInfo implements ClassMetadata
*
* @param string $queryName The query name.
*
* @throws MappingException
*
* @psalm-return array<string, mixed>
*
* @throws MappingException
*/
public function getNamedNativeQuery($queryName)
{
@@ -1388,9 +1418,10 @@ class ClassMetadataInfo implements ClassMetadata
*
* @param string $name The result set mapping name.
*
* @throws MappingException
* @return mixed[]
* @psalm-return array{name: string, entities: array, columns: array}
*
* @psalm-return array<string, mixed>
* @throws MappingException
*/
public function getSqlResultSetMapping($name)
{
@@ -1404,29 +1435,117 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Gets all sql result set mappings of the class.
*
* @psalm-return array<string, array<string, mixed>>
* @return mixed[]
* @psalm-return array<string, array{name: string, entities: array, columns: array}>
*/
public function getSqlResultSetMappings()
{
return $this->sqlResultSetMappings;
}
/**
* Checks whether given property has type
*
* @param string $name Property name
*/
private function isTypedProperty(string $name): bool
{
return PHP_VERSION_ID >= 70400
&& isset($this->reflClass)
&& $this->reflClass->hasProperty($name)
&& $this->reflClass->getProperty($name)->hasType();
}
/**
* Validates & completes the given field mapping based on typed property.
*
* @param mixed[] $mapping The field mapping to validate & complete.
*
* @return mixed[] The updated mapping.
*/
private function validateAndCompleteTypedFieldMapping(array $mapping): array
{
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
if ($type) {
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
switch ($type->getName()) {
case DateInterval::class:
$mapping['type'] = Types::DATEINTERVAL;
break;
case DateTime::class:
$mapping['type'] = Types::DATETIME_MUTABLE;
break;
case DateTimeImmutable::class:
$mapping['type'] = Types::DATETIME_IMMUTABLE;
break;
case 'array':
$mapping['type'] = Types::JSON;
break;
case 'bool':
$mapping['type'] = Types::BOOLEAN;
break;
case 'float':
$mapping['type'] = Types::FLOAT;
break;
case 'int':
$mapping['type'] = Types::INTEGER;
break;
case 'string':
$mapping['type'] = Types::STRING;
break;
}
}
}
return $mapping;
}
/**
* Validates & completes the basic mapping information based on typed property.
*
* @param mixed[] $mapping The mapping.
*
* @return mixed[] The updated mapping.
*/
private function validateAndCompleteTypedAssociationMapping(array $mapping): array
{
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
if ($type === null || ($mapping['type'] & self::TO_ONE) === 0) {
return $mapping;
}
if (! isset($mapping['targetEntity']) && $type instanceof ReflectionNamedType) {
$mapping['targetEntity'] = $type->getName();
}
return $mapping;
}
/**
* Validates & completes the given field mapping.
*
* @return void
* @psalm-param array<string, mixed> $mapping The field mapping to validate & complete.
*
* @return mixed[] The updated mapping.
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $mapping The field mapping to validate & complete.
*/
protected function _validateAndCompleteFieldMapping(array &$mapping)
protected function validateAndCompleteFieldMapping(array $mapping): array
{
// Check mandatory fields
if (! isset($mapping['fieldName']) || ! $mapping['fieldName']) {
throw MappingException::missingFieldName($this->name);
}
if ($this->isTypedProperty($mapping['fieldName'])) {
$mapping = $this->validateAndCompleteTypedFieldMapping($mapping);
}
if (! isset($mapping['type'])) {
// Default to string
$mapping['type'] = 'string';
@@ -1473,22 +1592,22 @@ class ClassMetadataInfo implements ClassMetadata
$mapping['requireSQLConversion'] = true;
}
return $mapping;
}
/**
* Validates & completes the basic mapping information that is common to all
* association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
*
* @return mixed[] The updated mapping.
*
* @throws MappingException If something is wrong with the mapping.
*
* @psalm-param array<string, mixed> $mapping The mapping.
*
* @return mixed[] The updated mapping.
* @psalm-return array{
* mappedBy: mixed,
* inversedBy: mixed,
* mappedBy: mixed|null,
* inversedBy: mixed|null,
* isOwningSide: bool,
* sourceEntity: string,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
@@ -1498,8 +1617,13 @@ class ClassMetadataInfo implements ClassMetadata
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* ?orphanRemoval: bool
* }
*
* @throws MappingException If something is wrong with the mapping.
*/
protected function _validateAndCompleteAssociationMapping(array $mapping)
{
@@ -1521,6 +1645,10 @@ class ClassMetadataInfo implements ClassMetadata
// the sourceEntity.
$mapping['sourceEntity'] = $this->name;
if ($this->isTypedProperty($mapping['fieldName'])) {
$mapping = $this->validateAndCompleteTypedAssociationMapping($mapping);
}
if (isset($mapping['targetEntity'])) {
$mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
@@ -1617,13 +1745,38 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Validates & completes a one-to-one association mapping.
*
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
*
* @return mixed[] The validated & completed mapping.
* @psalm-return array{isOwningSide: mixed, orphanRemoval: bool, isCascadeRemove: bool}
* @psalm-return array{
* mappedBy: mixed|null,
* inversedBy: mixed|null,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array<string>,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* joinColumns?: array{0: array{name: string, referencedColumnName: string}}|mixed,
* id?: mixed,
* sourceToTargetKeyColumns?: array,
* joinColumnFieldNames?: array,
* targetToSourceKeyColumns?: array<array-key>,
* orphanRemoval: bool
* }
*
* @throws RuntimeException
* @throws MappingException
*
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
* @psalm-return array{isOwningSide: mixed, orphanRemoval: bool, isCascadeRemove: bool}
*/
protected function _validateAndCompleteOneToOneMapping(array $mapping)
{
@@ -1707,12 +1860,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Validates & completes a one-to-many association mapping.
*
* @return mixed[] The validated and completed mapping.
*
* @throws MappingException
* @throws InvalidArgumentException
*
* @psalm-param array<string, mixed> $mapping The mapping to validate and complete.
*
* @return mixed[] The validated and completed mapping.
* @psalm-return array{
* mappedBy: mixed,
* inversedBy: mixed,
@@ -1729,6 +1879,9 @@ class ClassMetadataInfo implements ClassMetadata
* isCascadeDetach: bool,
* orphanRemoval: bool
* }
*
* @throws MappingException
* @throws InvalidArgumentException
*/
protected function _validateAndCompleteOneToManyMapping(array $mapping)
{
@@ -1736,7 +1889,7 @@ class ClassMetadataInfo implements ClassMetadata
// OneToMany-side MUST be inverse (must have mappedBy)
if (! isset($mapping['mappedBy'])) {
throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
throw MappingException::oneToManyRequiresMappedBy($this->name, $mapping['fieldName']);
}
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
@@ -1750,12 +1903,36 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Validates & completes a many-to-many association mapping.
*
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
*
* @return mixed[] The validated & completed mapping.
* @psalm-return array{
* mappedBy: mixed,
* inversedBy: mixed,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array<string>,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* joinTable?: array{inverseJoinColumns: mixed}|mixed,
* joinTableColumns?: list<mixed>,
* isOnDeleteCascade?: true,
* relationToSourceKeyColumns?: array,
* relationToTargetKeyColumns?: array,
* orphanRemoval: bool
* }
*
* @throws InvalidArgumentException
*
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
* @psalm-return array{isOwningSide: mixed, orphanRemoval: bool}
*/
protected function _validateAndCompleteManyToManyMapping(array $mapping)
{
@@ -1901,9 +2078,9 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the mapped identifier/primary key fields of this class.
* Mainly used by the ClassMetadataFactory to assign inherited identifiers.
*
* @return void
*
* @psalm-param list<mixed> $identifier
*
* @return void
*/
public function setIdentifier(array $identifier)
{
@@ -1930,9 +2107,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Gets an array containing all the column names.
*
* @return mixed[]
*
* @psalm-param list<string>|null $fieldNames
*
* @return mixed[]
* @psalm-return list<string>
*/
public function getColumnNames(?array $fieldNames = null)
@@ -2151,9 +2328,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Sets the mapped subclasses of this class.
*
* @return void
*
* @psalm-param list<string> $subclasses The names of all mapped subclasses.
*
* @return void
*/
public function setSubclasses(array $subclasses)
{
@@ -2167,9 +2344,9 @@ class ClassMetadataInfo implements ClassMetadata
* Assumes that the class names in the passed array are in the order:
* directParent -> directParentParent -> directParentParentParent ... -> root.
*
* @return void
*
* @psalm-param list<class-string> $classNames
*
* @return void
*/
public function setParentClasses(array $classNames)
{
@@ -2202,12 +2379,11 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the association to override association mapping of property for an entity relationship.
*
* @param string $fieldName
* @psalm-param array<string, mixed> $overrideMapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $overrideMapping
*/
public function setAssociationOverride($fieldName, array $overrideMapping)
{
@@ -2268,12 +2444,11 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the override for a mapped field.
*
* @param string $fieldName
* @psalm-param array<string, mixed> $overrideMapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $overrideMapping
*/
public function setAttributeOverride($fieldName, array $overrideMapping)
{
@@ -2308,7 +2483,7 @@ class ClassMetadataInfo implements ClassMetadata
unset($this->fieldNames[$mapping['columnName']]);
unset($this->columnNames[$mapping['fieldName']]);
$this->_validateAndCompleteFieldMapping($overrideMapping);
$overrideMapping = $this->validateAndCompleteFieldMapping($overrideMapping);
$this->fieldMappings[$fieldName] = $overrideMapping;
}
@@ -2381,9 +2556,9 @@ class ClassMetadataInfo implements ClassMetadata
*
* If a key is omitted, the current value is kept.
*
* @return void
*
* @psalm-param array<string, mixed> $table The table description.
*
* @return void
*/
public function setPrimaryTable(array $table)
{
@@ -2425,11 +2600,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Checks whether the given type identifies an inheritance type.
*
* @param int $type
*
* @return bool TRUE if the given type identifies an inheritance type, FALSe otherwise.
* @return bool TRUE if the given type identifies an inheritance type, FALSE otherwise.
*/
private function isInheritanceType($type)
private function isInheritanceType(int $type): bool
{
return $type === self::INHERITANCE_TYPE_NONE ||
$type === self::INHERITANCE_TYPE_SINGLE_TABLE ||
@@ -2440,15 +2613,15 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Adds a mapped field to the class.
*
* @psalm-param array<string, mixed> $mapping The field mapping.
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $mapping The field mapping.
*/
public function mapField(array $mapping)
{
$this->_validateAndCompleteFieldMapping($mapping);
$mapping = $this->validateAndCompleteFieldMapping($mapping);
$this->assertFieldNotMapped($mapping['fieldName']);
$this->fieldMappings[$mapping['fieldName']] = $mapping;
@@ -2459,11 +2632,11 @@ class ClassMetadataInfo implements ClassMetadata
* Adds an association mapping without completing/validating it.
* This is mainly used to add inherited association mappings to derived classes.
*
* @psalm-param array<string, mixed> $mapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $mapping
*/
public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
{
@@ -2479,9 +2652,9 @@ class ClassMetadataInfo implements ClassMetadata
* Adds a field mapping without completing/validating it.
* This is mainly used to add inherited field mappings to derived classes.
*
* @return void
*
* @psalm-param array<string, mixed> $fieldMapping
*
* @return void
*/
public function addInheritedFieldMapping(array $fieldMapping)
{
@@ -2494,11 +2667,13 @@ class ClassMetadataInfo implements ClassMetadata
* INTERNAL:
* Adds a named query to this class.
*
* @deprecated
*
* @psalm-param array<string, mixed> $queryMapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $queryMapping
*/
public function addNamedQuery(array $queryMapping)
{
@@ -2506,6 +2681,14 @@ class ClassMetadataInfo implements ClassMetadata
throw MappingException::nameIsMandatoryForQueryMapping($this->name);
}
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8592',
'Named Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
$queryMapping['name'],
$this->name
);
if (isset($this->namedQueries[$queryMapping['name']])) {
throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
}
@@ -2529,11 +2712,13 @@ class ClassMetadataInfo implements ClassMetadata
* INTERNAL:
* Adds a named native query to this class.
*
* @deprecated
*
* @psalm-param array<string, mixed> $queryMapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $queryMapping
*/
public function addNamedNativeQuery(array $queryMapping)
{
@@ -2541,6 +2726,14 @@ class ClassMetadataInfo implements ClassMetadata
throw MappingException::nameIsMandatoryForQueryMapping($this->name);
}
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8592',
'Named Native Queries are deprecated, here "%s" on entity %s. Move the query logic into EntityRepository',
$queryMapping['name'],
$this->name
);
if (isset($this->namedNativeQueries[$queryMapping['name']])) {
throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
}
@@ -2572,11 +2765,11 @@ class ClassMetadataInfo implements ClassMetadata
* INTERNAL:
* Adds a sql result set mapping to this class.
*
* @psalm-param array<string, mixed> $resultMapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $resultMapping
*/
public function addSqlResultSetMapping(array $resultMapping)
{
@@ -2646,9 +2839,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Adds a one-to-many mapping.
*
* @return void
*
* @psalm-param array<string, mixed> $mapping The mapping.
*
* @return void
*/
public function mapOneToMany(array $mapping)
{
@@ -2662,9 +2855,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Adds a many-to-one mapping.
*
* @return void
*
* @psalm-param array<string, mixed> $mapping The mapping.
*
* @return void
*/
public function mapManyToOne(array $mapping)
{
@@ -2679,9 +2872,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Adds a many-to-many mapping.
*
* @return void
*
* @psalm-param array<string, mixed> $mapping The mapping.
*
* @return void
*/
public function mapManyToMany(array $mapping)
{
@@ -2695,11 +2888,11 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Stores the association mapping.
*
* @psalm-param array<string, mixed> $assocMapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $assocMapping
*/
protected function _storeAssociationMapping(array $assocMapping)
{
@@ -2714,10 +2907,9 @@ 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
*
* @return void
*
* @psalm-param class-string $repositoryClassName
*/
public function setCustomRepositoryClass($repositoryClassName)
{
@@ -2776,6 +2968,16 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function addLifecycleCallback($callback, $event)
{
if ($this->isEmbeddedClass) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/8381',
'Registering lifecycle callback %s on Embedded class %s is not doing anything and will throw exception in 3.0',
$event,
$this->name
);
}
if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
return;
}
@@ -2787,9 +2989,9 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the lifecycle callbacks for entities of this class.
* Any previously registered callbacks are overwritten.
*
* @return void
*
* @psalm-param array<string, list<string>> $callbacks
*
* @return void
*/
public function setLifecycleCallbacks(array $callbacks)
{
@@ -2803,6 +3005,8 @@ class ClassMetadataInfo implements ClassMetadata
* @param string $class The listener class.
* @param string $method The listener callback method.
*
* @return void
*
* @throws MappingException
*/
public function addEntityListener($eventName, $class, $method)
@@ -2834,11 +3038,11 @@ class ClassMetadataInfo implements ClassMetadata
*
* @see getDiscriminatorColumn()
*
* @psalm-param array<string, mixed> $columnDef
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $columnDef
*/
public function setDiscriminatorColumn($columnDef)
{
@@ -2871,9 +3075,9 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the discriminator values used by this class.
* Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
*
* @return void
*
* @psalm-param array<string, class-string> $map
*
* @return void
*/
public function setDiscriminatorMap(array $map)
{
@@ -2886,12 +3090,11 @@ class ClassMetadataInfo implements ClassMetadata
* Adds one entry of the discriminator map with a new class and corresponding name.
*
* @param string $name
* @psalm-param class-string $className
*
* @return void
*
* @throws MappingException
*
* @psalm-param class-string $className
*/
public function addDiscriminatorMapClass($name, $className)
{
@@ -3071,9 +3274,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Sets definition.
*
* @return void
*
* @psalm-param array<string, string> $definition
*
* @return void
*/
public function setCustomGeneratorDefinition(array $definition)
{
@@ -3093,11 +3296,11 @@ class ClassMetadataInfo implements ClassMetadata
* )
* </code>
*
* @psalm-param array<string, string> $definition
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, string> $definition
*/
public function setSequenceGeneratorDefinition(array $definition)
{
@@ -3125,11 +3328,11 @@ class ClassMetadataInfo implements ClassMetadata
* Sets the version field mapping used for versioning. Sets the default
* value to use depending on the column type.
*
* @psalm-param array<string, mixed> $mapping The version field mapping array.
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $mapping The version field mapping array.
*/
public function setVersionMapping(array &$mapping)
{
@@ -3345,11 +3548,10 @@ class ClassMetadataInfo implements ClassMetadata
}
/**
* @param string|null $className
* @param string|null $className
* @psalm-param ?class-string $className
*
* @return string|null null if the input value is null
*
* @psalm-param ?class-string $className
*/
public function fullyQualifiedClassName($className)
{
@@ -3381,19 +3583,26 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Map Embedded Class
*
* @psalm-param array<string, mixed> $mapping
*
* @return void
*
* @throws MappingException
*
* @psalm-param array<string, mixed> $mapping
*/
public function mapEmbedded(array $mapping)
{
$this->assertFieldNotMapped($mapping['fieldName']);
if (! isset($mapping['class']) && $this->isTypedProperty($mapping['fieldName'])) {
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
if ($type instanceof ReflectionNamedType) {
$mapping['class'] = $type->getName();
}
}
$this->embeddedClasses[$mapping['fieldName']] = [
'class' => $this->fullyQualifiedClassName($mapping['class']),
'columnPrefix' => $mapping['columnPrefix'],
'columnPrefix' => $mapping['columnPrefix'] ?? null,
'declaredField' => $mapping['declaredField'] ?? null,
'originalField' => $mapping['originalField'] ?? null,
];
@@ -3403,6 +3612,8 @@ class ClassMetadataInfo implements ClassMetadata
* Inline the embeddable class
*
* @param string $property
*
* @return void
*/
public function inlineEmbeddable($property, ClassMetadataInfo $embeddable)
{

View File

@@ -20,17 +20,22 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target({"PROPERTY","ANNOTATION"})
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Column implements Annotation
{
/** @var string */
public $name;
/** @var mixed */
public $type = 'string';
public $type;
/** @var int */
public $length;
@@ -55,9 +60,34 @@ final class Column implements Annotation
/** @var bool */
public $nullable = false;
/** @var array */
/** @var array<string,mixed> */
public $options = [];
/** @var string */
public $columnDefinition;
/**
* @param array<string,mixed> $options
*/
public function __construct(
?string $name = null,
?string $type = null,
?int $length = null,
?int $precision = null,
?int $scale = null,
bool $unique = false,
bool $nullable = false,
array $options = [],
?string $columnDefinition = null
) {
$this->name = $name;
$this->type = $type;
$this->length = $length;
$this->precision = $precision;
$this->scale = $scale;
$this->unique = $unique;
$this->nullable = $nullable;
$this->options = $options;
$this->columnDefinition = $columnDefinition;
}
}

View File

@@ -20,12 +20,22 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class CustomIdGenerator implements Annotation
{
/** @var string */
public $class;
public function __construct(?string $class = null)
{
$this->class = $class;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class DiscriminatorColumn implements Annotation
{
/** @var string */
@@ -44,4 +49,16 @@ final class DiscriminatorColumn implements Annotation
/** @var string */
public $columnDefinition;
public function __construct(
?string $name = null,
?string $type = null,
?int $length = null,
?string $columnDefinition = null
) {
$this->name = $name;
$this->type = $type;
$this->length = $length;
$this->columnDefinition = $columnDefinition;
}
}

View File

@@ -20,12 +20,23 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class DiscriminatorMap implements Annotation
{
/** @var array<string> */
public $value;
/** @param array<string> $value */
public function __construct(array $value)
{
$this->value = $value;
}
}

View File

@@ -35,6 +35,7 @@ use UnexpectedValueException;
use function class_exists;
use function constant;
use function count;
use function defined;
use function get_class;
use function is_array;
@@ -110,7 +111,28 @@ class AnnotationDriver extends AbstractAnnotationDriver
if ($tableAnnot->indexes !== null) {
foreach ($tableAnnot->indexes as $indexAnnot) {
$index = ['columns' => $indexAnnot->columns];
$index = [];
if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}
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 (! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
@@ -130,7 +152,28 @@ class AnnotationDriver extends AbstractAnnotationDriver
if ($tableAnnot->uniqueConstraints !== null) {
foreach ($tableAnnot->uniqueConstraints as $uniqueConstraintAnnot) {
$uniqueConstraint = ['columns' => $uniqueConstraintAnnot->columns];
$uniqueConstraint = [];
if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}
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 (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
@@ -156,7 +199,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
$cacheAnnot = $classAnnotations[Mapping\Cache::class];
$cacheMap = [
'region' => $cacheAnnot->region,
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
];
$metadata->enableCache($cacheMap);
@@ -302,7 +345,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
$mapping['cache'] = $metadata->getAssociationCacheDefaults(
$mapping['fieldName'],
[
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAnnot->usage),
'region' => $cacheAnnot->region,
]
);
@@ -327,10 +370,6 @@ class AnnotationDriver extends AbstractAnnotationDriver
// @Column, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany
$columnAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Column::class);
if ($columnAnnot) {
if ($columnAnnot->type === null) {
throw MappingException::propertyTypeIsRequired($className, $property->getName());
}
$mapping = $this->columnToArray($property->getName(), $columnAnnot);
$idAnnot = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
@@ -488,7 +527,6 @@ class AnnotationDriver extends AbstractAnnotationDriver
/**
* @param mixed[] $joinColumns
*
* @psalm-param array<string, mixed> $mapping
*/
private function loadRelationShipMapping(
@@ -598,14 +636,11 @@ class AnnotationDriver extends AbstractAnnotationDriver
/**
* Attempts to resolve the fetch mode.
*
* @param string $className The class name.
* @param string $fetchMode The fetch mode.
*
* @return int The fetch mode as defined in ClassMetadata.
* @psalm-return \Doctrine\ORM\Mapping\ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getFetchMode($className, $fetchMode)
private function getFetchMode(string $className, string $fetchMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
throw MappingException::invalidFetchMode($className, $fetchMode);
@@ -618,8 +653,9 @@ class AnnotationDriver extends AbstractAnnotationDriver
* Parses the given method.
*
* @return callable[]
* @psalm-return list<callable-array>
*/
private function getMethodCallbacks(ReflectionMethod $method)
private function getMethodCallbacks(ReflectionMethod $method): array
{
$callbacks = [];
$annotations = $this->reader->getMethodAnnotations($method);
@@ -665,7 +701,6 @@ class AnnotationDriver extends AbstractAnnotationDriver
* Parse the given JoinColumn as array
*
* @return mixed[]
*
* @psalm-return array{
* name: string,
* unique: bool,
@@ -675,7 +710,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
* referencedColumnName: string
* }
*/
private function joinColumnToArray(Mapping\JoinColumn $joinColumn)
private function joinColumnToArray(Mapping\JoinColumn $joinColumn): array
{
return [
'name' => $joinColumn->name,
@@ -690,10 +725,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
/**
* Parse the given Column as array
*
* @param string $fieldName
*
* @return mixed[]
*
* @psalm-return array{
* fieldName: string,
* type: mixed,
@@ -707,7 +739,7 @@ class AnnotationDriver extends AbstractAnnotationDriver
* columnDefinition?: string
* }
*/
private function columnToArray($fieldName, Mapping\Column $column)
private function columnToArray(string $fieldName, Mapping\Column $column): array
{
$mapping = [
'fieldName' => $fieldName,

View File

@@ -0,0 +1,571 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\ORM\Events;
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;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function assert;
use function class_exists;
use function constant;
use function count;
use function defined;
class AttributeDriver extends AnnotationDriver
{
/** @var array<string,int> */
// @phpcs:ignore
protected $entityAnnotationClasses = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
/**
* @param array<string> $paths
*/
public function __construct(array $paths)
{
parent::__construct(new AttributeReader(), $paths);
}
public function loadMetadataForClass($className, ClassMetadata $metadata): void
{
assert($metadata instanceof ClassMetadataInfo);
$reflectionClass = $metadata->getReflectionClass();
$classAttributes = $this->reader->getClassAnnotations($reflectionClass);
// Evaluate Entity annotation
if (isset($classAttributes[Mapping\Entity::class])) {
$entityAttribute = $classAttributes[Mapping\Entity::class];
if ($entityAttribute->repositoryClass !== null) {
$metadata->setCustomRepositoryClass($entityAttribute->repositoryClass);
}
if ($entityAttribute->readOnly) {
$metadata->markReadOnly();
}
} elseif (isset($classAttributes[Mapping\MappedSuperclass::class])) {
$mappedSuperclassAttribute = $classAttributes[Mapping\MappedSuperclass::class];
$metadata->setCustomRepositoryClass($mappedSuperclassAttribute->repositoryClass);
$metadata->isMappedSuperclass = true;
} elseif (isset($classAttributes[Mapping\Embeddable::class])) {
$metadata->isEmbeddedClass = true;
} else {
throw MappingException::classIsNotAValidEntityOrMappedSuperClass($className);
}
$primaryTable = [];
if (isset($classAttributes[Mapping\Table::class])) {
$tableAnnot = $classAttributes[Mapping\Table::class];
$primaryTable['name'] = $tableAnnot->name;
$primaryTable['schema'] = $tableAnnot->schema;
if ($tableAnnot->options) {
$primaryTable['options'] = $tableAnnot->options;
}
}
if (isset($classAttributes[Mapping\Index::class])) {
foreach ($classAttributes[Mapping\Index::class] as $idx => $indexAnnot) {
$index = [];
if (! empty($indexAnnot->columns)) {
$index['columns'] = $indexAnnot->columns;
}
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 ?? $idx)
);
}
if (! empty($indexAnnot->flags)) {
$index['flags'] = $indexAnnot->flags;
}
if (! empty($indexAnnot->options)) {
$index['options'] = $indexAnnot->options;
}
if (! empty($indexAnnot->name)) {
$primaryTable['indexes'][$indexAnnot->name] = $index;
} else {
$primaryTable['indexes'][] = $index;
}
}
}
if (isset($classAttributes[Mapping\UniqueConstraint::class])) {
foreach ($classAttributes[Mapping\UniqueConstraint::class] as $idx => $uniqueConstraintAnnot) {
$uniqueConstraint = [];
if (! empty($uniqueConstraintAnnot->columns)) {
$uniqueConstraint['columns'] = $uniqueConstraintAnnot->columns;
}
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 ?? $idx)
);
}
if (! empty($uniqueConstraintAnnot->options)) {
$uniqueConstraint['options'] = $uniqueConstraintAnnot->options;
}
if (! empty($uniqueConstraintAnnot->name)) {
$primaryTable['uniqueConstraints'][$uniqueConstraintAnnot->name] = $uniqueConstraint;
} else {
$primaryTable['uniqueConstraints'][] = $uniqueConstraint;
}
}
}
$metadata->setPrimaryTable($primaryTable);
// Evaluate @Cache annotation
if (isset($classAttributes[Mapping\Cache::class])) {
$cacheAttribute = $classAttributes[Mapping\Cache::class];
$cacheMap = [
'region' => $cacheAttribute->region,
'usage' => constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage),
];
$metadata->enableCache($cacheMap);
}
// Evaluate InheritanceType annotation
if (isset($classAttributes[Mapping\InheritanceType::class])) {
$inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class];
$metadata->setInheritanceType(
constant('Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_' . $inheritanceTypeAttribute->value)
);
if ($metadata->inheritanceType !== Mapping\ClassMetadata::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn annotation
if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
$discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
$metadata->setDiscriminatorColumn(
[
'name' => $discrColumnAttribute->name,
'type' => $discrColumnAttribute->type ?: 'string',
'length' => $discrColumnAttribute->length ?: 255,
'columnDefinition' => $discrColumnAttribute->columnDefinition,
]
);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
// Evaluate DiscriminatorMap annotation
if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
$discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class];
$metadata->setDiscriminatorMap($discrMapAttribute->value);
}
}
}
// Evaluate DoctrineChangeTrackingPolicy annotation
if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) {
$changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class];
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value));
}
foreach ($reflectionClass->getProperties() as $property) {
assert($property instanceof ReflectionProperty);
if (
$metadata->isMappedSuperclass && ! $property->isPrivate()
||
$metadata->isInheritedField($property->name)
||
$metadata->isInheritedAssociation($property->name)
||
$metadata->isInheritedEmbeddedClass($property->name)
) {
continue;
}
$mapping = [];
$mapping['fieldName'] = $property->getName();
// Evaluate @Cache annotation
$cacheAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class);
if ($cacheAttribute !== null) {
assert($cacheAttribute instanceof Mapping\Cache);
$mapping['cache'] = $metadata->getAssociationCacheDefaults(
$mapping['fieldName'],
[
'usage' => (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $cacheAttribute->usage),
'region' => $cacheAttribute->region,
]
);
}
// Check for JoinColumn/JoinColumns annotations
$joinColumns = [];
$joinColumnAttributes = $this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class);
foreach ($joinColumnAttributes as $joinColumnAttribute) {
$joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
}
// Field can only be attributed with one of:
// Column, OneToOne, OneToMany, ManyToOne, ManyToMany, Embedded
$columnAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Column::class);
$oneToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToOne::class);
$oneToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OneToMany::class);
$manyToOneAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToOne::class);
$manyToManyAttribute = $this->reader->getPropertyAnnotation($property, Mapping\ManyToMany::class);
$embeddedAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Embedded::class);
if ($columnAttribute !== null) {
$mapping = $this->columnToArray($property->getName(), $columnAttribute);
if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) {
$mapping['id'] = true;
}
$generatedValueAttribute = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class);
if ($generatedValueAttribute !== null) {
$metadata->setIdGeneratorType(constant('Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_' . $generatedValueAttribute->strategy));
}
if ($this->reader->getPropertyAnnotation($property, Mapping\Version::class)) {
$metadata->setVersionMapping($mapping);
}
$metadata->mapField($mapping);
// Check for SequenceGenerator/TableGenerator definition
$seqGeneratorAttribute = $this->reader->getPropertyAnnotation($property, Mapping\SequenceGenerator::class);
$customGeneratorAttribute = $this->reader->getPropertyAnnotation($property, Mapping\CustomIdGenerator::class);
if ($seqGeneratorAttribute !== null) {
$metadata->setSequenceGeneratorDefinition(
[
'sequenceName' => $seqGeneratorAttribute->sequenceName,
'allocationSize' => $seqGeneratorAttribute->allocationSize,
'initialValue' => $seqGeneratorAttribute->initialValue,
]
);
} elseif ($customGeneratorAttribute !== null) {
$metadata->setCustomGeneratorDefinition(
[
'class' => $customGeneratorAttribute->class,
]
);
}
} elseif ($oneToOneAttribute !== null) {
if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) {
$mapping['id'] = true;
}
$mapping['targetEntity'] = $oneToOneAttribute->targetEntity;
$mapping['joinColumns'] = $joinColumns;
$mapping['mappedBy'] = $oneToOneAttribute->mappedBy;
$mapping['inversedBy'] = $oneToOneAttribute->inversedBy;
$mapping['cascade'] = $oneToOneAttribute->cascade;
$mapping['orphanRemoval'] = $oneToOneAttribute->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $oneToOneAttribute->fetch);
$metadata->mapOneToOne($mapping);
} elseif ($oneToManyAttribute !== null) {
$mapping['mappedBy'] = $oneToManyAttribute->mappedBy;
$mapping['targetEntity'] = $oneToManyAttribute->targetEntity;
$mapping['cascade'] = $oneToManyAttribute->cascade;
$mapping['indexBy'] = $oneToManyAttribute->indexBy;
$mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch);
$orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
if ($orderByAttribute !== null) {
$mapping['orderBy'] = $orderByAttribute->value;
}
$metadata->mapOneToMany($mapping);
} elseif ($manyToOneAttribute !== null) {
$idAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
if ($idAttribute !== null) {
$mapping['id'] = true;
}
$mapping['joinColumns'] = $joinColumns;
$mapping['cascade'] = $manyToOneAttribute->cascade;
$mapping['inversedBy'] = $manyToOneAttribute->inversedBy;
$mapping['targetEntity'] = $manyToOneAttribute->targetEntity;
$mapping['fetch'] = $this->getFetchMode($className, $manyToOneAttribute->fetch);
$metadata->mapManyToOne($mapping);
} elseif ($manyToManyAttribute !== null) {
$joinTable = [];
$joinTableAttribute = $this->reader->getPropertyAnnotation($property, Mapping\JoinTable::class);
if ($joinTableAttribute !== null) {
$joinTable = [
'name' => $joinTableAttribute->name,
'schema' => $joinTableAttribute->schema,
];
}
foreach ($this->reader->getPropertyAnnotation($property, Mapping\JoinColumn::class) as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($this->reader->getPropertyAnnotation($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
$mapping['joinTable'] = $joinTable;
$mapping['targetEntity'] = $manyToManyAttribute->targetEntity;
$mapping['mappedBy'] = $manyToManyAttribute->mappedBy;
$mapping['inversedBy'] = $manyToManyAttribute->inversedBy;
$mapping['cascade'] = $manyToManyAttribute->cascade;
$mapping['indexBy'] = $manyToManyAttribute->indexBy;
$mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch);
$orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
if ($orderByAttribute !== null) {
$mapping['orderBy'] = $orderByAttribute->value;
}
$metadata->mapManyToMany($mapping);
} elseif ($embeddedAttribute !== null) {
$mapping['class'] = $embeddedAttribute->class;
$mapping['columnPrefix'] = $embeddedAttribute->columnPrefix;
$metadata->mapEmbedded($mapping);
}
}
// Evaluate AttributeOverrides annotation
if (isset($classAttributes[Mapping\AttributeOverride::class])) {
foreach ($classAttributes[Mapping\AttributeOverride::class] as $attributeOverrideAttribute) {
$attributeOverride = $this->columnToArray($attributeOverrideAttribute->name, $attributeOverrideAttribute->column);
$metadata->setAttributeOverride($attributeOverrideAttribute->name, $attributeOverride);
}
}
// Evaluate EntityListeners annotation
if (isset($classAttributes[Mapping\EntityListeners::class])) {
$entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class];
foreach ($entityListenersAttribute->value as $item) {
$listenerClassName = $metadata->fullyQualifiedClassName($item);
if (! class_exists($listenerClassName)) {
throw MappingException::entityListenerClassNotFound($listenerClassName, $className);
}
$hasMapping = false;
$listenerClass = new ReflectionClass($listenerClassName);
foreach ($listenerClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
assert($method instanceof ReflectionMethod);
// find method callbacks.
$callbacks = $this->getMethodCallbacks($method);
$hasMapping = $hasMapping ?: ! empty($callbacks);
foreach ($callbacks as $value) {
$metadata->addEntityListener($value[1], $listenerClassName, $value[0]);
}
}
// Evaluate the listener using naming convention.
if (! $hasMapping) {
EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName);
}
}
}
// Evaluate @HasLifecycleCallbacks annotation
if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) {
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
assert($method instanceof ReflectionMethod);
foreach ($this->getMethodCallbacks($method) as $value) {
$metadata->addLifecycleCallback($value[0], $value[1]);
}
}
}
}
/**
* Attempts to resolve the fetch mode.
*
* @param string $className The class name.
* @param string $fetchMode The fetch mode.
*
* @return int The fetch mode as defined in ClassMetadata.
*
* @throws MappingException If the fetch mode is not valid.
*/
private function getFetchMode(string $className, string $fetchMode): int
{
if (! defined('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode)) {
throw MappingException::invalidFetchMode($className, $fetchMode);
}
return constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . $fetchMode);
}
/**
* Parses the given method.
*
* @return callable[]
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
$callbacks = [];
$attributes = $this->reader->getMethodAnnotations($method);
foreach ($attributes as $attribute) {
if ($attribute instanceof Mapping\PrePersist) {
$callbacks[] = [$method->name, Events::prePersist];
}
if ($attribute instanceof Mapping\PostPersist) {
$callbacks[] = [$method->name, Events::postPersist];
}
if ($attribute instanceof Mapping\PreUpdate) {
$callbacks[] = [$method->name, Events::preUpdate];
}
if ($attribute instanceof Mapping\PostUpdate) {
$callbacks[] = [$method->name, Events::postUpdate];
}
if ($attribute instanceof Mapping\PreRemove) {
$callbacks[] = [$method->name, Events::preRemove];
}
if ($attribute instanceof Mapping\PostRemove) {
$callbacks[] = [$method->name, Events::postRemove];
}
if ($attribute instanceof Mapping\PostLoad) {
$callbacks[] = [$method->name, Events::postLoad];
}
if ($attribute instanceof Mapping\PreFlush) {
$callbacks[] = [$method->name, Events::preFlush];
}
}
return $callbacks;
}
/**
* Parse the given JoinColumn as array
*
* @param Mapping\JoinColumn|Mapping\InverseJoinColumn $joinColumn
*
* @return mixed[]
* @psalm-return array{
* name: string,
* unique: bool,
* nullable: bool,
* onDelete: mixed,
* columnDefinition: string,
* referencedColumnName: string
* }
*/
private function joinColumnToArray($joinColumn): array
{
return [
'name' => $joinColumn->name,
'unique' => $joinColumn->unique,
'nullable' => $joinColumn->nullable,
'onDelete' => $joinColumn->onDelete,
'columnDefinition' => $joinColumn->columnDefinition,
'referencedColumnName' => $joinColumn->referencedColumnName,
];
}
/**
* Parse the given Column as array
*
* @return mixed[]
* @psalm-return array{
* fieldName: string,
* type: mixed,
* scale: int,
* length: int,
* unique: bool,
* nullable: bool,
* precision: int,
* options?: mixed[],
* columnName?: string,
* columnDefinition?: string
* }
*/
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,
];
if ($column->options) {
$mapping['options'] = $column->options;
}
if (isset($column->name)) {
$mapping['columnName'] = $column->name;
}
if (isset($column->columnDefinition)) {
$mapping['columnDefinition'] = $column->columnDefinition;
}
return $mapping;
}
}

View File

@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Attribute;
use Doctrine\ORM\Mapping\Annotation;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function count;
use function is_subclass_of;
/**
* @internal
*/
final class AttributeReader
{
/** @var array<string,bool> */
private array $isRepeatableAttribute = [];
/** @return array<object> */
public function getClassAnnotations(ReflectionClass $class): array
{
return $this->convertToAttributeInstances($class->getAttributes());
}
/** @return array<object>|object|null */
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
return $this->getClassAnnotations($class)[$annotationName] ?? ($this->isRepeatable($annotationName) ? [] : null);
}
/** @return array<object> */
public function getMethodAnnotations(ReflectionMethod $method): array
{
return $this->convertToAttributeInstances($method->getAttributes());
}
/** @return array<object>|object|null */
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
return $this->getMethodAnnotations($method)[$annotationName] ?? ($this->isRepeatable($annotationName) ? [] : null);
}
/** @return array<object> */
public function getPropertyAnnotations(ReflectionProperty $property): array
{
return $this->convertToAttributeInstances($property->getAttributes());
}
/** @return array<object>|object|null */
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
return $this->getPropertyAnnotations($property)[$annotationName] ?? ($this->isRepeatable($annotationName) ? [] : null);
}
/**
* @param array<object> $attributes
*
* @return array<Annotation>
*/
private function convertToAttributeInstances(array $attributes): array
{
$instances = [];
foreach ($attributes as $attribute) {
// Make sure we only get Doctrine Annotations
if (! is_subclass_of($attribute->getName(), Annotation::class)) {
continue;
}
$instance = $attribute->newInstance();
if ($this->isRepeatable($attribute->getName())) {
$instances[$attribute->getName()][] = $instance;
} else {
$instances[$attribute->getName()] = $instance;
}
}
return $instances;
}
private function isRepeatable(string $attributeClassName): bool
{
if (isset($this->isRepeatableAttribute[$attributeClassName])) {
return $this->isRepeatableAttribute[$attributeClassName];
}
$reflectionClass = new ReflectionClass($attributeClassName);
$attribute = $reflectionClass->getAttributes()[0]->newInstance();
return $this->isRepeatableAttribute[$attributeClassName] = ($attribute->flags & Attribute::IS_REPEATABLE) > 0;
}
}

View File

@@ -145,10 +145,10 @@ class DatabaseDriver implements MappingDriver
/**
* Sets tables manually instead of relying on the reverse engineering capabilities of SchemaManager.
*
* @return void
*
* @psalm-param list<Table> $entityTables
* @psalm-param list<Table> $manyToManyTables
*
* @return void
*/
public function setTables($entityTables, $manyToManyTables)
{
@@ -259,11 +259,9 @@ class DatabaseDriver implements MappingDriver
}
/**
* @return void
*
* @throws MappingException
*/
private function reverseEngineerMappingFromDatabase()
private function reverseEngineerMappingFromDatabase(): void
{
if ($this->tables !== null) {
return;
@@ -316,7 +314,7 @@ class DatabaseDriver implements MappingDriver
/**
* Build indexes from a class metadata.
*/
private function buildIndexes(ClassMetadataInfo $metadata)
private function buildIndexes(ClassMetadataInfo $metadata): void
{
$tableName = $metadata->table['name'];
$indexes = $this->tables[$tableName]->getIndexes();
@@ -339,7 +337,7 @@ class DatabaseDriver implements MappingDriver
/**
* Build field mapping from class metadata.
*/
private function buildFieldMappings(ClassMetadataInfo $metadata)
private function buildFieldMappings(ClassMetadataInfo $metadata): void
{
$tableName = $metadata->table['name'];
$columns = $this->tables[$tableName]->getColumns();
@@ -382,8 +380,6 @@ class DatabaseDriver implements MappingDriver
/**
* Build field mapping from a schema column definition
*
* @param string $tableName
*
* @psalm-return array{
* fieldName: string,
* columnName: string,
@@ -400,7 +396,7 @@ class DatabaseDriver implements MappingDriver
* length?: int|null
* }
*/
private function buildFieldMapping($tableName, Column $column)
private function buildFieldMapping(string $tableName, Column $column): array
{
$fieldMapping = [
'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false),
@@ -453,6 +449,8 @@ class DatabaseDriver implements MappingDriver
/**
* Build to one (one to one, many to one) association mapping from class metadata.
*
* @return void
*/
private function buildToOneAssociationMappings(ClassMetadataInfo $metadata)
{
@@ -498,10 +496,9 @@ class DatabaseDriver implements MappingDriver
* Retrieve schema table definition foreign keys.
*
* @return ForeignKeyConstraint[]
*
* @psalm-return array<string, ForeignKeyConstraint>
*/
private function getTableForeignKeys(Table $table)
private function getTableForeignKeys(Table $table): array
{
return $this->_sm->getDatabasePlatform()->supportsForeignKeyConstraints()
? $table->getForeignKeys()
@@ -513,7 +510,7 @@ class DatabaseDriver implements MappingDriver
*
* @return string[]
*/
private function getTablePrimaryKeys(Table $table)
private function getTablePrimaryKeys(Table $table): array
{
try {
return $table->getPrimaryKey()->getColumns();
@@ -527,11 +524,9 @@ class DatabaseDriver implements MappingDriver
/**
* Returns the mapped class name for a table if it exists. Otherwise return "classified" version.
*
* @param string $tableName
*
* @return string
* @psalm-return class-string
*/
private function getClassNameForTable($tableName)
private function getClassNameForTable(string $tableName): string
{
if (isset($this->classNamesForTables[$tableName])) {
return $this->namespace . $this->classNamesForTables[$tableName];
@@ -543,14 +538,13 @@ class DatabaseDriver implements MappingDriver
/**
* Return the mapped field name for a column, if it exists. Otherwise return camelized version.
*
* @param string $tableName
* @param string $columnName
* @param bool $fk Whether the column is a foreignkey or not.
*
* @return string
* @param bool $fk Whether the column is a foreignkey or not.
*/
private function getFieldNameForColumn($tableName, $columnName, $fk = false)
{
private function getFieldNameForColumn(
string $tableName,
string $columnName,
bool $fk = false
): string {
if (isset($this->fieldNamesForColumns[$tableName]) && isset($this->fieldNamesForColumns[$tableName][$columnName])) {
return $this->fieldNamesForColumns[$tableName][$columnName];
}

View File

@@ -31,6 +31,7 @@ use SimpleXMLElement;
use function assert;
use function constant;
use function count;
use function defined;
use function explode;
use function file_get_contents;
@@ -212,7 +213,28 @@ class XmlDriver extends FileDriver
if (isset($xmlRoot->indexes)) {
$metadata->table['indexes'] = [];
foreach ($xmlRoot->indexes->index as $indexXml) {
$index = ['columns' => explode(',', (string) $indexXml['columns'])];
$index = [];
if (isset($indexXml['columns']) && ! empty($indexXml['columns'])) {
$index['columns'] = explode(',', (string) $indexXml['columns']);
}
if (isset($indexXml['fields'])) {
$index['fields'] = explode(',', (string) $indexXml['fields']);
}
if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
(string) ($indexXml['name'] ?? count($metadata->table['indexes']))
);
}
if (isset($indexXml['flags'])) {
$index['flags'] = explode(',', (string) $indexXml['flags']);
@@ -234,7 +256,28 @@ class XmlDriver extends FileDriver
if (isset($xmlRoot->{'unique-constraints'})) {
$metadata->table['uniqueConstraints'] = [];
foreach ($xmlRoot->{'unique-constraints'}->{'unique-constraint'} as $uniqueXml) {
$unique = ['columns' => explode(',', (string) $uniqueXml['columns'])];
$unique = [];
if (isset($uniqueXml['columns']) && ! empty($uniqueXml['columns'])) {
$unique['columns'] = explode(',', (string) $uniqueXml['columns']);
}
if (isset($uniqueXml['fields'])) {
$unique['fields'] = explode(',', (string) $uniqueXml['fields']);
}
if (
isset($unique['columns'], $unique['fields'])
|| (
! isset($unique['columns'])
&& ! isset($unique['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
(string) ($uniqueXml['name'] ?? count($metadata->table['uniqueConstraints']))
);
}
if (isset($uniqueXml->options)) {
$unique['options'] = $this->parseOptions($uniqueXml->options->children());
@@ -281,7 +324,7 @@ class XmlDriver extends FileDriver
$mapping = [
'fieldName' => (string) $embeddedMapping['name'],
'class' => (string) $embeddedMapping['class'],
'class' => isset($embeddedMapping['class']) ? (string) $embeddedMapping['class'] : null,
'columnPrefix' => $useColumnPrefix ? $columnPrefix : false,
];
@@ -366,9 +409,12 @@ class XmlDriver extends FileDriver
foreach ($xmlRoot->{'one-to-one'} as $oneToOneElement) {
$mapping = [
'fieldName' => (string) $oneToOneElement['field'],
'targetEntity' => (string) $oneToOneElement['target-entity'],
];
if (isset($oneToOneElement['target-entity'])) {
$mapping['targetEntity'] = (string) $oneToOneElement['target-entity'];
}
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
@@ -419,10 +465,13 @@ class XmlDriver extends FileDriver
foreach ($xmlRoot->{'one-to-many'} as $oneToManyElement) {
$mapping = [
'fieldName' => (string) $oneToManyElement['field'],
'targetEntity' => (string) $oneToManyElement['target-entity'],
'mappedBy' => (string) $oneToManyElement['mapped-by'],
];
if (isset($oneToManyElement['target-entity'])) {
$mapping['targetEntity'] = (string) $oneToManyElement['target-entity'];
}
if (isset($oneToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $oneToManyElement['fetch']);
}
@@ -466,9 +515,12 @@ class XmlDriver extends FileDriver
foreach ($xmlRoot->{'many-to-one'} as $manyToOneElement) {
$mapping = [
'fieldName' => (string) $manyToOneElement['field'],
'targetEntity' => (string) $manyToOneElement['target-entity'],
];
if (isset($manyToOneElement['target-entity'])) {
$mapping['targetEntity'] = (string) $manyToOneElement['target-entity'];
}
if (isset($associationIds[$mapping['fieldName']])) {
$mapping['id'] = true;
}
@@ -511,9 +563,12 @@ class XmlDriver extends FileDriver
foreach ($xmlRoot->{'many-to-many'} as $manyToManyElement) {
$mapping = [
'fieldName' => (string) $manyToManyElement['field'],
'targetEntity' => (string) $manyToManyElement['target-entity'],
];
if (isset($manyToManyElement['target-entity'])) {
$mapping['targetEntity'] = (string) $manyToManyElement['target-entity'];
}
if (isset($manyToManyElement['fetch'])) {
$mapping['fetch'] = constant('Doctrine\ORM\Mapping\ClassMetadata::FETCH_' . (string) $manyToManyElement['fetch']);
}
@@ -680,8 +735,9 @@ class XmlDriver extends FileDriver
* @param SimpleXMLElement $options The XML element.
*
* @return mixed[] The options array.
* @psalm-return array<int|string, array<int|string, mixed|string>|bool|string>
*/
private function parseOptions(SimpleXMLElement $options)
private function parseOptions(SimpleXMLElement $options): array
{
$array = [];
@@ -714,7 +770,6 @@ class XmlDriver extends FileDriver
* @param SimpleXMLElement $joinColumnElement The XML element.
*
* @return mixed[] The mapping array.
*
* @psalm-return array{
* name: string,
* referencedColumnName: string,
@@ -724,7 +779,7 @@ class XmlDriver extends FileDriver
* columnDefinition?: string
* }
*/
private function joinColumnToArray(SimpleXMLElement $joinColumnElement)
private function joinColumnToArray(SimpleXMLElement $joinColumnElement): array
{
$joinColumn = [
'name' => (string) $joinColumnElement['name'],
@@ -754,7 +809,6 @@ class XmlDriver extends FileDriver
* Parses the given field as array.
*
* @return mixed[]
*
* @psalm-return array{
* fieldName: string,
* type?: string,
@@ -769,7 +823,7 @@ class XmlDriver extends FileDriver
* options?: array
* }
*/
private function columnToArray(SimpleXMLElement $fieldMapping)
private function columnToArray(SimpleXMLElement $fieldMapping): array
{
$mapping = [
'fieldName' => (string) $fieldMapping['name'],
@@ -822,10 +876,9 @@ class XmlDriver extends FileDriver
* Parse / Normalize the cache configuration
*
* @return mixed[]
*
* @psalm-return array{usage: mixed, region: string|null}
* @psalm-return array{usage: int|null, region?: string}
*/
private function cacheToArray(SimpleXMLElement $cacheMapping)
private function cacheToArray(SimpleXMLElement $cacheMapping): array
{
$region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null;
$usage = isset($cacheMapping['usage']) ? strtoupper($cacheMapping['usage']) : null;
@@ -835,7 +888,7 @@ class XmlDriver extends FileDriver
}
if ($usage) {
$usage = constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
$usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
}
return [
@@ -850,10 +903,9 @@ class XmlDriver extends FileDriver
* @param SimpleXMLElement $cascadeElement The cascade element.
*
* @return string[] The list of cascade options.
*
* @psalm-return list<string>
*/
private function getCascadeMappings(SimpleXMLElement $cascadeElement)
private function getCascadeMappings(SimpleXMLElement $cascadeElement): array
{
$cascades = [];
foreach ($cascadeElement->children() as $action) {
@@ -879,16 +931,19 @@ class XmlDriver extends FileDriver
if (isset($xmlElement->entity)) {
foreach ($xmlElement->entity as $entityElement) {
/** @psalm-var class-string */
$entityName = (string) $entityElement['name'];
$result[$entityName] = $entityElement;
}
} elseif (isset($xmlElement->{'mapped-superclass'})) {
foreach ($xmlElement->{'mapped-superclass'} as $mappedSuperClass) {
/** @psalm-var class-string */
$className = (string) $mappedSuperClass['name'];
$result[$className] = $mappedSuperClass;
}
} elseif (isset($xmlElement->embeddable)) {
foreach ($xmlElement->embeddable as $embeddableElement) {
/** @psalm-var class-string */
$embeddableName = (string) $embeddableElement['name'];
$result[$embeddableName] = $embeddableElement;
}

View File

@@ -20,6 +20,7 @@
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata as Metadata;
use Doctrine\ORM\Mapping\MappingException;
@@ -39,9 +40,6 @@ use function sprintf;
use function strlen;
use function strtoupper;
use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* The YamlDriver reads the mapping metadata from yaml schema files.
@@ -57,9 +55,10 @@ class YamlDriver extends FileDriver
*/
public function __construct($locator, $fileExtension = self::DEFAULT_FILE_EXTENSION)
{
@trigger_error(
'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to annotation or XML driver.',
E_USER_DEPRECATED
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8465',
'YAML mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to annotation or XML driver.'
);
parent::__construct($locator, $fileExtension);
@@ -229,10 +228,35 @@ class YamlDriver extends FileDriver
$indexYml['name'] = $name;
}
if (is_string($indexYml['columns'])) {
$index = ['columns' => array_map('trim', explode(',', $indexYml['columns']))];
} else {
$index = ['columns' => $indexYml['columns']];
$index = [];
if (isset($indexYml['columns'])) {
if (is_string($indexYml['columns'])) {
$index['columns'] = array_map('trim', explode(',', $indexYml['columns']));
} else {
$index['columns'] = $indexYml['columns'];
}
}
if (isset($indexYml['fields'])) {
if (is_string($indexYml['fields'])) {
$index['fields'] = array_map('trim', explode(',', $indexYml['fields']));
} else {
$index['fields'] = $indexYml['fields'];
}
}
if (
isset($index['columns'], $index['fields'])
|| (
! isset($index['columns'])
&& ! isset($index['fields'])
)
) {
throw MappingException::invalidIndexConfiguration(
$className,
$indexYml['name']
);
}
if (isset($indexYml['flags'])) {
@@ -258,10 +282,35 @@ class YamlDriver extends FileDriver
$uniqueYml['name'] = $name;
}
if (is_string($uniqueYml['columns'])) {
$unique = ['columns' => array_map('trim', explode(',', $uniqueYml['columns']))];
} else {
$unique = ['columns' => $uniqueYml['columns']];
$unique = [];
if (isset($uniqueYml['columns'])) {
if (is_string($uniqueYml['columns'])) {
$unique['columns'] = array_map('trim', explode(',', $uniqueYml['columns']));
} else {
$unique['columns'] = $uniqueYml['columns'];
}
}
if (isset($uniqueYml['fields'])) {
if (is_string($uniqueYml['fields'])) {
$unique['fields'] = array_map('trim', explode(',', $uniqueYml['fields']));
} else {
$unique['fields'] = $uniqueYml['fields'];
}
}
if (
isset($unique['columns'], $unique['fields'])
|| (
! isset($unique['columns'])
&& ! isset($unique['fields'])
)
) {
throw MappingException::invalidUniqueConstraintConfiguration(
$className,
$uniqueYml['name']
);
}
if (isset($uniqueYml['options'])) {
@@ -359,7 +408,7 @@ class YamlDriver extends FileDriver
foreach ($element['embedded'] as $name => $embeddedMapping) {
$mapping = [
'fieldName' => $name,
'class' => $embeddedMapping['class'],
'class' => $embeddedMapping['class'] ?? null,
'columnPrefix' => $embeddedMapping['columnPrefix'] ?? null,
];
$metadata->mapEmbedded($mapping);
@@ -371,7 +420,7 @@ class YamlDriver extends FileDriver
foreach ($element['oneToOne'] as $name => $oneToOneElement) {
$mapping = [
'fieldName' => $name,
'targetEntity' => $oneToOneElement['targetEntity'],
'targetEntity' => $oneToOneElement['targetEntity'] ?? null,
];
if (isset($associationIds[$mapping['fieldName']])) {
@@ -466,7 +515,7 @@ class YamlDriver extends FileDriver
foreach ($element['manyToOne'] as $name => $manyToOneElement) {
$mapping = [
'fieldName' => $name,
'targetEntity' => $manyToOneElement['targetEntity'],
'targetEntity' => $manyToOneElement['targetEntity'] ?? null,
];
if (isset($associationIds[$mapping['fieldName']])) {
@@ -689,8 +738,6 @@ class YamlDriver extends FileDriver
* Constructs a joinColumn mapping array based on the information
* found in the given join column element.
*
* @return mixed[] The mapping array.
*
* @psalm-param array{
* referencedColumnName?: mixed,
* name?: mixed,
@@ -700,6 +747,8 @@ class YamlDriver extends FileDriver
* onDelete?: mixed,
* columnDefinition?: mixed
* } $joinColumnElement The array join column element.
*
* @return mixed[] The mapping array.
* @psalm-return array{
* referencedColumnName?: string,
* name?: string,
@@ -747,8 +796,6 @@ class YamlDriver extends FileDriver
/**
* Parses the given column as array.
*
* @return mixed[]
*
* @psalm-param array{
* type?: string,
* column?: string,
@@ -760,11 +807,13 @@ class YamlDriver extends FileDriver
* version?: mixed,
* columnDefinition?: mixed
* }|null $column
*
* @return mixed[]
* @psalm-return array{
* fieldName: string,
* type?: string,
* columnName?: mixed,
* length?: mixed,
* columnName?: string,
* length?: int,
* precision?: mixed,
* scale?: mixed,
* unique?: bool,
@@ -832,13 +881,13 @@ class YamlDriver extends FileDriver
* Parse / Normalize the cache configuration
*
* @param mixed[] $cacheMapping
* @psalm-param array{usage: mixed, region: (string|null)} $cacheMapping
* @psalm-param array{usage: string, region?: string} $cacheMapping
*
* @return mixed[]
*
* @psalm-param array{usage: mixed, region: (string|null)} $cacheMapping
* @psalm-return array{usage: mixed, region: (string|null)}
* @psalm-return array{usage: int, region: string|null}
*/
private function cacheToArray($cacheMapping)
private function cacheToArray(array $cacheMapping): array
{
$region = isset($cacheMapping['region']) ? (string) $cacheMapping['region'] : null;
$usage = isset($cacheMapping['usage']) ? strtoupper($cacheMapping['usage']) : null;
@@ -848,7 +897,7 @@ class YamlDriver extends FileDriver
}
if ($usage) {
$usage = constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
$usage = (int) constant('Doctrine\ORM\Mapping\ClassMetadata::CACHE_USAGE_' . $usage);
}
return [

View File

@@ -20,10 +20,13 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
/**
* @Annotation
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class Embeddable implements Annotation
{
}

View File

@@ -20,18 +20,26 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Embedded implements Annotation
{
/**
* @Required
* @var string
*/
/** @var string|null */
public $class;
/** @var mixed */
/** @var string|bool|null */
public $columnPrefix;
public function __construct(?string $class = null, $columnPrefix = null)
{
$this->class = $class;
$this->columnPrefix = $columnPrefix;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class Entity implements Annotation
{
/** @var string */
@@ -31,4 +36,10 @@ final class Entity implements Annotation
/** @var bool */
public $readOnly = false;
public function __construct(?string $repositoryClass = null, bool $readOnly = false)
{
$this->repositoryClass = $repositoryClass;
$this->readOnly = $readOnly;
}
}

View File

@@ -20,13 +20,18 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* The EntityListeners annotation specifies the callback listener classes to be used for an entity or mapped superclass.
* The EntityListeners annotation may be applied to an entity class or mapped superclass.
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class EntityListeners implements Annotation
{
/**
@@ -35,4 +40,12 @@ final class EntityListeners implements Annotation
* @var array<string>
*/
public $value = [];
/**
* @param array<string> $value
*/
public function __construct(array $value = [])
{
$this->value = $value;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class GeneratedValue implements Annotation
{
/**
@@ -33,4 +38,9 @@ final class GeneratedValue implements Annotation
* @Enum({"AUTO", "SEQUENCE", "TABLE", "IDENTITY", "NONE", "UUID", "CUSTOM"})
*/
public $strategy = 'AUTO';
public function __construct(string $strategy = 'AUTO')
{
$this->strategy = $strategy;
}
}

View File

@@ -20,10 +20,13 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
/**
* @Annotation
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class HasLifecycleCallbacks implements Annotation
{
}

View File

@@ -20,10 +20,13 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
/**
* @Annotation
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Id implements Annotation
{
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("ANNOTATION")
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Index implements Annotation
{
/** @var string */
@@ -32,9 +37,32 @@ final class Index implements Annotation
/** @var array<string> */
public $columns;
/** @var array<string> */
public $fields;
/** @var array<string> */
public $flags;
/** @var array */
/** @var array<string,mixed> */
public $options;
/**
* @param array<string> $columns
* @param array<string> $fields
* @param array<string> $flags
* @param array<string> $options
*/
public function __construct(
?array $columns = null,
?array $fields = null,
?string $name = null,
?array $flags = null,
?array $options = null
) {
$this->columns = $columns;
$this->fields = $fields;
$this->name = $name;
$this->flags = $flags;
$this->options = $options;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class InheritanceType implements Annotation
{
/**
@@ -33,4 +38,9 @@ final class InheritanceType implements Annotation
* @Enum({"NONE", "JOINED", "SINGLE_TABLE", "TABLE_PER_CLASS"})
*/
public $value;
public function __construct(string $value)
{
$this->value = $value;
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\ORM\Mapping;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class InverseJoinColumn implements Annotation
{
/** @var string */
public $name;
/** @var string */
public $referencedColumnName = 'id';
/** @var bool */
public $unique = false;
/** @var bool */
public $nullable = true;
/** @var mixed */
public $onDelete;
/** @var string */
public $columnDefinition;
/**
* Field name used in non-object hydration (array/scalar).
*
* @var string
*/
public $fieldName;
public function __construct(
?string $name = null,
string $referencedColumnName = 'id',
bool $unique = false,
bool $nullable = true,
$onDelete = null,
?string $columnDefinition = null,
?string $fieldName = null
) {
$this->name = $name;
$this->referencedColumnName = $referencedColumnName;
$this->unique = $unique;
$this->nullable = $nullable;
$this->onDelete = $onDelete;
$this->columnDefinition = $columnDefinition;
$this->fieldName = $fieldName;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target({"PROPERTY","ANNOTATION"})
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class JoinColumn implements Annotation
{
/** @var string */
@@ -50,4 +55,22 @@ final class JoinColumn implements Annotation
* @var string
*/
public $fieldName;
public function __construct(
?string $name = null,
string $referencedColumnName = 'id',
bool $unique = false,
bool $nullable = true,
$onDelete = null,
?string $columnDefinition = null,
?string $fieldName = null
) {
$this->name = $name;
$this->referencedColumnName = $referencedColumnName;
$this->unique = $unique;
$this->nullable = $nullable;
$this->onDelete = $onDelete;
$this->columnDefinition = $columnDefinition;
$this->fieldName = $fieldName;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target({"PROPERTY","ANNOTATION"})
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class JoinTable implements Annotation
{
/** @var string */
@@ -37,4 +42,18 @@ final class JoinTable implements Annotation
/** @var array<\Doctrine\ORM\Mapping\JoinColumn> */
public $inverseJoinColumns = [];
public function __construct(
?string $name = null,
?string $schema = null,
$joinColumns = [],
$inverseJoinColumns = []
) {
$this->name = $name;
$this->schema = $schema;
$this->joinColumns = $joinColumns instanceof JoinColumn ? [$joinColumns] : $joinColumns;
$this->inverseJoinColumns = $inverseJoinColumns instanceof JoinColumn
? [$inverseJoinColumns]
: $inverseJoinColumns;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class ManyToMany implements Annotation
{
/** @var string */
@@ -51,4 +56,25 @@ final class ManyToMany implements Annotation
/** @var string */
public $indexBy;
/**
* @param array<string> $cascade
*/
public function __construct(
string $targetEntity,
?string $mappedBy = null,
?string $inversedBy = null,
?array $cascade = null,
string $fetch = 'LAZY',
bool $orphanRemoval = false,
?string $indexBy = null
) {
$this->targetEntity = $targetEntity;
$this->mappedBy = $mappedBy;
$this->inversedBy = $inversedBy;
$this->cascade = $cascade;
$this->fetch = $fetch;
$this->orphanRemoval = $orphanRemoval;
$this->indexBy = $indexBy;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class ManyToOne implements Annotation
{
/** @var string */
@@ -42,4 +47,19 @@ final class ManyToOne implements Annotation
/** @var string */
public $inversedBy;
/**
* @param array<string> $cascade
*/
public function __construct(
?string $targetEntity = null,
?array $cascade = null,
string $fetch = 'LAZY',
?string $inversedBy = null
) {
$this->targetEntity = $targetEntity;
$this->cascade = $cascade;
$this->fetch = $fetch;
$this->inversedBy = $inversedBy;
}
}

View File

@@ -20,12 +20,22 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class MappedSuperclass implements Annotation
{
/** @var string */
public $repositoryClass;
public function __construct(?string $repositoryClass = null)
{
$this->repositoryClass = $repositoryClass;
}
}

View File

@@ -278,14 +278,13 @@ class MappingException extends ORMException
return new self(sprintf("Result set mapping name on entity class '%s' is not defined.", $className));
}
/**
* @param string $fieldName
*
* @return MappingException
*/
public static function oneToManyRequiresMappedBy($fieldName)
public static function oneToManyRequiresMappedBy(string $entityName, string $fieldName): MappingException
{
return new self(sprintf("OneToMany mapping on field '%s' requires the 'mappedBy' attribute.", $fieldName));
return new self(sprintf(
"OneToMany mapping on entity '%s' field '%s' requires the 'mappedBy' attribute.",
$entityName,
$fieldName
));
}
/**
@@ -383,6 +382,8 @@ class MappingException extends ORMException
}
/**
* @deprecated 2.9 no longer in use
*
* @param string $className
* @param string $propertyName
*
@@ -921,7 +922,7 @@ class MappingException extends ORMException
}
/**
* @return MappingException
* @return self
*/
public static function illegalOverrideOfInheritedProperty($className, $propertyName)
{
@@ -934,4 +935,32 @@ class MappingException extends ORMException
)
);
}
/**
* @return self
*/
public static function invalidIndexConfiguration($className, $indexName)
{
return new self(
sprintf(
'Index %s for entity %s should contain columns or fields values, but not both.',
$indexName,
$className
)
);
}
/**
* @return self
*/
public static function invalidUniqueConstraintConfiguration($className, $indexName)
{
return new self(
sprintf(
'Unique constraint %s for entity %s should contain columns or fields values, but not both.',
$indexName,
$className
)
);
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class OneToMany implements Annotation
{
/** @var string */
@@ -48,4 +53,23 @@ final class OneToMany implements Annotation
/** @var string */
public $indexBy;
/**
* @param array<string> $cascade
*/
public function __construct(
?string $mappedBy = null,
?string $targetEntity = null,
?array $cascade = null,
string $fetch = 'LAZY',
bool $orphanRemoval = false,
?string $indexBy = null
) {
$this->mappedBy = $mappedBy;
$this->targetEntity = $targetEntity;
$this->cascade = $cascade;
$this->fetch = $fetch;
$this->orphanRemoval = $orphanRemoval;
$this->indexBy = $indexBy;
}
}

View File

@@ -20,10 +20,15 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class OneToOne implements Annotation
{
/** @var string */
@@ -48,4 +53,23 @@ final class OneToOne implements Annotation
/** @var bool */
public $orphanRemoval = false;
/**
* @param array<string> $cascade
*/
public function __construct(
?string $mappedBy = null,
?string $inversedBy = null,
?string $targetEntity = null,
?array $cascade = null,
string $fetch = 'LAZY',
bool $orphanRemoval = false
) {
$this->mappedBy = $mappedBy;
$this->inversedBy = $inversedBy;
$this->targetEntity = $targetEntity;
$this->cascade = $cascade;
$this->fetch = $fetch;
$this->orphanRemoval = $orphanRemoval;
}
}

View File

@@ -20,12 +20,25 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class OrderBy implements Annotation
{
/** @var array<string> */
public $value;
/**
* @param array<string> $value
*/
public function __construct(array $value)
{
$this->value = $value;
}
}

View File

@@ -20,10 +20,13 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
/**
* @Annotation
* @Target("METHOD")
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class PostLoad implements Annotation
{
}

View File

@@ -20,10 +20,13 @@
namespace Doctrine\ORM\Mapping;
use Attribute;
/**
* @Annotation
* @Target("METHOD")
*/
#[Attribute(Attribute::TARGET_METHOD)]
final class PostPersist implements Annotation
{
}

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