Compare commits

...

137 Commits

Author SHA1 Message Date
Alexander M. Turek
f82485e651 Support doctrine/annotations 2 (#10320) 2022-12-19 22:51:58 +01:00
Grégoire Paris
c4835cca0d Drop forceful loading of annotations (#10321)
Since doctrine/annotations 1.10, autoloading is used when everything
else has failed. Using registerFile() has been deprecated in favor of
that later on.
2022-12-19 20:12:23 +01:00
Alexander M. Turek
82a406332e Document stdClass structures used by CommitOrderCalculator (#10315) 2022-12-19 15:43:46 +01:00
Alexander M. Turek
8093c2eef6 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Psalm 5.3.0 (#10317)
  PHPStan 1.9.4 (#10318)
2022-12-19 15:22:24 +01:00
Alexander M. Turek
099e51d899 Psalm 5.3.0 (#10317) 2022-12-19 15:04:28 +01:00
Alexander M. Turek
5df84d4ec0 PHPStan 1.9.4 (#10318) 2022-12-19 12:16:41 +01:00
Alexander M. Turek
f94cb9a5e6 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  add apcu enabled check if apcu extension loaded (#10310) (#10311)
2022-12-19 00:23:32 +01:00
aleksejs1
c23220b68a add apcu enabled check if apcu extension loaded (#10310) (#10311)
* add apcu enabled check if apcu extension loaded (#10310)

* Apply suggestions from code review

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

* add use function apcu_enabled

* enable apcu_cli for cache tests

Co-authored-by: Aleksejs Kovalovs <kovalovs@co.inbox.lv>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2022-12-18 21:21:27 +01:00
michnovka
6255461b84 Add TypedFieldMapper for automatic mapping of typed PHP fields to DBAL types (#10313)
Previously, only a predefined set of automatic mappings was allowed, such as int, float, boolean, DateTime etc.
With this extension, it is possible to supply custom TypedFieldMapper implementation which takes as parameter the ReflectionProperty of a given field and decides the appropriate mapping.
A new configuration option was added to set and get the TypedFieldMapper.
The old logic was moved into a class DefaultTypedFieldMapper which is used by default when no mapper is supplied.
The selected TypedFieldMapper is passed into ClassMetadataInfo constructor. If empty, the DefaultTypedFieldMapper is used.
There is also ChainTypedFieldMapper class which allows chaining multiple TypedFieldMappers and apply them in a cascade (always if a field gets type assigned by the earlier mapper in the list, it will not be changed later).
2022-12-18 21:08:30 +01:00
Grégoire Paris
0aa5946286 Merge pull request #10301 from greg0ire/allow-lexer-2
Allow lexer 2
2022-12-14 20:29:27 +01:00
Grégoire Paris
eda7558674 Allow doctrine/lexer 2 2022-12-14 13:49:51 +01:00
Grégoire Paris
13bab31da6 Merge pull request #10302 from greg0ire/non-nullability-assertions
Add assertions about non nullability
2022-12-13 23:22:19 +01:00
Grégoire Paris
f960bc2c11 Add assertions about non nullability 2022-12-13 21:13:38 +01:00
Grégoire Paris
15058ca83e Merge pull request #10288 from michnovka/2.13.x-enum-discriminator-columns
Allow enum discriminator columns
2022-12-13 20:01:31 +01:00
Alexander M. Turek
9b14786738 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Bump coding standard to v11 (#10295)
  PHPStan 1.9.3 (#10298)
  Rename AbstractCommandTest (#10294)
2022-12-13 19:09:05 +01:00
Alexander M. Turek
83f6356f25 Bump coding standard to v11 (#10295) 2022-12-13 19:08:04 +01:00
Alexander M. Turek
4e6cb908f6 PHPStan 1.9.3 (#10298) 2022-12-13 18:51:58 +01:00
Tomas
497ee166bd Add support for enum discriminator columns
This commit adds enumType option to DiscriminatorColumn as well as support for custom DBAL types which use PHP enums for PHP values.
Previously, the enumType option was completely missing, but also even using custom types that used PHP enums would end up in exception because ObjectHydrator would try to convert enums to string using (string) explicit conversion.
Apart from hydrators, ClassMetadataBuilder was extended to support specifying enumType.
Documentation was updated.
2022-12-13 11:15:39 +01:00
Alexander M. Turek
9b723a88aa Rename AbstractCommandTest (#10294) 2022-12-12 16:36:16 +01:00
Alexander M. Turek
db18161a1a Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Fix changeset computation for enum arrays (#10277)
  Psalm 5.2.0 (#10291)
  Run tools on PHP 8.2 (#10287)
2022-12-12 13:59:45 +01:00
michnovka
bd11475615 Fix changeset computation for enum arrays (#10277)
Previously, array of enums were incorrectly compared in UoW::computeChangeSet() resulting always in false positive, since the original data for comparison is fetched using ReflectionEnumProperty::getValue(), which returns the enum values, not enum objects.
This fix ensures comparing the individual enum array members' values.
2022-12-12 13:58:21 +01:00
Alexander M. Turek
90f1f54e73 Psalm 5.2.0 (#10291) 2022-12-12 13:54:50 +01:00
Alexander M. Turek
b25561ad96 Run tools on PHP 8.2 (#10287) 2022-12-11 11:58:09 +01:00
Alexander M. Turek
284e81403b Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Fix association mapping with enum fields
  Correct spelling errors
2022-12-10 18:32:48 +01:00
Alexander M. Turek
3ea8550ca6 Control proxy implementation via env (#10282) 2022-12-10 18:11:02 +01:00
Grégoire Paris
aa4b62ce78 Merge pull request #10187 from nicolas-grekas/ve-proxy-2
Leverage LazyGhostTrait when possible
2022-12-09 14:14:42 +01:00
Nicolas Grekas
e5e674c686 Leverage LazyGhostTrait when possible 2022-12-08 22:09:29 +01:00
Grégoire Paris
b391431a0e Merge pull request #10274 from michnovka/2.13.x-complex-enum-ids
Fix enum IDs in association mappings
2022-12-08 08:59:52 +01:00
Tomas
d5a6b36e6f Fix association mapping with enum fields
Enum fields as ID have worked for some time, but referencing these fields from other entities in association mappings such as OneToOne, ManyToOne and ManyToMany resulted in fatal error as there was an attempt to convert enum value to string improperly.
2022-12-07 16:56:35 +01:00
Alexander M. Turek
a2b5bae923 Fix deprecation message (#10270) 2022-12-05 21:45:50 +01:00
Grégoire Paris
aeed977d6e Merge pull request #10271 from kalifg/patch-1
Correct spelling errors
2022-12-05 19:43:18 +01:00
Michael Dwyer
f66b008b8b Correct spelling errors 2022-12-05 12:31:10 -06:00
Alexander M. Turek
5a8541b450 Add $not constructor parameter to AST classes (#10267) 2022-12-05 11:44:53 +01:00
Grégoire Paris
fa18e130cb Merge pull request #10265 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-12-04 00:12:25 +01:00
Grégoire Paris
2ed3f55c01 Merge pull request #10264 from greg0ire/psalm-5.1
Upgrade to Psalm 5.1.0
2022-12-04 00:00:18 +01:00
Grégoire Paris
2dfb4f44e2 Upgrade to Psalm 5.1.0 2022-12-03 10:58:00 +01:00
Alexander M. Turek
24bf06725b Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Bump Psalm to 5.0.0 and fix errors for Symfony 6.2 (#10261)
  Leverage Lexer's Token type (follow up)
2022-11-30 22:01:47 +01:00
Alexander M. Turek
4b577e7a18 Bump Psalm to 5.0.0 and fix errors for Symfony 6.2 (#10261) 2022-11-30 22:00:40 +01:00
Grégoire Paris
d5ef6be4cc Merge pull request #10256 from greg0ire/parser-consistent-style
Make use statements redundant
2022-11-27 21:59:00 +01:00
Grégoire Paris
65da1fe8cb Merge pull request #10257 from greg0ire/fix-phpdoc-parser
Leverage Lexer's Token type (follow up)
2022-11-27 21:53:55 +01:00
Grégoire Paris
54336840e6 Make use statements redundant
In 68bc00b6c6, while fixing coding style,
I introduced many, many use statements in that file. Using
half-qualified names sometimes, and unqualified names some other times
makes no sense.

If AST\ is used at least once, use it always.
2022-11-26 17:09:39 +01:00
Grégoire Paris
74986f1d53 Merge pull request #10253 from greg0ire/2.14.x
Merge 2.13.x up into 2.14.x
2022-11-26 14:35:02 +01:00
Grégoire Paris
1df221860f Leverage Lexer's Token type (follow up)
Follow-up of f82db6a894.
The previous value introduced in
dc37c2cd2f was too restrictive, hence the
changes to the Psalm baseline.
2022-11-26 14:24:02 +01:00
Grégoire Paris
16cd49ba89 Merge remote-tracking branch 'origin/2.13.x' into 2.14.x 2022-11-26 14:18:14 +01:00
Grégoire Paris
57ac275137 Merge pull request #10252 from greg0ire/psalm-5
Upgrade to Psalm v5
2022-11-26 14:11:21 +01:00
Grégoire Paris
e958046c4a Upgrade to Psalm v5
It is not stable yet, but should be good enough, and this will help
taking care of #10118

Let us baseline all the new issues, just because they are new does not
mean they are more important than already-baselined errors. Besides, it
is more important to have higher standards for new code than to get an
increased baseline.
2022-11-26 13:41:02 +01:00
Grégoire Paris
3038f6aeef Psalm 5 fixes (#10248)
* Account for ClassMetadata::inheritanceType always being truthy

* Remove redundant cast to array
2022-11-24 21:28:23 +01:00
Grégoire Paris
28bf6cb1fa Merge pull request #10247 from derrabus/sa/paginator
Fix types for Paginator
2022-11-23 21:49:53 +01:00
Alexander M. Turek
e864c4cbc2 Fix types for Paginator 2022-11-23 21:40:17 +01:00
Grégoire Paris
f9d5a89a39 Merge pull request #10242 from VincentLanglet/staticAnalysis
Solve some PHPStan baseline errors
2022-11-21 08:09:27 +01:00
Vincent Langlet
db7333cc84 Update baseline 2022-11-20 23:55:47 +01:00
Vincent Langlet
47b4ccc4e6 Solve some baseline errors 2022-11-20 22:49:51 +01:00
Grégoire Paris
5a7fce12b8 Merge pull request #10238 from VincentLanglet/lockMode
Use more precise phpdoc
2022-11-20 22:20:08 +01:00
Vincent Langlet
cc9e456ed8 Use more precise phpdoc 2022-11-20 21:56:59 +01:00
Alexander M. Turek
da356316c1 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Remove fragile assertions (#10239)
2022-11-20 20:57:47 +01:00
Grégoire Paris
1ce806fcb7 Merge pull request #10231 from greg0ire/static-analysis-improvements
Make the code easier to statically analyse
2022-11-16 08:08:49 +01:00
David Maicher
958d0b6193 update help for RunDqlCommand (#10233) 2022-11-15 17:38:56 +01:00
Grégoire Paris
843f3c3b23 Make the code easier to statically analyse 2022-11-14 23:00:25 +01:00
Grégoire Paris
82e4c644f9 Merge pull request #10230 from greg0ire/2.14.x
Merge 2.13.x up into 2.14.x
2022-11-14 22:50:32 +01:00
Grégoire Paris
9399f1f3a8 Merge remote-tracking branch 'origin/2.13.x' into 2.14.x 2022-11-14 22:43:56 +01:00
Grégoire Paris
ad69810775 Merge pull request #10224 from greg0ire/fix-wrong-phpdoc
Document property as non-nullable
2022-11-13 22:11:54 +01:00
Grégoire Paris
6c7a5e6faa Document property as non-nullable
The constructor forbids it.
2022-11-11 18:39:25 +01:00
Grégoire Paris
dcc1c26826 Merge pull request #10222 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-11-11 16:31:42 +01:00
Grégoire Paris
8afb644a18 Ignore PropertyNotSetInConstructor (#10218)
Inside the Query\AST namespace, many classes use public properties that
are supposed to be set from outside the class. Let us ignore
PropertyNotSetInConstructor for that entire namespace instead of doing
it on a case by case basis.
2022-11-11 14:56:31 +01:00
Alexander M. Turek
953e42d059 Add a constructor to CacheKey (#10212) 2022-11-11 10:50:07 +01:00
Alexander M. Turek
318af0a666 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Psalm 4.30.0, PHPStan 1.9.2 (#10213)
2022-11-11 10:16:45 +01:00
Willem Verspyck
90ececcc85 Allow "Expr\Func" as condition in join (#10202) 2022-11-10 14:43:40 +01:00
Simon Podlipsky
88b36e07e1 refactor: use list type in SchemaTool (#10199) 2022-11-10 14:22:22 +01:00
Grégoire Paris
a37c2cc05f Merge pull request #10206 from greg0ire/rename-variable
Use a more accurate name for $annotationName
2022-11-06 22:13:19 +01:00
Grégoire Paris
40b34b03c1 Use a more accurate name for $annotationName 2022-11-06 21:44:29 +01:00
Grégoire Paris
8d9ab72613 Merge pull request #10204 from greg0ire/rename-internal-methods
Rename internal methods
2022-11-06 21:26:20 +01:00
Grégoire Paris
12f0674b1a Deprecate AttributeDriver::$entityAnnotationClasses 2022-11-06 21:04:05 +01:00
Grégoire Paris
069206ba14 Refer to attributes in comments and variable names 2022-11-06 21:04:05 +01:00
Grégoire Paris
e8a4d2e91b Remove useless comment
Cannot tell what this ignores.
2022-11-06 21:04:05 +01:00
Grégoire Paris
284baf890e Improve phpdoc 2022-11-06 21:04:05 +01:00
Grégoire Paris
a1f9b28cdc Replace Annotations with Attributes in method names
These are internal, so it should be fine.
2022-11-06 21:04:04 +01:00
Grégoire Paris
2b7485af97 Merge pull request #10205 from greg0ire/avoid-references-to-annotations
Avoid references to annotations
2022-11-06 20:56:45 +01:00
Grégoire Paris
edb6332359 Avoid $annotation as a parameter name 2022-11-06 14:58:38 +01:00
Grégoire Paris
48c1eef1b8 Migrate comments to attributes 2022-11-06 14:51:02 +01:00
Alexander M. Turek
5d88dc9be4 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  PHPStan 1.9.0 (#10200)
2022-11-04 13:08:38 +01:00
michnovka
c97f0a1078 Backport NonProxyLoadingEntityManager::refresh() signature change (#10197) 2022-11-02 16:49:13 +01:00
Alexander M. Turek
474f76fc8b Remove Doctrine\Persistence\ObjectManager::refresh from Psalm baseline 2022-11-02 00:15:00 +01:00
michnovka
25ce9b9101 Add lockMode to EntityManager#refresh() (#10040) 2022-11-01 23:59:11 +01:00
Alexander M. Turek
75340b68b2 Merge 2.13.x into 2.14.x (#10190) 2022-10-31 09:20:30 +01:00
Alexander M. Turek
543be3fe35 Deprecate the Annotation interface (#10178) 2022-10-26 21:51:46 +02:00
Alexander M. Turek
1edfa91714 Merge 2.13.x into 2.14.x (#10181) 2022-10-26 11:53:28 +02:00
HypeMC
c828a3814b Automap events in AttachEntityListenersListener (#10122) 2022-10-26 10:36:06 +02:00
Grégoire Paris
cf91ce63d3 Merge pull request #10153 from greg0ire/address-dbal-deprecations
Address dbal deprecations
2022-10-24 08:53:38 +02:00
Alexander M. Turek
f3f453286f Deprecate EntityManager::create() (#9961) 2022-10-24 00:15:56 +02:00
Grégoire Paris
7cb96fcf0e Address deprecation of SchemaDiff::toSaveSql()
This implies deprecating a feature relying on that method.
2022-10-23 23:29:36 +02:00
Grégoire Paris
ac94d826dc Address deprecation of SchemaDiff::toSql()
The new method AbstractPlatform::getAlterSchemaSQL() should be preferred
when available.
2022-10-23 22:58:31 +02:00
Grégoire Paris
f33919d7d6 Merge pull request #10162 from greg0ire/stderr-for-humans
Use error style for notifications
2022-10-23 22:56:18 +02:00
Grégoire Paris
f256d996cc Use error style for notifications
stderr is not a great name for something that is not meant to be
processed (piped into) a program, but to be read by humans.
Most commands should use stderr, and some of them should partially use
stdout, typically when dumping SQL requests.
2022-10-23 21:51:12 +02:00
Grégoire Paris
d617323a48 Merge pull request #10161 from greg0ire/deprecate-mapping-drivers
Make the mapping driver deprecations more obvious
2022-10-23 10:30:43 +02:00
Alexander M. Turek
6b61e26238 Fix calls to AbstractSchemaManager::createSchema() (#10165) 2022-10-22 17:04:53 -07:00
Alexander M. Turek
edad800711 Merge 2.13.x into 2.14.x (#10164) 2022-10-23 01:19:54 +02:00
Grégoire Paris
fba05675b6 Deprecate methods related to the annotation driver 2022-10-22 15:23:57 +02:00
Grégoire Paris
8160e89c5a Use correct link 2022-10-22 15:05:19 +02:00
Grégoire Paris
a76b776802 Deprecate annotations 2022-10-22 14:54:12 +02:00
Grégoire Paris
ad1d1ca942 Remove trailing whitespace 2022-10-22 14:54:12 +02:00
Grégoire Paris
be835bb8e2 Merge remote-tracking branch 'origin/2.13.x' into 2.14.x 2022-10-21 21:28:17 +02:00
Alexander M. Turek
bac784c9ba Merge 2.13.x up into 2.14.x (#10149) 2022-10-17 23:19:27 +02:00
Alexander M. Turek
1ad936a448 Fix type doc blocks in annotation classes (#10145) 2022-10-17 22:20:08 +02:00
Alexander M. Turek
7e75807918 Merge 2.13.x into 2.14.x (#10139) 2022-10-17 10:14:22 +02:00
Alexander M. Turek
90f82202a8 Merge 2.13.x into 2.14.x (#10129) 2022-10-13 15:21:22 +02:00
Alexander M. Turek
97aa5b37e6 Allow doctrine/event-manager 2 (#10123) 2022-10-13 13:15:37 +02:00
Grégoire Paris
1a9f40c785 Address deprecation of Table::changeColumn() (#10121)
It is deprecated in favor of Table::modifyColumn().
2022-10-11 19:38:01 +02:00
Grégoire Paris
7ce9a6fe5c Merge pull request #10116 from greg0ire/address-dbal-deprecations
Address deprecations form DBAL
2022-10-10 08:42:54 +02:00
Grégoire Paris
8e062955d5 Address deprecations from DBAL
See https://github.com/doctrine/dbal/pull/5731
2022-10-10 08:42:21 +02:00
Grégoire Paris
0e65b0c3dc Merge pull request #10108 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-10-07 08:52:39 +02:00
Alexander M. Turek
e14e9bebcc Merge pull request #10105 from greg0ire/backport-dbal-fixes
Backport DBAL-related fixes
2022-10-06 19:57:45 +02:00
Grégoire Paris
9422260efd Specify precision for decimal columns
doctrine/dbal 4 no longer provides a default value for that column
option.
2022-10-06 11:32:28 +02:00
Grégoire Paris
d46512332c Address AbstractSchemaManager::createSchema() removal 2022-10-06 10:56:38 +02:00
Grégoire Paris
0d56ffc261 Address method rename
See https://github.com/doctrine/dbal/pull/5724
2022-10-06 10:46:22 +02:00
Grégoire Paris
95c818928e Merge pull request #10098 from greg0ire/deprecate-annotations 2022-10-05 13:37:57 +02:00
Grégoire Paris
f27034880a Remove trailing whitespace 2022-10-05 12:24:53 +02:00
Grégoire Paris
3735822662 Deprecate annotation driver
No new project should use that driver.
2022-10-05 12:24:53 +02:00
Grégoire Paris
52f7ddc680 Use appropriate directive 2022-10-05 09:46:43 +02:00
Grégoire Paris
de32d8239f Merge pull request #10089 from greg0ire/remove-unneeded-checks
Document properties as possibly null
2022-10-04 23:28:01 +02:00
Grégoire Paris
397751ee65 Update Psalm baseline 2022-10-03 21:41:04 +02:00
Grégoire Paris
b0038eeea6 Document properties as possibly null
__sleep() does not list these properties as suposed to be serialized.
This means we have to assert that it is not in the many scenarios where
we resort to these properties. Introducing a private method allows us to
centralize the assertions.
2022-10-03 21:41:03 +02:00
Grégoire Paris
f06f9f843e Merge pull request #10093 from doctrine/2.13.x
Merge 2.13.x up in 2.14.x
2022-10-03 21:40:34 +02:00
Grégoire Paris
5d3fc62023 Merge pull request #10086 from franmomu/create_specific_event_classes
Create dedicated event argument classes
2022-10-02 21:49:09 +02:00
Grégoire Paris
154e7130f5 Merge pull request #10091 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-10-02 16:54:10 +02:00
Fran Moreno
99f044cbc7 Deprecate LifecycleEventArgs 2022-10-01 14:39:31 +02:00
Fran Moreno
e8ca7b4abf Add dedicated classes for events 2022-10-01 14:39:31 +02:00
Grégoire Paris
b05fb96ad3 Merge pull request #10070 from greg0ire/preview-coll-2
Add support for doctrine/collection 2
2022-09-29 23:30:59 +02:00
Grégoire Paris
bb020320ca Allow collections 2 2022-09-29 23:08:43 +02:00
Grégoire Paris
1e0ac8899c Merge pull request #10079 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-09-29 23:07:20 +02:00
Grégoire Paris
4a04c8c2db Merge pull request #10055 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-09-22 15:49:21 +02:00
Grégoire Paris
470e2ddd05 Merge pull request #10045 from nicolas-grekas/proxy-common-less 2022-09-22 11:29:02 +02:00
Nicolas Grekas
6ec5ab9145 Deprecate Doctrine\ORM\Proxy\Proxy and decouple a bit more from Doctrine\Common\Proxy 2022-09-22 09:56:30 +02:00
Alexander M. Turek
86000d9c70 Merge 2.13.x into 2.14.x (#10051) 2022-09-22 08:41:04 +02:00
Alexander M. Turek
328bf707cc Merge 2.13.x into 2.14.x (#10046) 2022-09-18 15:20:51 +02:00
Alexander M. Turek
7a9037e9d7 Merge branch '2.13.x' into 2.14.x
* 2.13.x:
  Bump Ubuntu version and shared workflows (#10020)
  Fail gracefully if EM could not be constructed in tests (#10008)
2022-08-30 21:22:58 +02:00
Vincent Langlet
8d03f8f089 Make paginator covariant (#10002) 2022-08-30 13:43:14 +02:00
Grégoire Paris
8dfe8b8782 Merge pull request #10010 from doctrine/2.13.x
Merge 2.13.x up into 2.14.x
2022-08-27 21:07:59 +02:00
462 changed files with 4702 additions and 2183 deletions

View File

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

View File

@@ -43,6 +43,8 @@ jobs:
- "default"
extension:
- "pdo_sqlite"
proxy:
- "common"
include:
- php-version: "8.0"
dbal-version: "2.13"
@@ -53,6 +55,10 @@ jobs:
- php-version: "8.2"
dbal-version: "default"
extension: "sqlite3"
- php-version: "8.1"
dbal-version: "default"
proxy: "lazy-ghost"
extension: "pdo_sqlite"
steps:
- name: "Checkout"
@@ -66,12 +72,15 @@ jobs:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, ${{ matrix.extension }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -81,11 +90,13 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 0
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
- name: "Run PHPUnit with Second Level Cache"
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
env:
ENABLE_SECOND_LEVEL_CACHE: 1
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v3"
@@ -139,12 +150,15 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -206,12 +220,15 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
@@ -276,13 +293,16 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
extensions: "${{ matrix.extension }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -327,7 +347,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"

View File

@@ -45,7 +45,7 @@ jobs:
with:
php-version: "${{ matrix.php-version }}"
coverage: "pcov"
ini-values: "zend.assertions=1"
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Cache dependencies installed with composer"
uses: "actions/cache@v3"

View File

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

View File

@@ -1,4 +1,3 @@
name: "Static Analysis"
on:
@@ -31,18 +30,14 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
dbal-version:
- "default"
persistence-version:
- "default"
include:
- php-version: "8.1"
dbal-version: "2.13"
- dbal-version: "2.13"
persistence-version: "default"
- php-version: "8.1"
dbal-version: "default"
- dbal-version: "default"
persistence-version: "2.5"
steps:
@@ -53,15 +48,17 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
php-version: "8.2"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^${{ matrix.persistence-version }} --no-update"
if: "${{ matrix.persistence-version != 'default' }}"
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
@@ -86,9 +83,6 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout code"
@@ -98,7 +92,13 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "${{ matrix.php-version }}"
php-version: "8.2"
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^3.1 --no-update"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"

View File

@@ -1,5 +1,80 @@
# Upgrade to 2.14
## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`
The following public constants have been deprecated:
* `CommitOrderCalculator::NOT_VISITED`
* `CommitOrderCalculator::IN_PROGRESS`
* `CommitOrderCalculator::VISITED`
These constants were used for internal purposes. Relying on them is discouraged.
## Deprecated `Doctrine\ORM\Query\AST\InExpression`
The AST parser will create a `InListExpression` or a `InSubselectExpression` when
encountering an `IN ()` DQL expression instead of a generic `InExpression`.
As a consequence, `SqlWalker::walkInExpression()` has been deprecated in favor of
`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`.
## Deprecated constructing a `CacheKey` without `$hash`
The `Doctrine\ORM\Cache\CacheKey` class has an explicit constructor now with
an optional parameter `$hash`. That parameter will become mandatory in 3.0.
## Deprecated `AttributeDriver::$entityAnnotationClasses`
If you need to change the behavior of `AttributeDriver::isTransient()`,
override that method instead.
## Deprecated incomplete schema updates
Using `orm:schema-tool:update` without passing the `--complete` flag is
deprecated. Use schema asset filtering if you need to preserve assets not
managed by DBAL.
Likewise, calling `SchemaTool::updateSchema()` or
`SchemaTool::getUpdateSchemaSql()` with a second argument is deprecated.
## Deprecated annotation mapping driver.
Please switch to one of the other mapping drivers. Native attributes which PHP
supports since version 8.0 are probably your best option.
As a consequence, the following methods are deprecated:
- `ORMSetup::createAnnotationMetadataConfiguration`
- `ORMSetup::createDefaultAnnotationDriver`
The marker interface `Doctrine\ORM\Mapping\Annotation` is deprecated as well.
All annotation/attribute classes implement
`Doctrine\ORM\Mapping\MappingAttribute` now.
## Deprecated `Doctrine\ORM\Proxy\Proxy` interface.
Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized.
## Deprecated `Doctrine\ORM\Event\LifecycleEventArgs` class.
It will be removed in 3.0. Use one of the dedicated event classes instead:
* `Doctrine\ORM\Event\PrePersistEventArgs`
* `Doctrine\ORM\Event\PreUpdateEventArgs`
* `Doctrine\ORM\Event\PreRemoveEventArgs`
* `Doctrine\ORM\Event\PostPersistEventArgs`
* `Doctrine\ORM\Event\PostUpdateEventArgs`
* `Doctrine\ORM\Event\PostRemoveEventArgs`
* `Doctrine\ORM\Event\PostLoadEventArgs`
# Upgrade to 2.13
## Deprecated `EntityManager::create()`
The constructor of `EntityManager` is now public and should be used instead of the `create()` method.
However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters.
You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the
connection.
## Deprecated `QueryBuilder` methods and constants.
1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern.
@@ -191,8 +266,8 @@ The following methods have been deprecated:
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultClassMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativQueryResultSetMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativQueryEntityResultMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultSetMapping()`
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryEntityResultMapping()`
## Deprecated classes related to Doctrine 1 and reverse engineering
@@ -394,7 +469,7 @@ function foo(EntityManagerInterface $entityManager, callable $func) {
if (method_exists($entityManager, 'wrapInTransaction')) {
return $entityManager->wrapInTransaction($func);
}
return $entityManager->transactional($func);
}
```
@@ -460,7 +535,7 @@ implementation. To work around this:
* 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
@@ -485,12 +560,12 @@ Note that `toIterable()` yields results of the query, unlike `iterate()` which y
# Upgrade to 2.7
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache
(depending on passed flag) was split into two.
(depending on passed flag) was split into two.
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
perform the pagination with join collections when max results isn't set in the query.
@@ -509,7 +584,7 @@ In the last patch of the `v2.6.x` series, we fixed a bug that was not converting
In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This
argument will be removed in 3.0 and the default behavior will be the fixed one.
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()`
and `disableResultCache()`. It will be removed in 3.0.
@@ -539,7 +614,7 @@ These related classes have been deprecated:
* `Doctrine\ORM\Proxy\ProxyFactory`
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
These methods have been deprecated:
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
@@ -588,7 +663,7 @@ If your code relies on single entity flushing optimisations via
Said API was affected by multiple data integrity bugs due to the fact
that change tracking was being restricted upon a subset of the managed
entities. The ORM cannot support committing subsets of the managed
entities. The ORM cannot support committing subsets of the managed
entities while also guaranteeing data integrity, therefore this
utility was removed.
@@ -689,8 +764,8 @@ either:
- map those classes as `MappedSuperclass`
## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
an ``EntityManagerInterface`` instead.
If you are extending any of the following classes, then you need to check following
signatures:
@@ -783,7 +858,7 @@ the `Doctrine\ORM\Repository\DefaultRepositoryFactory`.
When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query:
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
Previously, your result would be similar to this:
array(

View File

@@ -24,34 +24,35 @@
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/cache": "^1.12.1 || ^2.1.1",
"doctrine/collections": "^1.5",
"doctrine/collections": "^1.5 || ^2.0",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.13.1 || ^3.2",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3",
"doctrine/lexer": "^1.2.3",
"doctrine/lexer": "^1.2.3 || ^2",
"doctrine/persistence": "^2.4 || ^3",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0",
"symfony/console": "^4.2 || ^5.0 || ^6.0",
"symfony/polyfill-php72": "^1.23",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"doctrine/annotations": "^1.13",
"doctrine/coding-standard": "^9.0.2 || ^10.0",
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^11.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.9.2",
"phpstan/phpstan": "~1.4.10 || 1.9.4",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.1",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.30.0"
"vimeo/psalm": "4.30.0 || 5.3.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 2.0"
"doctrine/annotations": "<1.13 || >= 3.0"
},
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",

View File

@@ -1,74 +0,0 @@
Accessing private/protected properties/methods of the same class from different instance
========================================================================================
.. sectionauthor:: Michael Olsavsky (olsavmic)
As explained in the :doc:`restrictions for entity classes in the manual <../reference/architecture>`,
it is dangerous to access private/protected properties of different entity instance of the same class because of lazy loading.
The proxy instance that's injected instead of the real entity may not be initialized yet
and therefore not contain expected data which may result in unexpected behavior.
That's a limitation of current proxy implementation - only public methods automatically initialize proxies.
It is usually preferable to use a public interface to manipulate the object from outside the `$this`
context but it may not be convenient in some cases. The following example shows how to do it safely.
Safely accessing private properties from different instance of the same class
-----------------------------------------------------------------------------
To safely access private property of different instance of the same class, make sure to initialise
the proxy before use manually as follows:
.. code-block:: php
<?php
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class Entity
{
// ...
/**
* @ORM\ManyToOne(targetEntity="Entity")
* @ORM\JoinColumn(nullable=false)
*/
private self $parent;
/**
* @ORM\Column(type="string", nullable=false)
*/
private string $name;
// ...
public function doSomethingWithParent()
{
// Always initializing the proxy before use
if ($this->parent instanceof Proxy) {
$this->parent->__load();
}
// Accessing the `$this->parent->name` property without loading the proxy first
// may throw error in case the Proxy has not been initialized yet.
$this->parent->name;
}
public function doSomethingWithAnotherInstance(self $instance)
{
// Always initializing the proxy before use
if ($instance instanceof Proxy) {
$instance->__load();
}
// Accessing the `$instance->name` property without loading the proxy first
// may throw error in case the Proxy has not been initialized yet.
$instance->name;
}
// ...
}

View File

@@ -196,7 +196,7 @@ Example usage
<?php
// Bootstrapping stuff...
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
// $em = new \Doctrine\ORM\EntityManager($connection, $config);
// Setup custom mapping type
use Doctrine\DBAL\Types\Type;

View File

@@ -46,7 +46,7 @@ configuration:
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
$em = new EntityManager($connection, $config);
The ``$name`` is the name the function will be referred to in the
DQL query. ``$class`` is a string of a class-name which has to
@@ -247,5 +247,3 @@ vendor sql functions and extend the DQL languages scope.
Code for this Extension to DQL and other Doctrine Extensions can be
found
`in the GitHub DoctrineExtensions repository <https://github.com/beberlei/DoctrineExtensions>`_.

View File

@@ -1,78 +0,0 @@
Implementing Wakeup or Clone
============================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the :ref:`restrictions for entity classes in the manual
<terminology_entities>`,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing __wakeup
----------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
// ...
public function __wakeup()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
// ...
}
Safely implementing __clone
---------------------------
Safely implementing ``__clone`` is pretty much the same:
.. code-block:: php
<?php
class MyEntity
{
private $id; // This is the identifier of the entity.
// ...
public function __clone()
{
// If the entity has an identity, proceed as normal.
if ($this->id) {
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
// ...
}
Summary
-------
As you have seen, it is quite easy to safely make use of
``__wakeup`` and ``__clone`` in your entities without adding any
really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine
invokes these methods, the entities never have an identity (yet).
Furthermore, it is possibly a good idea to check for the identity
in your code anyway, since it's rarely the case that you want to
unserialize or clone an entity with no identity.

View File

@@ -127,7 +127,8 @@ the targetEntity resolution will occur reliably:
// Add the ResolveTargetEntityListener
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
$connection = \Doctrine\DBAL\DriverManager::createConnection($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
Final Thoughts
--------------
@@ -136,5 +137,3 @@ With the ``ResolveTargetEntityListener``, we are able to decouple our
bundles, keeping them usable by themselves, but still being able to
define relationships between different objects. By using this method,
I've found my bundles end up being easier to maintain independently.

View File

@@ -81,6 +81,4 @@ before the prefix has been set.
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);

View File

@@ -72,6 +72,7 @@ Advanced Topics
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
* :doc:`Filters <reference/filters>`
* :doc:`NamingStrategy <reference/namingstrategy>`
* :doc:`TypedFieldMapper <reference/typedfieldmapper>`
* :doc:`Improving Performance <reference/improving-performance>`
* :doc:`Caching <reference/caching>`
* :doc:`Partial Objects <reference/partial-objects>`
@@ -112,7 +113,6 @@ Cookbook
* **Implementation**:
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
:doc:`Validation <cookbook/validation-of-entities>` |
:doc:`Entities in the Session <cookbook/entities-in-session>` |

View File

@@ -41,12 +41,12 @@ steps of configuration.
$config->setAutoGenerateProxyClasses(false);
}
$connectionOptions = array(
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite'
);
'path' => 'database.sqlite',
], $config);
$em = EntityManager::create($connectionOptions, $config);
$em = new EntityManager($connection, $config);
Doctrine and Caching
--------------------
@@ -114,11 +114,13 @@ classes.
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``
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver`` (deprecated and will
be removed in ``doctrine/orm`` 3.0)
- ``Doctrine\ORM\Mapping\Driver\YamlDriver`` (deprecated and will be
removed in ``doctrine/orm`` 3.0)
Throughout the most part of this manual the AttributeDriver is
used in the examples. For information on the usage of the
@@ -214,7 +216,7 @@ option that controls this behavior is:
Possible values for ``$mode`` are:
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
Never autogenerate a proxy. You will need to generate the proxies
manually, for this use the Doctrine Console like so:
@@ -230,17 +232,17 @@ methods were added to the entity class that are not yet in the proxy class.
In such a case, simply use the Doctrine Console to (re)generate the
proxy classes.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
Always generates a new proxy in every request and writes it to disk.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
Generate the proxy class when the proxy file does not exist.
This strategy causes a file exists call whenever any proxy is
used the first time in a request.
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_EVAL``
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
Generate the proxy classes and evaluate them on the fly via eval(),
avoiding writing the proxies to disk.
@@ -274,15 +276,13 @@ proxy sets an exclusive file lock which can cause serious
performance bottlenecks in systems with regular concurrent
requests.
Connection Options
------------------
Connection
----------
The ``$connectionOptions`` passed as the first argument to
``EntityManager::create()`` has to be either an array or an
instance of ``Doctrine\DBAL\Connection``. If an array is passed it
is directly passed along to the DBAL Factory
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
configuration is explained in the
The ``$connection`` passed as the first argument to he constructor of
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
to create such a connection. The DBAL configuration is explained in the
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
Proxy Objects
@@ -325,8 +325,9 @@ identifier. You could simply do this:
$cart->addItem($item);
Here, we added an Item to a Cart without loading the Item from the
database. If you invoke any method on the Item instance, it would
fully initialize its state transparently from the database. Here
database. If you access any state that isn't yet available in the
Item instance, the proxying mechanism would fully initialize the
object's state transparently from the database. Here
$item is actually an instance of the proxy class that was generated
for the Item class but your code does not need to care. In fact it
**should not care**. Proxy objects should be transparent to your

View File

@@ -1,6 +1,11 @@
Annotations Reference
=====================
.. warning::
The annotation driver is deprecated and will be removed in version
3.0. It is strongly recommended to switch to one of the other
mapping drivers.
.. note::
To be able to use annotations, you will have to install an extra
@@ -124,7 +129,7 @@ Optional attributes:
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be
@@ -1382,4 +1387,3 @@ Example:
* @Version
*/
protected $version;

View File

@@ -74,32 +74,13 @@ Entities
An entity is a lightweight, persistent domain object. An entity can
be any regular PHP class observing the following restrictions:
- An entity class must not be final or contain final methods.
- All persistent properties/field of any entity class should
always be private or protected, otherwise lazy-loading might not
work as expected. In case you serialize entities (for example Session)
properties should be protected (See Serialize section below).
- An entity class must not implement ``__clone`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
- An entity class must not implement ``__wakeup`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
You can also consider implementing
`Serializable <https://php.net/manual/en/class.serializable.php>`_,
but be aware that it is deprecated since PHP 8.1. We do not recommend its usage.
- PHP 7.4 introduces :doc:`the new magic method <https://php.net/manual/en/language.oop5.magic.php#object.unserialize>`
``__unserialize``, which changes the execution priority between
``__wakeup`` and itself when used. This can cause unexpected behaviour in
an Entity.
- An entity class must not be final nor read-only but
it may contain final methods or read-only properties.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
must not have a mapped field with the same name as an already
mapped field that is inherited from A.
- An entity cannot make use of func_get_args() to implement variable parameters.
Generated proxies do not support this for performance reasons and your code might
actually fail to work when violating this restriction.
- Entity cannot access private/protected properties/methods of another entity of the same class or :doc:`do so safely <../cookbook/accessing-private-properties-of-the-same-class-from-different-instance>`.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
@@ -159,17 +140,13 @@ Serializing entities
Serializing entities can be problematic and is not really
recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an
EntityManager. If you intend to serialize (and unserialize) entity
instances that still hold references to proxy objects you may run
into problems with private properties because of technical
limitations. Proxy objects implement ``__sleep`` and it is not
possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us). The
``Serializable`` interface is also deprecated beginning with PHP 8.1.
references to proxy objects or is still managed by an EntityManager.
By default, serializing proxy objects does not initialize them. On
unserialization, resulting objects are detached from the entity
manager and cannot be initialiazed anymore. You can implement the
``__serialize()`` method if you want to change that behavior, but
then you need to ensure that you won't generate large serialized
object graphs and take care of circular associations.
The EntityManager
~~~~~~~~~~~~~~~~~

View File

@@ -366,6 +366,8 @@ Optional parameters:
- **type**: By default this is string.
- **length**: By default this is 255.
- **columnDefinition**: By default this is null the definition according to the type will be used. This option allows to override it.
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
.. _attrref_discriminatormap:

View File

@@ -45,9 +45,10 @@ Doctrine provides several different ways to specify object-relational
mapping metadata:
- :doc:`Attributes <attributes-reference>`
- :doc:`Docblock Annotations <annotations-reference>`
- :doc:`XML <xml-mapping>`
- :doc:`PHP code <php-mapping>`
- :doc:`Docblock Annotations <annotations-reference>` (deprecated and
will be removed in ``doctrine/orm`` 3.0)
- :doc:`YAML <yaml-mapping>` (deprecated and will be removed in ``doctrine/orm`` 3.0.)
This manual will usually show mapping metadata via attributes, though
@@ -288,6 +289,13 @@ These are the "automatic" mapping rules:
As of version 2.11 Doctrine can also automatically map typed properties using a
PHP 8.1 enum to set the right ``type`` and ``enumType``.
.. versionadded:: 2.14
Since version 2.14 you can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
:doc:`Read more about TypedFieldMapper <typedfieldmapper>`.
.. _reference-mapping-types:
Doctrine Mapping Types

View File

@@ -41,22 +41,24 @@ access point to ORM functionality provided by Doctrine.
// bootstrap.php
require_once "vendor/autoload.php";
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
$paths = array("/path/to/entity-files");
$paths = ['/path/to/entity-files'];
$isDevMode = false;
// the connection configuration
$dbParams = array(
$dbParams = [
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => '',
'dbname' => 'foo',
);
];
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
@@ -68,18 +70,20 @@ Or if you prefer XML:
.. code-block:: php
<?php
$paths = array("/path/to/xml-mappings");
$paths = ['/path/to/xml-mappings'];
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
Or if you prefer YAML:
.. code-block:: php
<?php
$paths = array("/path/to/yml-mappings");
$paths = ['/path/to/yml-mappings'];
$config = ORMSetup::createYAMLMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
If you want to use yml mapping you should add yaml dependency to your `composer.json`:

View File

@@ -180,10 +180,10 @@ not need to lazy load the association with another query.
Doctrine allows you to walk all the associations between
all the objects in your domain model. Objects that were not already
loaded from the database are replaced with lazy load proxy
instances. Non-loaded Collections are also replaced by lazy-load
loaded from the database are replaced with lazy-loading proxy
instances. Non-loaded Collections are also replaced by lazy-loading
instances that fetch all the contained objects upon first access.
However relying on the lazy-load mechanism leads to many small
However relying on the lazy-loading mechanism leads to many small
queries executed against the database, which can significantly
affect the performance of your application. **Fetch Joins** are the
solution to hydrate most or all of the entities that you need in a
@@ -319,11 +319,11 @@ With Nested Conditions in WHERE Clause:
<?php
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
$query->setParameters(array(
$query->setParameters([
'name' => 'Bob',
'name2' => 'Alice',
'id' => 321,
));
]);
$users = $query->getResult(); // array of ForumUser objects
With COUNT DISTINCT:
@@ -794,7 +794,7 @@ You can register custom DQL functions in your ORM Configuration:
$config->addCustomNumericFunction($name, $class);
$config->addCustomDatetimeFunction($name, $class);
$em = EntityManager::create($dbParams, $config);
$em = new EntityManager($connection, $config);
The functions have to return either a string, numeric or datetime
value depending on the registered function type. As an example we
@@ -806,8 +806,8 @@ classes have to implement the base class :
<?php
namespace MyProject\Query\AST;
use \Doctrine\ORM\Query\AST\Functions\FunctionNode;
use \Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
class MysqlFloor extends FunctionNode
{
@@ -1057,7 +1057,7 @@ the Query class. Here they are:
Instead of using these methods, you can alternatively use the
general-purpose method
``Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)``.
``Query#execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``.
Using this method you can directly supply the hydration mode as the
second parameter via one of the Query constants. In fact, the
methods mentioned earlier are just convenient shortcuts for the

View File

@@ -144,20 +144,20 @@ Events Overview
| Event | Dispatched by | Lifecycle | Passed |
| | | Callback | Argument |
+=================================================================+=======================+===========+=====================================+
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
| | on *initial* persist | | |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `LifecycleEventArgs`_ |
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
| | metadata | | |
@@ -214,7 +214,7 @@ specific to a particular entity class's lifecycle.
<?php
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
#[Entity]
#[HasLifecycleCallbacks]
@@ -226,7 +226,7 @@ specific to a particular entity class's lifecycle.
public $value;
#[PrePersist]
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
@@ -246,7 +246,7 @@ specific to a particular entity class's lifecycle.
.. code-block:: annotation
<?php
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
/**
* @Entity
@@ -260,7 +260,7 @@ specific to a particular entity class's lifecycle.
public $value;
/** @PrePersist */
public function doStuffOnPrePersist(LifecycleEventArgs $eventArgs)
public function doStuffOnPrePersist(PrePersistEventArgs $eventArgs)
{
$this->createdAt = date('Y-m-d H:i:s');
}
@@ -353,11 +353,11 @@ A lifecycle event listener looks like the following:
.. code-block:: php
<?php
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class MyEventListener
{
public function preUpdate(LifecycleEventArgs $args)
public function preUpdate(PreUpdateEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
@@ -374,9 +374,9 @@ A lifecycle event subscriber may look like this:
.. code-block:: php
<?php
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Doctrine\ORM\Events;
use Doctrine\EventSubscriber;
use Doctrine\Persistence\Event\LifecycleEventArgs;
class MyEventSubscriber implements EventSubscriber
{
@@ -387,7 +387,7 @@ A lifecycle event subscriber may look like this:
);
}
public function postUpdate(LifecycleEventArgs $args)
public function postUpdate(PostUpdateEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
@@ -416,7 +416,7 @@ EventManager that is passed to the EntityManager factory:
$eventManager->addEventListener([Events::preUpdate], new MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());
$entityManager = EntityManager::create($dbOpts, $config, $eventManager);
$entityManager = new EntityManager($connection, $config, $eventManager);
You can also retrieve the event manager instance after the
EntityManager was created:
@@ -461,7 +461,7 @@ this association is marked as :ref:`cascade: persist<transitive-persistence>`. A
during this operation is also persisted and ``prePersist`` called
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
In both cases you get passed a ``LifecycleEventArgs`` instance
In both cases you get passed a ``PrePersistEventArgs`` instance
which has access to the entity and the entity manager.
This event is only triggered on *initial* persist of an entity
@@ -830,69 +830,79 @@ you need to map the listener method using the event type mapping:
.. code-block:: attribute
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\PostLoadEventArgs;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostRemoveEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreRemoveEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class UserListener
{
#[PrePersist]
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
#[PostPersist]
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
#[PreUpdate]
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
#[PostUpdate]
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
#[PostRemove]
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
#[PreRemove]
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
#[PreFlush]
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
#[PostLoad]
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Event\PostLoadEventArgs;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostRemoveEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreRemoveEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class UserListener
{
/** @PrePersist */
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
/** @PostPersist */
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
/** @PreUpdate */
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
/** @PostUpdate */
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
/** @PostRemove */
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
/** @PreRemove */
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
/** @PreFlush */
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
/** @PostLoad */
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: xml
@@ -1006,7 +1016,7 @@ Implementing your own resolver:
// Configure the listener resolver only before instantiating the EntityManager
$configurations->setEntityListenerResolver(new MyEntityListenerResolver);
EntityManager::create(.., $configurations, ..);
$entityManager = new EntityManager(.., $configurations, ..);
.. _reference-events-load-class-metadata:
@@ -1106,8 +1116,13 @@ and the EntityManager.
}
}
.. _LifecycleEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/LifecycleEventArgs.php
.. _PrePersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PrePersistEventArgs.php
.. _PreRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreRemoveEventArgs.php
.. _PreUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php
.. _PostPersistEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostPersistEventArgs.php
.. _PostRemoveEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostRemoveEventArgs.php
.. _PostUpdateEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostUpdateEventArgs.php
.. _PostLoadEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostLoadEventArgs.php
.. _PreFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PreFlushEventArgs.php
.. _PostFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/PostFlushEventArgs.php
.. _OnFlushEventArgs: https://github.com/doctrine/orm/blob/HEAD/lib/Doctrine/ORM/Event/OnFlushEventArgs.php

View File

@@ -177,27 +177,3 @@ MySQL with MyISAM tables
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
other storage engines that support transactions if you need integrity.
Entities, Proxies and Reflection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using methods for Reflection on entities can be prone to error, when the entity
is actually a proxy the following methods will not work correctly:
- ``new ReflectionClass``
- ``new ReflectionObject``
- ``get_class()``
- ``get_parent_class()``
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
methods, which resolve the proxy problem beforehand.
.. code-block:: php
<?php
use Doctrine\Common\Util\ClassUtils;
$bookProxy = $entityManager->getReference('Acme\Book');
$reflection = ClassUtils::newReflectionClass($bookProxy);
$class = ClassUtils::getClass($bookProxy)¸

View File

@@ -13,11 +13,16 @@ metadata:
- **XML files** (XmlDriver)
- **Class DocBlock Annotations** (AnnotationDriver)
- **Attributes** (AttributeDriver)
- **YAML files** (YamlDriver)
- **PHP Code in files or static functions** (PhpDriver)
There are also two deprecated ways to do this:
- **Class DocBlock Annotations** (AnnotationDriver)
- **YAML files** (YamlDriver)
They will be removed in 3.0, make sure to avoid them.
Something important to note about the above drivers is they are all
an intermediate step to the same end result. The mapping
information is populated to ``Doctrine\ORM\Mapping\ClassMetadata``
@@ -40,8 +45,9 @@ an entity.
If you want to use one of the included core metadata drivers you need to
configure it. If you pick the annotation driver, you will additionally
need to install ``doctrine/annotations``. All the drivers are in the
configure it. If you pick the annotation driver despite it being
deprecated, you will additionally need to install
``doctrine/annotations``. All the drivers are in the
``Doctrine\ORM\Mapping\Driver`` namespace:
.. code-block:: php
@@ -120,17 +126,17 @@ the ``FileDriver`` implementation for you to extend from:
* {@inheritdoc}
*/
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritdoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
$data = $this->_loadMappingFile($file);
// populate ClassMetadata instance from $data
}
/**
* {@inheritdoc}
*/
@@ -198,5 +204,3 @@ iterate over them:
foreach ($class->fieldMappings as $fieldMapping) {
echo $fieldMapping['fieldName'] . "\n";
}

View File

@@ -167,7 +167,7 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte
- ``addNamedQuery($name, $dqlQuery)``
- ``setJoinedTableInheritance()``
- ``setSingleTableInheritance()``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null)``
- ``addDiscriminatorMapClass($name, $class)``
- ``setChangeTrackingPolicyDeferredExplicit()``
- ``setChangeTrackingPolicyNotify()``

View File

@@ -412,7 +412,7 @@ Doctrine ORM currently supports two pessimistic lock modes:
locks other concurrent requests that attempt to update or lock rows
in write mode.
You can use pessimistic locks in three different scenarios:
You can use pessimistic locks in four different scenarios:
1. Using
@@ -424,6 +424,10 @@ You can use pessimistic locks in three different scenarios:
or
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
3. Using
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
4. Using
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
or
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``

View File

@@ -0,0 +1,176 @@
Implementing a TypedFieldMapper
===============================
.. versionadded:: 2.14
You can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
.. code-block:: php
<?php
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
DefaultTypedFieldMapper
-----------------------
By default the ``Doctrine\ORM\Mapping\DefaultTypedFieldMapper`` is used, and you can pass an array of
PHP type => DBAL type mappings into its constructor to override the default behavior or add new mappings.
.. code-block:: php
<?php
use App\CustomIds\CustomIdObject;
use App\DBAL\Type\CustomIdObjectType;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
CustomIdObject::class => CustomIdObjectType::class,
]));
Then, an entity using the ``CustomIdObject`` typed field will be correctly assigned its DBAL type
(``CustomIdObjectType``) without the need of explicit declaration.
.. configuration-block::
.. code-block:: attribute
<?php
#[ORM\Entity]
#[ORM\Table(name: 'cms_users_typed_with_custom_typed_field')]
class UserTypedWithCustomTypedField
{
#[ORM\Column]
public CustomIdObject $customId;
// ...
}
.. code-block:: annotation
<?php
/**
* @Entity
* @Table(name="cms_users_typed_with_custom_typed_field")
*/
class UserTypedWithCustomTypedField
{
/** @Column */
public CustomIdObject $customId;
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="UserTypedWithCustomTypedField">
<field name="customId"/>
<!-- -->
</entity>
</doctrine-mapping>
.. code-block:: yaml
UserTypedWithCustomTypedField:
type: entity
fields:
customId: ~
It is perfectly valid to override even the "automatic" mapping rules mentioned above:
.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
'int' => CustomIntType::class,
]));
.. note::
If chained, once the first ``TypedFieldMapper`` assigns a type to a field, the ``DefaultTypedFieldMapper`` will
ignore its mapping and not override it anymore (if it is later in the chain). See below for chaining type mappers.
TypedFieldMapper interface
-------------------------
The interface ``Doctrine\ORM\Mapping\TypedFieldMapper`` allows you to implement your own
typed field mapping logic. It consists of just one function
.. code-block:: php
<?php
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, enumType?: string, type?: mixed} $mapping The field mapping to validate & complete.
* @param \ReflectionProperty $field
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
ChainTypedFieldMapper
---------------------
The class ``Doctrine\ORM\Mapping\ChainTypedFieldMapper`` allows you to chain multiple ``TypedFieldMapper`` instances.
When being evaluated, the ``TypedFieldMapper::validateAndComplete`` is called in the order in which
the instances were supplied to the ``ChainTypedFieldMapper`` constructor.
.. code-block:: php
<?php
use App\DBAL\Type\CustomIntType;
$configuration->setTypedFieldMapper(
new ChainTypedFieldMapper(
DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new CustomTypedFieldMapper()
)
);
Implementing a TypedFieldMapper
-------------------------------
If you want to assign all ``BackedEnum`` fields to your custom ``BackedEnumDBALType`` or you want to use different
DBAL types based on whether the entity field is nullable or not, you can achieve this by implementing your own
typed field mapper.
You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMapper``.
.. code-block:: php
<?php
final class CustomEnumTypedFieldMapper implements TypedFieldMapper
{
/**
* {@inheritdoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
$type = $field->getType();
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['type'] = BackedEnumDBALType::class;
}
}
return $mapping;
}
}
.. note::
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
that behaves like ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.

View File

@@ -38,7 +38,7 @@ will still end up with the same reference:
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check for proxyinterface
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
$this->assertInstanceOf('Doctrine\Persistence\Proxy', $objectA);
$objectB = $this->entityManager->find('EntityName', 1);

View File

@@ -162,28 +162,6 @@ your code. See the following code:
echo "This will always be true!";
}
A slice of the generated proxy classes code looks like the
following piece of code. A real proxy class override ALL public
methods along the lines of the ``getName()`` method shown below:
.. code-block:: php
<?php
class UserProxy extends User implements Proxy
{
private function _load(): void
{
// lazy loading code
}
public function getName(): string
{
$this->_load();
return parent::getName();
}
// .. other public methods of User
}
.. warning::
Traversing the object graph for parts that are lazy-loaded will
@@ -414,14 +392,6 @@ Example:
// $entity now refers to the fully managed copy returned by the merge operation.
// The EntityManager $em now manages the persistence of $entity as usual.
.. note::
When you want to serialize/unserialize entities you
have to make all entity properties protected, never private. The
reason for this is, if you serialize a class that was a proxy
instance before, the private variables won't be serialized and a
PHP Notice is thrown.
The semantics of the merge operation, applied to an entity X, are
as follows:

View File

@@ -1,7 +1,7 @@
YAML Mapping
============
.. note::
.. warning::
The YAML driver is deprecated and will be removed in version 3.0.
It is strongly recommended to switch to one of the other mappings.

View File

@@ -73,7 +73,6 @@
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction

View File

@@ -75,7 +75,6 @@ Cookbook
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/implementing-wakeup-or-clone
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction

View File

@@ -43,14 +43,15 @@ What are Entities?
Entities are PHP Objects that can be identified over many requests
by a unique identifier or primary key. These classes don't need to extend any
abstract base class or interface. An entity class must not be final
or contain final methods. Additionally it must not implement
**clone** nor **wakeup**, unless it :doc:`does so safely <../cookbook/implementing-wakeup-or-clone>`.
abstract base class or interface.
An entity contains persistable properties. A persistable property
is an instance variable of the entity that is saved into and retrieved from the database
by Doctrine's data mapping capabilities.
An entity class must not be final nor read-only, although
it can contain final methods or read-only properties.
An Example Model: Bug Tracker
-----------------------------
@@ -136,6 +137,7 @@ step:
<?php
// bootstrap.php
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
@@ -160,14 +162,14 @@ step:
// isDevMode: true,
// );
// database configuration parameters
$conn = array(
// configuring the database connection
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => __DIR__ . '/db.sqlite',
);
], $config)
// obtaining the entity manager
$entityManager = EntityManager::create($conn, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
The YAML driver is deprecated and will be removed in version 3.0.
@@ -888,18 +890,6 @@ domain model to match the requirements:
understand the changes that have happened to the collection that are
noteworthy for persistence.
.. warning::
Lazy load proxies always contain an instance of
Doctrine's EntityManager and all its dependencies. Therefore a
``var_dump()`` will possibly dump a very large recursive structure
which is impossible to render and read. You have to use
``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a
human readable level. Additionally you should be aware that dumping
the EntityManager to a Browser may take several minutes, and the
``Debug::dump()`` method just ignores any occurrences of it in Proxy
instances.
Because we only work with collections for the references we must be
careful to implement a bidirectional reference in the domain model.
The concept of owning or inverse side of a relation is central to
@@ -1588,39 +1578,8 @@ The output of the engineers name is fetched from the database! What is happen
Since we only retrieved the bug by primary key both the engineer and reporter
are not immediately loaded from the database but are replaced by LazyLoading
proxies. These proxies will load behind the scenes, when the first method
is called on them.
Sample code of this proxy generated code can be found in the specified Proxy
Directory, it looks like:
.. code-block:: php
<?php
namespace MyProject\Proxies;
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ORM. DO NOT EDIT THIS FILE.
**/
class UserProxy extends \User implements \Doctrine\ORM\Proxy\Proxy
{
// .. lazy load code here
public function addReportedBug($bug)
{
$this->_load();
return parent::addReportedBug($bug);
}
public function assignedToBug($bug)
{
$this->_load();
return parent::assignedToBug($bug);
}
}
See how upon each method call the proxy is lazily loaded from the
database?
proxies. These proxies will load behind the scenes, when attempting to access
any of their un-initialized state.
The call prints:

View File

@@ -337,6 +337,7 @@
<xs:attribute name="field-name" type="xs:NMTOKEN" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="column-definition" type="xs:string" />
<xs:attribute name="enum-type" type="xs:string" />
<xs:anyAttribute namespace="##other"/>
</xs:complexType>

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache;
use Doctrine\Deprecations\Deprecation;
/**
* Defines entity / collection / query key to be stored in the cache region.
* Allows multiple roles to be stored in the same cache region.
@@ -17,4 +19,18 @@ abstract class CacheKey
* @var string
*/
public $hash;
public function __construct(?string $hash = null)
{
if ($hash === null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10212',
'Calling %s() without providing a value for the $hash parameter is deprecated.',
__METHOD__
);
} else {
$this->hash = $hash;
}
}
}

View File

@@ -52,6 +52,7 @@ class CollectionCacheKey extends CacheKey
$this->ownerIdentifier = $ownerIdentifier;
$this->entityClass = (string) $entityClass;
$this->association = (string) $association;
$this->hash = str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association;
parent::__construct(str_replace('\\', '.', strtolower($entityClass)) . '_' . implode(' ', $ownerIdentifier) . '__' . $association);
}
}

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\Exception\FeatureNotImplemented;
use Doctrine\ORM\Cache\Exception\NonCacheableEntity;
@@ -17,6 +16,7 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Proxy;
use function array_map;
use function array_shift;

View File

@@ -42,6 +42,7 @@ class EntityCacheKey extends CacheKey
$this->identifier = $identifier;
$this->entityClass = $entityClass;
$this->hash = str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier));
parent::__construct(str_replace('\\', '.', strtolower($entityClass) . '_' . implode(' ', $identifier)));
}
}

View File

@@ -41,9 +41,10 @@ class QueryCacheKey extends CacheKey
int $cacheMode = Cache::MODE_NORMAL,
?TimestampCacheKey $timestampKey = null
) {
$this->hash = $cacheId;
$this->lifetime = $lifetime;
$this->cacheMode = $cacheMode;
$this->timestampKey = $timestampKey;
parent::__construct($cacheId);
}
}

View File

@@ -12,6 +12,6 @@ class TimestampCacheKey extends CacheKey
/** @param string $space Result cache id */
public function __construct($space)
{
$this->hash = (string) $space;
parent::__construct((string) $space);
}
}

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM;
use BadMethodCallException;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Cache\ArrayCache;
@@ -13,7 +13,6 @@ use Doctrine\Common\Cache\Cache as CacheDriver;
use Doctrine\Common\Cache\Psr6\CacheAdapter;
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
use Doctrine\Common\Persistence\PersistentObject;
use Doctrine\Common\Proxy\AbstractProxyFactory;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Cache\Exception\CacheException;
@@ -36,6 +35,7 @@ use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Mapping\EntityListenerResolver;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\QuoteStrategy;
use Doctrine\ORM\Mapping\TypedFieldMapper;
use Doctrine\ORM\Proxy\ProxyFactory;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Filter\SQLFilter;
@@ -44,14 +44,17 @@ use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\ObjectRepository;
use Doctrine\Persistence\Reflection\RuntimeReflectionProperty;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\VarExporter\LazyGhostTrait;
use function class_exists;
use function is_a;
use function method_exists;
use function sprintf;
use function strtolower;
use function trait_exists;
use function trim;
/**
@@ -92,18 +95,18 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the strategy for automatically generating proxy classes.
*
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
* @return int Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
* @psalm-return AutogenerateMode
*/
public function getAutoGenerateProxyClasses()
{
return $this->_attributes['autoGenerateProxyClasses'] ?? AbstractProxyFactory::AUTOGENERATE_ALWAYS;
return $this->_attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
}
/**
* Sets the strategy for automatically generating proxy classes.
*
* @param bool|int $autoGenerate Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
* @param bool|int $autoGenerate Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
* @psalm-param bool|AutogenerateMode $autoGenerate
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
*
@@ -172,16 +175,21 @@ class Configuration extends \Doctrine\DBAL\Configuration
);
if (! class_exists(AnnotationReader::class)) {
throw new LogicException(sprintf(
throw new LogicException(
'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library'
. ' is not installed. Please run "composer require doctrine/annotations" or choose a different'
. ' metadata driver.'
));
);
}
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
if ($useSimpleAnnotationReader) {
if (! class_exists(SimpleAnnotationReader::class)) {
throw new BadMethodCallException(
'SimpleAnnotationReader has been removed in doctrine/annotations 2.'
. ' Downgrade to version 1 or set $useSimpleAnnotationReader to false.'
);
}
// Register the ORM Annotations in the AnnotationRegistry
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Doctrine\ORM\Mapping');
@@ -189,7 +197,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
$reader = new AnnotationReader();
}
if (class_exists(ArrayCache::class)) {
if (class_exists(ArrayCache::class) && class_exists(CachedReader::class)) {
$reader = new CachedReader($reader, new ArrayCache());
}
@@ -572,7 +580,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
throw QueryCacheUsesNonPersistentCache::fromDriver($queryCacheImpl);
}
if ($this->getAutoGenerateProxyClasses() !== AbstractProxyFactory::AUTOGENERATE_NEVER) {
if ($this->getAutoGenerateProxyClasses() !== ProxyFactory::AUTOGENERATE_NEVER) {
throw ProxyClassesAlwaysRegenerating::create();
}
@@ -717,7 +725,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $name
*
* @return string|callable|null
* @psalm-return class-string|callable|null $name
* @psalm-return class-string|callable|null
*/
public function getCustomDatetimeFunction($name)
{
@@ -746,6 +754,22 @@ class Configuration extends \Doctrine\DBAL\Configuration
}
}
/**
* Sets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
*/
public function setTypedFieldMapper(?TypedFieldMapper $typedFieldMapper): void
{
$this->_attributes['typedFieldMapper'] = $typedFieldMapper;
}
/**
* Gets a TypedFieldMapper for php typed fields to DBAL types auto-completion.
*/
public function getTypedFieldMapper(): ?TypedFieldMapper
{
return $this->_attributes['typedFieldMapper'] ?? null;
}
/**
* Sets the custom hydrator modes in one pass.
*
@@ -1073,4 +1097,28 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$this->_attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
}
public function isLazyGhostObjectEnabled(): bool
{
return $this->_attributes['isLazyGhostObjectEnabled'] ?? false;
}
public function setLazyGhostObjectEnabled(bool $flag): void
{
if ($flag && ! trait_exists(LazyGhostTrait::class)) {
throw new LogicException(
'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library'
. ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".'
);
}
if ($flag && ! class_exists(RuntimeReflectionProperty::class)) {
throw new LogicException(
'Lazy ghost objects cannot be enabled because the "doctrine/persistence" library'
. ' version 3.1 or higher is not installed. Please run "composer update doctrine/persistence".'
);
}
$this->_attributes['isLazyGhostObjectEnabled'] = $flag;
}
}

View File

@@ -9,6 +9,8 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\Persistence\ObjectManagerDecorator;
use function func_get_arg;
use function func_num_args;
use function get_debug_type;
use function method_exists;
use function sprintf;
@@ -211,6 +213,20 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
$this->wrapped->flush($entity);
}
/**
* {@inheritdoc}
*/
public function refresh($object)
{
$lockMode = null;
if (func_num_args() > 1) {
$lockMode = func_get_arg(1);
}
$this->wrapped->refresh($object, $lockMode);
}
/**
* {@inheritdoc}
*/

View File

@@ -60,8 +60,8 @@ use function strpos;
* $paths = ['/path/to/entity/mapping/files'];
*
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
* $dbParams = ['driver' => 'pdo_sqlite', 'memory' => true];
* $entityManager = EntityManager::create($dbParams, $config);
* $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
* $entityManager = new EntityManager($connection, $config);
*
* For more information see
* {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/configuration.html}
@@ -156,7 +156,7 @@ class EntityManager implements EntityManagerInterface
* Creates a new EntityManager that operates on the given database connection
* and uses the given Configuration and EventManager implementations.
*/
public function __construct(Connection $conn, Configuration $config)
public function __construct(Connection $conn, Configuration $config, ?EventManager $eventManager = null)
{
if (! $config->getMetadataDriverImpl()) {
throw MissingMappingDriverImplementation::create();
@@ -164,7 +164,7 @@ class EntityManager implements EntityManagerInterface
$this->conn = $conn;
$this->config = $config;
$this->eventManager = $conn->getEventManager();
$this->eventManager = $eventManager ?? $conn->getEventManager();
$metadataFactoryClassName = $config->getClassMetadataFactoryName();
@@ -693,14 +693,16 @@ class EntityManager implements EntityManagerInterface
* Refreshes the persistent state of an entity from the database,
* overriding any local changes that have not yet been persisted.
*
* @param object $entity The entity to refresh.
* @param object $entity The entity to refresh
* @psalm-param LockMode::*|null $lockMode
*
* @return void
*
* @throws ORMInvalidArgumentException
* @throws ORMException
* @throws TransactionRequiredException
*/
public function refresh($entity)
public function refresh($entity, ?int $lockMode = null)
{
if (! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
@@ -708,7 +710,7 @@ class EntityManager implements EntityManagerInterface
$this->errorIfClosed();
$this->unitOfWork->refresh($entity);
$this->unitOfWork->refresh($entity, $lockMode);
}
/**
@@ -767,6 +769,8 @@ class EntityManager implements EntityManagerInterface
/**
* {@inheritDoc}
*
* @psalm-return never
*/
public function copy($entity, $deep = false)
{
@@ -952,6 +956,8 @@ class EntityManager implements EntityManagerInterface
/**
* Factory method to create EntityManager instances.
*
* @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection and call the constructor.
*
* @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager|null $eventManager The EventManager instance to use.
@@ -964,6 +970,15 @@ class EntityManager implements EntityManagerInterface
*/
public static function create($connection, Configuration $config, ?EventManager $eventManager = null)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9961',
'%s() is deprecated. To boostrap a DBAL connection, call %s::getConnection() instead. Use the constructor to create an instance of %s.',
__METHOD__,
DriverManager::class,
self::class
);
$connection = static::createConnection($connection, $config, $eventManager);
return new EntityManager($connection, $config);
@@ -972,6 +987,8 @@ class EntityManager implements EntityManagerInterface
/**
* Factory method to create Connection instances.
*
* @deprecated Use {@see DriverManager::getConnection()} to bootstrap the connection.
*
* @param mixed[]|Connection $connection An array with the connection parameters or an existing Connection instance.
* @param Configuration $config The Configuration instance to use.
* @param EventManager|null $eventManager The EventManager instance to use.
@@ -984,6 +1001,14 @@ class EntityManager implements EntityManagerInterface
*/
protected static function createConnection($connection, Configuration $config, ?EventManager $eventManager = null)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9961',
'%s() is deprecated, call %s::getConnection() instead.',
__METHOD__,
DriverManager::class
);
if (is_array($connection)) {
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
}

View File

@@ -22,6 +22,7 @@ use Doctrine\Persistence\ObjectManager;
*
* @method Mapping\ClassMetadataFactory getMetadataFactory()
* @method mixed wrapInTransaction(callable $func)
* @method void refresh(object $object, ?int $lockMode = null)
*/
interface EntityManagerInterface extends ObjectManager
{

View File

@@ -12,6 +12,8 @@ use Doctrine\Persistence\Event\LifecycleEventArgs as BaseLifecycleEventArgs;
* Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
* of entities.
*
* @deprecated This class will be removed in ORM 3.0. Use one of the dedicated classes instead.
*
* @extends BaseLifecycleEventArgs<EntityManagerInterface>
*/
class LifecycleEventArgs extends BaseLifecycleEventArgs

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Event;
final class PostLoadEventArgs extends LifecycleEventArgs
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Event;
final class PostPersistEventArgs extends LifecycleEventArgs
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Event;
final class PostRemoveEventArgs extends LifecycleEventArgs
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Event;
final class PostUpdateEventArgs extends LifecycleEventArgs
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Event;
final class PrePersistEventArgs extends LifecycleEventArgs
{
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Event;
final class PreRemoveEventArgs extends LifecycleEventArgs
{
}

View File

@@ -12,7 +12,7 @@ use function get_debug_type;
use function sprintf;
/**
* Class that holds event arguments for a preInsert/preUpdate event.
* Class that holds event arguments for a preUpdate event.
*/
class PreUpdateEventArgs extends LifecycleEventArgs
{

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal\CommitOrder;
/** @internal */
final class Edge
{
/**
* @var string
* @readonly
*/
public $from;
/**
* @var string
* @readonly
*/
public $to;
/**
* @var int
* @readonly
*/
public $weight;
public function __construct(string $from, string $to, int $weight)
{
$this->from = $from;
$this->to = $to;
$this->weight = $weight;
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal\CommitOrder;
use Doctrine\ORM\Mapping\ClassMetadata;
/** @internal */
final class Vertex
{
/**
* @var string
* @readonly
*/
public $hash;
/**
* @var int
* @psalm-var VertexState::*
*/
public $state = VertexState::NOT_VISITED;
/**
* @var ClassMetadata
* @readonly
*/
public $value;
/** @var array<string, Edge> */
public $dependencyList = [];
public function __construct(string $hash, ClassMetadata $value)
{
$this->hash = $hash;
$this->value = $value;
}
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal\CommitOrder;
/** @internal */
final class VertexState
{
public const NOT_VISITED = 0;
public const IN_PROGRESS = 1;
public const VISITED = 2;
private function __construct()
{
}
}

View File

@@ -4,7 +4,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal;
use stdClass;
use Doctrine\ORM\Internal\CommitOrder\Edge;
use Doctrine\ORM\Internal\CommitOrder\Vertex;
use Doctrine\ORM\Internal\CommitOrder\VertexState;
use Doctrine\ORM\Mapping\ClassMetadata;
use function array_reverse;
@@ -17,33 +20,28 @@ use function array_reverse;
*/
class CommitOrderCalculator
{
public const NOT_VISITED = 0;
public const IN_PROGRESS = 1;
public const VISITED = 2;
/** @deprecated */
public const NOT_VISITED = VertexState::NOT_VISITED;
/** @deprecated */
public const IN_PROGRESS = VertexState::IN_PROGRESS;
/** @deprecated */
public const VISITED = VertexState::VISITED;
/**
* Matrix of nodes (aka. vertex).
*
* Keys are provided hashes and values are the node definition objects.
*
* The node state definition contains the following properties:
*
* - <b>state</b> (integer)
* Whether the node is NOT_VISITED or IN_PROGRESS
*
* - <b>value</b> (object)
* Actual node value
*
* - <b>dependencyList</b> (array<string>)
* Map of node dependencies defined as hashes.
*
* @var array<stdClass>
* @var array<string, Vertex>
*/
private $nodeList = [];
/**
* Volatile variable holding calculated nodes during sorting process.
*
* @psalm-var list<object>
* @psalm-var list<ClassMetadata>
*/
private $sortedNodeList = [];
@@ -62,21 +60,14 @@ class CommitOrderCalculator
/**
* Adds a new node (vertex) to the graph, assigning its hash and value.
*
* @param string $hash
* @param object $node
* @param string $hash
* @param ClassMetadata $node
*
* @return void
*/
public function addNode($hash, $node)
{
$vertex = new stdClass();
$vertex->hash = $hash;
$vertex->state = self::NOT_VISITED;
$vertex->value = $node;
$vertex->dependencyList = [];
$this->nodeList[$hash] = $vertex;
$this->nodeList[$hash] = new Vertex($hash, $node);
}
/**
@@ -90,14 +81,8 @@ class CommitOrderCalculator
*/
public function addDependency($fromHash, $toHash, $weight)
{
$vertex = $this->nodeList[$fromHash];
$edge = new stdClass();
$edge->from = $fromHash;
$edge->to = $toHash;
$edge->weight = $weight;
$vertex->dependencyList[$toHash] = $edge;
$this->nodeList[$fromHash]->dependencyList[$toHash]
= new Edge($fromHash, $toHash, $weight);
}
/**
@@ -106,12 +91,12 @@ class CommitOrderCalculator
*
* {@internal Highly performance-sensitive method.}
*
* @psalm-return list<object>
* @psalm-return list<ClassMetadata>
*/
public function sort()
{
foreach ($this->nodeList as $vertex) {
if ($vertex->state !== self::NOT_VISITED) {
if ($vertex->state !== VertexState::NOT_VISITED) {
continue;
}
@@ -131,19 +116,19 @@ class CommitOrderCalculator
*
* {@internal Highly performance-sensitive method.}
*/
private function visit(stdClass $vertex): void
private function visit(Vertex $vertex): void
{
$vertex->state = self::IN_PROGRESS;
$vertex->state = VertexState::IN_PROGRESS;
foreach ($vertex->dependencyList as $edge) {
$adjacentVertex = $this->nodeList[$edge->to];
switch ($adjacentVertex->state) {
case self::VISITED:
case VertexState::VISITED:
// Do nothing, since node was already visited
break;
case self::IN_PROGRESS:
case VertexState::IN_PROGRESS:
if (
isset($adjacentVertex->dependencyList[$vertex->hash]) &&
$adjacentVertex->dependencyList[$vertex->hash]->weight < $edge->weight
@@ -153,25 +138,25 @@ class CommitOrderCalculator
foreach ($adjacentVertex->dependencyList as $adjacentEdge) {
$adjacentEdgeVertex = $this->nodeList[$adjacentEdge->to];
if ($adjacentEdgeVertex->state === self::NOT_VISITED) {
if ($adjacentEdgeVertex->state === VertexState::NOT_VISITED) {
$this->visit($adjacentEdgeVertex);
}
}
$adjacentVertex->state = self::VISITED;
$adjacentVertex->state = VertexState::VISITED;
$this->sortedNodeList[] = $adjacentVertex->value;
}
break;
case self::NOT_VISITED:
case VertexState::NOT_VISITED:
$this->visit($adjacentVertex);
}
}
if ($vertex->state !== self::VISITED) {
$vertex->state = self::VISITED;
if ($vertex->state !== VertexState::VISITED) {
$vertex->state = VertexState::VISITED;
$this->sortedNodeList[] = $vertex->value;
}

View File

@@ -617,6 +617,7 @@ abstract class AbstractHydrator
'fieldName' => $fieldName,
'type' => $type,
'dqlAlias' => $dqlAlias,
'enumType' => $this->_rsm->enumMappings[$key] ?? null,
];
}

View File

@@ -4,12 +4,13 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\Hydration;
use BackedEnum;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Proxy\Proxy;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Proxy;
use function array_fill_keys;
use function array_keys;
@@ -246,7 +247,12 @@ class ObjectHydrator extends AbstractHydrator
}
$discrMap = $this->_metadataCache[$className]->discriminatorMap;
$discriminatorValue = (string) $data[$discrColumn];
$discriminatorValue = $data[$discrColumn];
if ($discriminatorValue instanceof BackedEnum) {
$discriminatorValue = $discriminatorValue->value;
}
$discriminatorValue = (string) $discriminatorValue;
if (! isset($discrMap[$discriminatorValue])) {
throw HydrationException::invalidDiscriminatorValue($discriminatorValue, array_keys($discrMap));

View File

@@ -148,10 +148,6 @@ class SimpleObjectHydrator extends AbstractHydrator
}
}
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
}
$uow = $this->_em->getUnitOfWork();
$entity = $uow->createEntity($entityName, $data, $this->_hints);

View File

@@ -5,8 +5,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\ListenersInvoker;
use Doctrine\ORM\Event\PostLoadEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -67,7 +67,7 @@ final class HydrationCompleteHandler
$class,
Events::postLoad,
$entity,
new LifecycleEventArgs($entity, $this->em),
new PostLoadEventArgs($entity, $this->em),
$invoke
);
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
/** @deprecated Use {@see MappingAttribute} instead. */
interface Annotation
{
}

View File

@@ -5,53 +5,60 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
/**
* This annotation is used to override association mapping of property for an entity relationship.
* This attribute is used to override association mapping of property for an entity relationship.
*
* @Annotation
* @NamedArgumentConstructor
* @Target("ANNOTATION")
*/
final class AssociationOverride implements Annotation
final class AssociationOverride implements MappingAttribute
{
/**
* The name of the relationship property whose mapping is being overridden.
*
* @var string
* @readonly
*/
public $name;
/**
* The join column that is being mapped to the persistent attribute.
*
* @var array<\Doctrine\ORM\Mapping\JoinColumn>|null
* @var array<JoinColumn>|null
* @readonly
*/
public $joinColumns;
/**
* The join column that is being mapped to the persistent attribute.
*
* @var array<\Doctrine\ORM\Mapping\JoinColumn>|null
* @var array<JoinColumn>|null
* @readonly
*/
public $inverseJoinColumns;
/**
* The join table that maps the relationship.
*
* @var \Doctrine\ORM\Mapping\JoinTable|null
* @var JoinTable|null
* @readonly
*/
public $joinTable;
/**
* The name of the association-field on the inverse-side.
*
* @var ?string
* @var string|null
* @readonly
*/
public $inversedBy;
/**
* The fetching strategy to use for the association.
*
* @var ?string
* @var string|null
* @psalm-var 'LAZY'|'EAGER'|'EXTRA_LAZY'|null
* @readonly
* @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
*/
public $fetch;
@@ -59,6 +66,7 @@ final class AssociationOverride implements Annotation
/**
* @param JoinColumn|array<JoinColumn> $joinColumns
* @param JoinColumn|array<JoinColumn> $inverseJoinColumns
* @psalm-param 'LAZY'|'EAGER'|'EXTRA_LAZY'|null $fetch
*/
public function __construct(
string $name,

View File

@@ -6,22 +6,24 @@ namespace Doctrine\ORM\Mapping;
use Attribute;
use function array_values;
use function is_array;
/**
* This annotation is used to override association mappings of relationship properties.
* This attribute is used to override association mappings of relationship properties.
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class AssociationOverrides implements Annotation
final class AssociationOverrides implements MappingAttribute
{
/**
* Mapping overrides of relationship properties.
*
* @var array<AssociationOverride>
* @var list<AssociationOverride>
* @readonly
*/
public $overrides = [];
@@ -36,8 +38,8 @@ final class AssociationOverrides implements Annotation
if (! ($override instanceof AssociationOverride)) {
throw MappingException::invalidOverrideType('AssociationOverride', $override);
}
$this->overrides[] = $override;
}
$this->overrides = array_values($overrides);
}
}

View File

@@ -5,25 +5,27 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
/**
* This annotation is used to override the mapping of a entity property.
* This attribute is used to override the mapping of a entity property.
*
* @Annotation
* @NamedArgumentConstructor
* @Target("ANNOTATION")
*/
final class AttributeOverride implements Annotation
final class AttributeOverride implements MappingAttribute
{
/**
* The name of the property whose mapping is being overridden.
*
* @var string
* @readonly
*/
public $name;
/**
* The column definition.
*
* @var \Doctrine\ORM\Mapping\Column
* @var Column
* @readonly
*/
public $column;

View File

@@ -6,22 +6,24 @@ namespace Doctrine\ORM\Mapping;
use Attribute;
use function array_values;
use function is_array;
/**
* This annotation is used to override the mapping of a entity property.
* This attribute is used to override the mapping of a entity property.
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class AttributeOverrides implements Annotation
final class AttributeOverrides implements MappingAttribute
{
/**
* One or more field or property mapping overrides.
*
* @var array<AttributeOverride>
* @var list<AttributeOverride>
* @readonly
*/
public $overrides = [];
@@ -36,8 +38,8 @@ final class AttributeOverrides implements Annotation
if (! ($override instanceof AttributeOverride)) {
throw MappingException::invalidOverrideType('AttributeOverride', $override);
}
$this->overrides[] = $override;
}
$this->overrides = array_values($overrides);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Builder;
use BackedEnum;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
@@ -218,16 +219,19 @@ class ClassMetadataBuilder
* @param string $name
* @param string $type
* @param int $length
* @psalm-param class-string<BackedEnum>|null $enumType
*
* @return $this
*/
public function setDiscriminatorColumn($name, $type = 'string', $length = 255)
public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null)
{
$this->cm->setDiscriminatorColumn(
[
'name' => $name,
'type' => $type,
'length' => $length,
'columnDefinition' => $columnDefinition,
'enumType' => $enumType,
]
);

View File

@@ -15,17 +15,21 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target({"CLASS","PROPERTY"})
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
final class Cache implements Annotation
final class Cache implements MappingAttribute
{
/**
* The concurrency strategy.
*
* @Enum({"READ_ONLY", "NONSTRICT_READ_WRITE", "READ_WRITE"})
* @var string The concurrency strategy.
* @var string
* @psalm-var 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE'
*/
public $usage = 'READ_ONLY';
/** @var string|null Cache region name. */
public $region;
/** @psalm-param 'READ_ONLY'|'NONSTRICT_READ_WRITE'|'READ_WRITE' $usage */
public function __construct(string $usage = 'READ_ONLY', ?string $region = null)
{
$this->usage = $usage;

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use ReflectionProperty;
final class ChainTypedFieldMapper implements TypedFieldMapper
{
/**
* @readonly
* @var TypedFieldMapper[] $typedFieldMappers
*/
private array $typedFieldMappers;
public function __construct(TypedFieldMapper ...$typedFieldMappers)
{
$this->typedFieldMappers = $typedFieldMappers;
}
/**
* {@inheritdoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
foreach ($this->typedFieldMappers as $typedFieldMapper) {
$mapping = $typedFieldMapper->validateAndComplete($mapping, $field);
}
return $mapping;
}
}

View File

@@ -13,16 +13,19 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class ChangeTrackingPolicy implements Annotation
final class ChangeTrackingPolicy implements MappingAttribute
{
/**
* The change tracking policy.
*
* @var string
* @psalm-var 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY'
* @readonly
* @Enum({"DEFERRED_IMPLICIT", "DEFERRED_EXPLICIT", "NOTIFY"})
*/
public $value;
/** @psalm-param 'DEFERRED_IMPLICIT'|'DEFERRED_EXPLICIT'|'NOTIFY' $value */
public function __construct(string $value)
{
$this->value = $value;

View File

@@ -21,8 +21,8 @@ class ClassMetadata extends ClassMetadataInfo
* @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)
public function __construct($entityName, ?NamingStrategy $namingStrategy = null, ?TypedFieldMapper $typedFieldMapper = null)
{
parent::__construct($entityName, $namingStrategy);
parent::__construct($entityName, $namingStrategy, $typedFieldMapper);
}
}

View File

@@ -293,7 +293,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
*/
protected function newClassMetadataInstance($className)
{
return new ClassMetadata($className, $this->em->getConfiguration()->getNamingStrategy());
return new ClassMetadata(
$className,
$this->em->getConfiguration()->getNamingStrategy(),
$this->em->getConfiguration()->getTypedFieldMapper()
);
}
/**

View File

@@ -6,12 +6,8 @@ namespace Doctrine\ORM\Mapping;
use BackedEnum;
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;
@@ -23,7 +19,6 @@ use Doctrine\Persistence\Mapping\ReflectionService;
use InvalidArgumentException;
use LogicException;
use ReflectionClass;
use ReflectionEnum;
use ReflectionNamedType;
use ReflectionProperty;
use RuntimeException;
@@ -142,6 +137,14 @@ use const PHP_VERSION_ID;
* type: int,
* unique?: bool,
* }
* @psalm-type DiscriminatorColumnMapping = array{
* name: string,
* fieldName: string,
* type: string,
* length?: int,
* columnDefinition?: string|null,
* enumType?: class-string<BackedEnum>|null,
* }
*/
class ClassMetadataInfo implements ClassMetadata
{
@@ -574,7 +577,8 @@ class ClassMetadataInfo implements ClassMetadata
* READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
* inheritance mappings.
*
* @psalm-var array{name: string, fieldName: string, type: string, length?: int, columnDefinition?: string|null}|null
* @var array<string, mixed>
* @psalm-var DiscriminatorColumnMapping|null
*/
public $discriminatorColumn;
@@ -800,6 +804,9 @@ class ClassMetadataInfo implements ClassMetadata
/** @var InstantiatorInterface|null */
private $instantiator;
/** @var TypedFieldMapper $typedFieldMapper */
private $typedFieldMapper;
/**
* Initializes a new ClassMetadata instance that will hold the object-relational mapping
* metadata of the class with the given name.
@@ -807,12 +814,13 @@ class ClassMetadataInfo implements ClassMetadata
* @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)
public function __construct($entityName, ?NamingStrategy $namingStrategy = null, ?TypedFieldMapper $typedFieldMapper = null)
{
$this->name = $entityName;
$this->rootEntityName = $entityName;
$this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
$this->instantiator = new Instantiator();
$this->name = $entityName;
$this->rootEntityName = $entityName;
$this->namingStrategy = $namingStrategy ?? new DefaultNamingStrategy();
$this->instantiator = new Instantiator();
$this->typedFieldMapper = $typedFieldMapper ?? new DefaultTypedFieldMapper();
}
/**
@@ -1574,56 +1582,15 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Validates & completes the given field mapping based on typed property.
*
* @param mixed[] $mapping The field mapping to validate & complete.
* @param array{fieldName: string, type?: mixed} $mapping The field mapping to validate & complete.
*
* @return mixed[] The updated mapping.
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
*/
private function validateAndCompleteTypedFieldMapping(array $mapping): array
{
$type = $this->reflClass->getProperty($mapping['fieldName'])->getType();
$field = $this->reflClass->getProperty($mapping['fieldName']);
if ($type) {
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['enumType'] = $type->getName();
$reflection = new ReflectionEnum($type->getName());
$type = $reflection->getBackingType();
assert($type instanceof ReflectionNamedType);
}
switch ($type->getName()) {
case DateInterval::class:
$mapping['type'] = Types::DATEINTERVAL;
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;
}
}
}
$mapping = $this->typedFieldMapper->validateAndComplete($mapping, $field);
return $mapping;
}
@@ -1631,7 +1598,7 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Validates & completes the basic mapping information based on typed property.
*
* @param mixed[] $mapping The mapping.
* @param array{type: self::ONE_TO_ONE|self::MANY_TO_ONE|self::ONE_TO_MANY|self::MANY_TO_MANY, fieldName: string, targetEntity?: class-string} $mapping The mapping.
*
* @return mixed[] The updated mapping.
*/
@@ -1653,7 +1620,13 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Validates & completes the given field mapping.
*
* @psalm-param array<string, mixed> $mapping The field mapping to validate & complete.
* @psalm-param array{
* fieldName?: string,
* columnName?: string,
* id?: bool,
* generated?: int,
* enumType?: class-string,
* } $mapping The field mapping to validate & complete.
*
* @return mixed[] The updated mapping.
*
@@ -1897,7 +1870,6 @@ 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}
@@ -3215,7 +3187,7 @@ class ClassMetadataInfo implements ClassMetadata
* @see getDiscriminatorColumn()
*
* @param mixed[]|null $columnDef
* @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null}|null $columnDef
* @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string<BackedEnum>|null}|null $columnDef
*
* @return void
*
@@ -3248,7 +3220,10 @@ class ClassMetadataInfo implements ClassMetadata
}
}
/** @return array<string, mixed> */
/**
* @return array<string, mixed>
* @psalm-return DiscriminatorColumnMapping
*/
final public function getDiscriminatorColumn(): array
{
if ($this->discriminatorColumn === null) {

View File

@@ -5,29 +5,40 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Attribute;
use BackedEnum;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
/**
* @Annotation
* @NamedArgumentConstructor()
* @NamedArgumentConstructor
* @Target({"PROPERTY","ANNOTATION"})
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Column implements Annotation
final class Column implements MappingAttribute
{
/** @var string|null */
/**
* @var string|null
* @readonly
*/
public $name;
/** @var mixed */
/**
* @var mixed
* @readonly
*/
public $type;
/** @var int|null */
/**
* @var int|null
* @readonly
*/
public $length;
/**
* The precision for a decimal (exact numeric) column (Applies only for decimal column).
*
* @var int|null
* @readonly
*/
public $precision = 0;
@@ -35,40 +46,63 @@ final class Column implements Annotation
* The scale for a decimal (exact numeric) column (Applies only for decimal column).
*
* @var int|null
* @readonly
*/
public $scale = 0;
/** @var bool */
/**
* @var bool
* @readonly
*/
public $unique = false;
/** @var bool */
/**
* @var bool
* @readonly
*/
public $nullable = false;
/** @var bool */
/**
* @var bool
* @readonly
*/
public $insertable = true;
/** @var bool */
/**
* @var bool
* @readonly
*/
public $updatable = true;
/** @var class-string<\BackedEnum>|null */
/**
* @var class-string<BackedEnum>|null
* @readonly
*/
public $enumType = null;
/** @var array<string,mixed> */
/**
* @var array<string,mixed>
* @readonly
*/
public $options = [];
/** @var string|null */
/**
* @var string|null
* @readonly
*/
public $columnDefinition;
/**
* @var string|null
* @readonly
* @psalm-var 'NEVER'|'INSERT'|'ALWAYS'|null
* @Enum({"NEVER", "INSERT", "ALWAYS"})
*/
public $generated;
/**
* @param class-string<\BackedEnum>|null $enumType
* @param array<string,mixed> $options
* @param class-string<BackedEnum>|null $enumType
* @param array<string,mixed> $options
* @psalm-param 'NEVER'|'INSERT'|'ALWAYS'|null $generated
*/
public function __construct(

View File

@@ -11,7 +11,7 @@ namespace Doctrine\ORM\Mapping;
* @Annotation
* @Target("ANNOTATION")
*/
final class ColumnResult implements Annotation
final class ColumnResult implements MappingAttribute
{
/**
* The name of a column in the SELECT clause of a SQL query.

View File

@@ -13,9 +13,12 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class CustomIdGenerator implements Annotation
final class CustomIdGenerator implements MappingAttribute
{
/** @var string|null */
/**
* @var string|null
* @readonly
*/
public $class;
public function __construct(?string $class = null)

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use DateInterval;
use DateTime;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use ReflectionEnum;
use ReflectionNamedType;
use ReflectionProperty;
use function array_merge;
use function assert;
use function enum_exists;
use const PHP_VERSION_ID;
/** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */
final class DefaultTypedFieldMapper implements TypedFieldMapper
{
/** @var array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
private $typedFieldMappings;
private const DEFAULT_TYPED_FIELD_MAPPINGS = [
DateInterval::class => Types::DATEINTERVAL,
DateTime::class => Types::DATETIME_MUTABLE,
DateTimeImmutable::class => Types::DATETIME_IMMUTABLE,
'array' => Types::JSON,
'bool' => Types::BOOLEAN,
'float' => Types::FLOAT,
'int' => Types::INTEGER,
'string' => Types::STRING,
];
/** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
public function __construct(array $typedFieldMappings = [])
{
$this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings);
}
/**
* {@inheritdoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
$type = $field->getType();
if (
! isset($mapping['type'])
&& ($type instanceof ReflectionNamedType)
) {
if (PHP_VERSION_ID >= 80100 && ! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['enumType'] = $type->getName();
$reflection = new ReflectionEnum($type->getName());
$type = $reflection->getBackingType();
assert($type instanceof ReflectionNamedType);
}
if (isset($this->typedFieldMappings[$type->getName()])) {
$mapping['type'] = $this->typedFieldMappings[$type->getName()];
}
}
return $mapping;
}
}

View File

@@ -13,36 +13,50 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class DiscriminatorColumn implements Annotation
final class DiscriminatorColumn implements MappingAttribute
{
/** @var string|null */
/**
* @var string|null
* @readonly
*/
public $name;
/** @var string|null */
/**
* @var string|null
* @readonly
*/
public $type;
/** @var int|null */
/**
* @var int|null
* @readonly
*/
public $length;
/**
* Field name used in non-object hydration (array/scalar).
*
* @var mixed
* @var string|null
* @readonly
*/
public $fieldName;
/** @var string */
public $columnDefinition;
/**
* @var class-string<\BackedEnum>|null
* @readonly
*/
public $enumType = null;
/** @param class-string<\BackedEnum>|null $enumType */
public function __construct(
?string $name = null,
?string $type = null,
?int $length = null,
?string $columnDefinition = null
?string $columnDefinition = null,
?string $enumType = null
) {
$this->name = $name;
$this->type = $type;
$this->length = $length;
$this->columnDefinition = $columnDefinition;
$this->enumType = $enumType;
}
}

View File

@@ -13,9 +13,12 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class DiscriminatorMap implements Annotation
final class DiscriminatorMap implements MappingAttribute
{
/** @var array<int|string, string> */
/**
* @var array<int|string, string>
* @readonly
*/
public $value;
/** @param array<int|string, string> $value */

View File

@@ -62,6 +62,11 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
*/
public function __construct($reader, $paths = null)
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/10098',
'The annotation mapping driver is deprecated and will be removed in Doctrine ORM 3.0, please migrate to the attribute or XML driver.'
);
$this->reader = $reader;
$this->addPaths((array) $paths);
@@ -314,6 +319,7 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
'type' => $discrColumnAnnot->type ?: 'string',
'length' => $discrColumnAnnot->length ?? 255,
'columnDefinition' => $discrColumnAnnot->columnDefinition,
'enumType' => $discrColumnAnnot->enumType,
]
);
} else {
@@ -541,8 +547,9 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
}
/**
* @param mixed[] $joinColumns
* @psalm-param array<string, mixed> $mapping
* @param mixed[] $joinColumns
* @param class-string $className
* @param array<string, mixed> $mapping
*/
private function loadRelationShipMapping(
ReflectionProperty $property,
@@ -655,6 +662,8 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
/**
* Attempts to resolve the fetch mode.
*
* @param class-string $className
*
* @psalm-return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
*
* @throws MappingException If the fetch mode is not valid.

View File

@@ -9,6 +9,7 @@ use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingAttribute;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
@@ -30,15 +31,20 @@ class AttributeDriver extends CompatibilityAnnotationDriver
{
use ColocatedMappingDriver;
/** @var array<string,int> */
// @phpcs:ignore
protected $entityAnnotationClasses = [
private const ENTITY_ATTRIBUTE_CLASSES = [
Mapping\Entity::class => 1,
Mapping\MappedSuperclass::class => 2,
];
/**
* The annotation reader.
* @deprecated override isTransient() instead of overriding this property
*
* @var array<class-string<MappingAttribute>, int>
*/
protected $entityAnnotationClasses = self::ENTITY_ATTRIBUTE_CLASSES;
/**
* The attribute reader.
*
* @internal this property will be private in 3.0
*
@@ -58,6 +64,15 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$this->reader = new AttributeReader();
$this->addPaths($paths);
if ($this->entityAnnotationClasses !== self::ENTITY_ATTRIBUTE_CLASSES) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10204',
'Changing the value of %s::$entityAnnotationClasses is deprecated and will have no effect in Doctrine ORM 3.0.',
self::class
);
}
}
/**
@@ -84,11 +99,11 @@ class AttributeDriver extends CompatibilityAnnotationDriver
*/
public function isTransient($className)
{
$classAnnotations = $this->reader->getClassAnnotations(new ReflectionClass($className));
$classAttributes = $this->reader->getClassAttributes(new ReflectionClass($className));
foreach ($classAnnotations as $a) {
$annot = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
if (isset($this->entityAnnotationClasses[get_class($annot)])) {
foreach ($classAttributes as $a) {
$attr = $a instanceof RepeatableAttributeCollection ? $a[0] : $a;
if (isset($this->entityAnnotationClasses[get_class($attr)])) {
return false;
}
}
@@ -107,13 +122,13 @@ class AttributeDriver extends CompatibilityAnnotationDriver
public function loadMetadataForClass($className, PersistenceClassMetadata $metadata): void
{
$reflectionClass = $metadata->getReflectionClass()
// this happens when running annotation driver in combination with
// this happens when running attribute driver in combination with
// static reflection services. This is not the nicest fix
?? new ReflectionClass($metadata->name);
$classAttributes = $this->reader->getClassAnnotations($reflectionClass);
$classAttributes = $this->reader->getClassAttributes($reflectionClass);
// Evaluate Entity annotation
// Evaluate Entity attribute
if (isset($classAttributes[Mapping\Entity::class])) {
$entityAttribute = $classAttributes[Mapping\Entity::class];
if ($entityAttribute->repositoryClass !== null) {
@@ -226,7 +241,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$metadata->setPrimaryTable($primaryTable);
// Evaluate @Cache annotation
// Evaluate #[Cache] attribute
if (isset($classAttributes[Mapping\Cache::class])) {
$cacheAttribute = $classAttributes[Mapping\Cache::class];
$cacheMap = [
@@ -237,7 +252,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$metadata->enableCache($cacheMap);
}
// Evaluate InheritanceType annotation
// Evaluate InheritanceType attribute
if (isset($classAttributes[Mapping\InheritanceType::class])) {
$inheritanceTypeAttribute = $classAttributes[Mapping\InheritanceType::class];
@@ -246,7 +261,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
);
if ($metadata->inheritanceType !== ClassMetadata::INHERITANCE_TYPE_NONE) {
// Evaluate DiscriminatorColumn annotation
// Evaluate DiscriminatorColumn attribute
if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
$discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
@@ -256,13 +271,14 @@ class AttributeDriver extends CompatibilityAnnotationDriver
'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string',
'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255,
'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null,
'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null,
]
);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
// Evaluate DiscriminatorMap annotation
// Evaluate DiscriminatorMap attribute
if (isset($classAttributes[Mapping\DiscriminatorMap::class])) {
$discrMapAttribute = $classAttributes[Mapping\DiscriminatorMap::class];
$metadata->setDiscriminatorMap($discrMapAttribute->value);
@@ -270,7 +286,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
}
}
// Evaluate DoctrineChangeTrackingPolicy annotation
// Evaluate DoctrineChangeTrackingPolicy attribute
if (isset($classAttributes[Mapping\ChangeTrackingPolicy::class])) {
$changeTrackingAttribute = $classAttributes[Mapping\ChangeTrackingPolicy::class];
$metadata->setChangeTrackingPolicy(constant('Doctrine\ORM\Mapping\ClassMetadata::CHANGETRACKING_' . $changeTrackingAttribute->value));
@@ -293,8 +309,8 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$mapping = [];
$mapping['fieldName'] = $property->getName();
// Evaluate @Cache annotation
$cacheAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Cache::class);
// Evaluate #[Cache] attribute
$cacheAttribute = $this->reader->getPropertyAttribute($property, Mapping\Cache::class);
if ($cacheAttribute !== null) {
assert($cacheAttribute instanceof Mapping\Cache);
@@ -307,10 +323,10 @@ class AttributeDriver extends CompatibilityAnnotationDriver
);
}
// Check for JoinColumn/JoinColumns annotations
// Check for JoinColumn/JoinColumns attributes
$joinColumns = [];
$joinColumnAttributes = $this->reader->getPropertyAnnotationCollection($property, Mapping\JoinColumn::class);
$joinColumnAttributes = $this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class);
foreach ($joinColumnAttributes as $joinColumnAttribute) {
$joinColumns[] = $this->joinColumnToArray($joinColumnAttribute);
@@ -318,35 +334,35 @@ class AttributeDriver extends CompatibilityAnnotationDriver
// 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);
$columnAttribute = $this->reader->getPropertyAttribute($property, Mapping\Column::class);
$oneToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToOne::class);
$oneToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\OneToMany::class);
$manyToOneAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToOne::class);
$manyToManyAttribute = $this->reader->getPropertyAttribute($property, Mapping\ManyToMany::class);
$embeddedAttribute = $this->reader->getPropertyAttribute($property, Mapping\Embedded::class);
if ($columnAttribute !== null) {
$mapping = $this->columnToArray($property->getName(), $columnAttribute);
if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) {
if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
$mapping['id'] = true;
}
$generatedValueAttribute = $this->reader->getPropertyAnnotation($property, Mapping\GeneratedValue::class);
$generatedValueAttribute = $this->reader->getPropertyAttribute($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)) {
if ($this->reader->getPropertyAttribute($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);
$seqGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\SequenceGenerator::class);
$customGeneratorAttribute = $this->reader->getPropertyAttribute($property, Mapping\CustomIdGenerator::class);
if ($seqGeneratorAttribute !== null) {
$metadata->setSequenceGeneratorDefinition(
@@ -364,7 +380,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
);
}
} elseif ($oneToOneAttribute !== null) {
if ($this->reader->getPropertyAnnotation($property, Mapping\Id::class)) {
if ($this->reader->getPropertyAttribute($property, Mapping\Id::class)) {
$mapping['id'] = true;
}
@@ -384,7 +400,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$mapping['orphanRemoval'] = $oneToManyAttribute->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $oneToManyAttribute->fetch);
$orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
$orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
if ($orderByAttribute !== null) {
$mapping['orderBy'] = $orderByAttribute->value;
@@ -392,7 +408,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$metadata->mapOneToMany($mapping);
} elseif ($manyToOneAttribute !== null) {
$idAttribute = $this->reader->getPropertyAnnotation($property, Mapping\Id::class);
$idAttribute = $this->reader->getPropertyAttribute($property, Mapping\Id::class);
if ($idAttribute !== null) {
$mapping['id'] = true;
@@ -406,7 +422,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$metadata->mapManyToOne($mapping);
} elseif ($manyToManyAttribute !== null) {
$joinTable = [];
$joinTableAttribute = $this->reader->getPropertyAnnotation($property, Mapping\JoinTable::class);
$joinTableAttribute = $this->reader->getPropertyAttribute($property, Mapping\JoinTable::class);
if ($joinTableAttribute !== null) {
$joinTable = [
@@ -419,11 +435,11 @@ class AttributeDriver extends CompatibilityAnnotationDriver
}
}
foreach ($this->reader->getPropertyAnnotationCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($this->reader->getPropertyAnnotationCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\InverseJoinColumn::class) as $joinColumn) {
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
@@ -436,7 +452,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
$mapping['orphanRemoval'] = $manyToManyAttribute->orphanRemoval;
$mapping['fetch'] = $this->getFetchMode($className, $manyToManyAttribute->fetch);
$orderByAttribute = $this->reader->getPropertyAnnotation($property, Mapping\OrderBy::class);
$orderByAttribute = $this->reader->getPropertyAttribute($property, Mapping\OrderBy::class);
if ($orderByAttribute !== null) {
$mapping['orderBy'] = $orderByAttribute->value;
@@ -509,7 +525,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
}
}
// Evaluate AttributeOverrides annotation
// Evaluate AttributeOverrides attribute
if (isset($classAttributes[Mapping\AttributeOverrides::class])) {
$attributeOverridesAnnot = $classAttributes[Mapping\AttributeOverrides::class];
@@ -520,7 +536,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
}
}
// Evaluate EntityListeners annotation
// Evaluate EntityListeners attribute
if (isset($classAttributes[Mapping\EntityListeners::class])) {
$entityListenersAttribute = $classAttributes[Mapping\EntityListeners::class];
@@ -552,7 +568,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
}
}
// Evaluate @HasLifecycleCallbacks annotation
// Evaluate #[HasLifecycleCallbacks] attribute
if (isset($classAttributes[Mapping\HasLifecycleCallbacks::class])) {
foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
assert($method instanceof ReflectionMethod);
@@ -566,8 +582,8 @@ class AttributeDriver extends CompatibilityAnnotationDriver
/**
* Attempts to resolve the fetch mode.
*
* @param string $className The class name.
* @param string $fetchMode The fetch mode.
* @param class-string $className The class name.
* @param string $fetchMode The fetch mode.
*
* @return ClassMetadata::FETCH_* The fetch mode as defined in ClassMetadata.
*
@@ -604,7 +620,7 @@ class AttributeDriver extends CompatibilityAnnotationDriver
private function getMethodCallbacks(ReflectionMethod $method): array
{
$callbacks = [];
$attributes = $this->reader->getMethodAnnotations($method);
$attributes = $this->reader->getMethodAttributes($method);
foreach ($attributes as $attribute) {
if ($attribute instanceof Mapping\PrePersist) {

View File

@@ -28,7 +28,7 @@ final class AttributeReader
*
* @template T of Annotation
*/
public function getClassAnnotations(ReflectionClass $class): array
public function getClassAttributes(ReflectionClass $class): array
{
return $this->convertToAttributeInstances($class->getAttributes());
}
@@ -38,7 +38,7 @@ final class AttributeReader
*
* @template T of Annotation
*/
public function getMethodAnnotations(ReflectionMethod $method): array
public function getMethodAttributes(ReflectionMethod $method): array
{
return $this->convertToAttributeInstances($method->getAttributes());
}
@@ -48,50 +48,50 @@ final class AttributeReader
*
* @template T of Annotation
*/
public function getPropertyAnnotations(ReflectionProperty $property): array
public function getPropertyAttributes(ReflectionProperty $property): array
{
return $this->convertToAttributeInstances($property->getAttributes());
}
/**
* @param class-string<T> $annotationName The name of the annotation.
* @param class-string<T> $attributeName The name of the annotation.
*
* @return T|null
*
* @template T of Annotation
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
public function getPropertyAttribute(ReflectionProperty $property, $attributeName)
{
if ($this->isRepeatable($annotationName)) {
if ($this->isRepeatable($attributeName)) {
throw new LogicException(sprintf(
'The attribute "%s" is repeatable. Call getPropertyAnnotationCollection() instead.',
$annotationName
'The attribute "%s" is repeatable. Call getPropertyAttributeCollection() instead.',
$attributeName
));
}
return $this->getPropertyAnnotations($property)[$annotationName]
?? ($this->isRepeatable($annotationName) ? new RepeatableAttributeCollection() : null);
return $this->getPropertyAttributes($property)[$attributeName]
?? ($this->isRepeatable($attributeName) ? new RepeatableAttributeCollection() : null);
}
/**
* @param class-string<T> $annotationName The name of the annotation.
* @param class-string<T> $attributeName The name of the annotation.
*
* @return RepeatableAttributeCollection<T>
*
* @template T of Annotation
*/
public function getPropertyAnnotationCollection(
public function getPropertyAttributeCollection(
ReflectionProperty $property,
string $annotationName
string $attributeName
): RepeatableAttributeCollection {
if (! $this->isRepeatable($annotationName)) {
if (! $this->isRepeatable($attributeName)) {
throw new LogicException(sprintf(
'The attribute "%s" is not repeatable. Call getPropertyAnnotation() instead.',
$annotationName
'The attribute "%s" is not repeatable. Call getPropertyAttribute() instead.',
$attributeName
));
}
return $this->getPropertyAnnotations($property)[$annotationName] ?? new RepeatableAttributeCollection();
return $this->getPropertyAttributes($property)[$attributeName] ?? new RepeatableAttributeCollection();
}
/**
@@ -108,7 +108,7 @@ final class AttributeReader
foreach ($attributes as $attribute) {
$attributeName = $attribute->getName();
assert(is_string($attributeName));
// Make sure we only get Doctrine Annotations
// Make sure we only get Doctrine Attributes
if (! is_subclass_of($attributeName, Annotation::class)) {
continue;
}

View File

@@ -66,7 +66,7 @@ class DatabaseDriver implements MappingDriver
/** @var array<string,Table>|null */
private $tables = null;
/** @var mixed[] */
/** @var array<class-string, string> */
private $classToTableNames = [];
/** @psalm-var array<string, Table> */
@@ -304,14 +304,15 @@ class DatabaseDriver implements MappingDriver
$allForeignKeyColumns = array_merge($allForeignKeyColumns, $foreignKey->getLocalColumns());
}
if (! $table->hasPrimaryKey()) {
$primaryKey = $table->getPrimaryKey();
if ($primaryKey === null) {
throw new MappingException(
'Table ' . $tableName . ' has no primary key. Doctrine does not ' .
"support reverse engineering from tables that don't have a primary key."
);
}
$pkColumns = $table->getPrimaryKey()->getColumns();
$pkColumns = $primaryKey->getColumns();
sort($pkColumns);
sort($allForeignKeyColumns);

View File

@@ -1,54 +0,0 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/../Annotation.php';
require_once __DIR__ . '/../Entity.php';
require_once __DIR__ . '/../Embeddable.php';
require_once __DIR__ . '/../Embedded.php';
require_once __DIR__ . '/../MappedSuperclass.php';
require_once __DIR__ . '/../InheritanceType.php';
require_once __DIR__ . '/../DiscriminatorColumn.php';
require_once __DIR__ . '/../DiscriminatorMap.php';
require_once __DIR__ . '/../Id.php';
require_once __DIR__ . '/../GeneratedValue.php';
require_once __DIR__ . '/../Version.php';
require_once __DIR__ . '/../JoinColumn.php';
require_once __DIR__ . '/../JoinColumns.php';
require_once __DIR__ . '/../Column.php';
require_once __DIR__ . '/../OneToOne.php';
require_once __DIR__ . '/../OneToMany.php';
require_once __DIR__ . '/../ManyToOne.php';
require_once __DIR__ . '/../ManyToMany.php';
require_once __DIR__ . '/../Table.php';
require_once __DIR__ . '/../UniqueConstraint.php';
require_once __DIR__ . '/../Index.php';
require_once __DIR__ . '/../JoinTable.php';
require_once __DIR__ . '/../SequenceGenerator.php';
require_once __DIR__ . '/../CustomIdGenerator.php';
require_once __DIR__ . '/../ChangeTrackingPolicy.php';
require_once __DIR__ . '/../OrderBy.php';
require_once __DIR__ . '/../NamedQueries.php';
require_once __DIR__ . '/../NamedQuery.php';
require_once __DIR__ . '/../HasLifecycleCallbacks.php';
require_once __DIR__ . '/../PrePersist.php';
require_once __DIR__ . '/../PostPersist.php';
require_once __DIR__ . '/../PreUpdate.php';
require_once __DIR__ . '/../PostUpdate.php';
require_once __DIR__ . '/../PreRemove.php';
require_once __DIR__ . '/../PostRemove.php';
require_once __DIR__ . '/../PostLoad.php';
require_once __DIR__ . '/../PreFlush.php';
require_once __DIR__ . '/../FieldResult.php';
require_once __DIR__ . '/../ColumnResult.php';
require_once __DIR__ . '/../EntityResult.php';
require_once __DIR__ . '/../NamedNativeQuery.php';
require_once __DIR__ . '/../NamedNativeQueries.php';
require_once __DIR__ . '/../SqlResultSetMapping.php';
require_once __DIR__ . '/../SqlResultSetMappings.php';
require_once __DIR__ . '/../AssociationOverride.php';
require_once __DIR__ . '/../AssociationOverrides.php';
require_once __DIR__ . '/../AttributeOverride.php';
require_once __DIR__ . '/../AttributeOverrides.php';
require_once __DIR__ . '/../EntityListeners.php';
require_once __DIR__ . '/../Cache.php';

View File

@@ -19,6 +19,7 @@ class SimplifiedXmlDriver extends XmlDriver
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
{
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
parent::__construct($locator, $fileExtension);
}
}

View File

@@ -21,6 +21,7 @@ class SimplifiedYamlDriver extends YamlDriver
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
{
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
parent::__construct($locator, $fileExtension);
}
}

View File

@@ -210,6 +210,7 @@ class XmlDriver extends FileDriver
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255,
'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null,
'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null,
]
);
} else {
@@ -966,7 +967,7 @@ class XmlDriver extends FileDriver
foreach ($cascadeElement->children() as $action) {
// According to the JPA specifications, XML uses "cascade-persist"
// instead of "persist". Here, both variations
// are supported because both YAML and Annotation use "persist"
// are supported because YAML, Annotation and Attribute use "persist"
// and we want to make sure that this driver doesn't need to know
// anything about the supported cascading actions
$cascades[] = str_replace('cascade-', '', $action->getName());

View File

@@ -201,6 +201,7 @@ class YamlDriver extends FileDriver
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255,
'columnDefinition' => isset($discrColumn['columnDefinition']) ? (string) $discrColumn['columnDefinition'] : null,
'enumType' => isset($discrColumn['enumType']) ? (string) $discrColumn['enumType'] : null,
]
);
} else {

View File

@@ -11,6 +11,6 @@ use Attribute;
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class Embeddable implements Annotation
final class Embeddable implements MappingAttribute
{
}

View File

@@ -13,12 +13,18 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Embedded implements Annotation
final class Embedded implements MappingAttribute
{
/** @var string|null */
/**
* @var string|null
* @readonly
*/
public $class;
/** @var string|bool|null */
/**
* @var string|bool|null
* @readonly
*/
public $columnPrefix;
public function __construct(?string $class = null, $columnPrefix = null)

View File

@@ -15,15 +15,19 @@ use Doctrine\ORM\EntityRepository;
* @template T of object
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class Entity implements Annotation
final class Entity implements MappingAttribute
{
/**
* @var string|null
* @psalm-var class-string<EntityRepository<T>>|null
* @readonly
*/
public $repositoryClass;
/** @var bool */
/**
* @var bool
* @readonly
*/
public $readOnly = false;
/** @psalm-param class-string<EntityRepository<T>>|null $repositoryClass */

View File

@@ -8,20 +8,21 @@ 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.
* The EntityListeners attribute specifies the callback listener classes to be used for an entity or mapped superclass.
* The EntityListeners attribute may be applied to an entity class or mapped superclass.
*
* @Annotation
* @NamedArgumentConstructor()
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class EntityListeners implements Annotation
final class EntityListeners implements MappingAttribute
{
/**
* Specifies the names of the entity listeners.
*
* @var array<string>
* @readonly
*/
public $value = [];

View File

@@ -13,7 +13,7 @@ namespace Doctrine\ORM\Mapping;
* @Annotation
* @Target("ANNOTATION")
*/
final class EntityResult implements Annotation
final class EntityResult implements MappingAttribute
{
/**
* The class of the result.

View File

@@ -10,7 +10,7 @@ namespace Doctrine\ORM\Mapping;
* @Annotation
* @Target("ANNOTATION")
*/
final class FieldResult implements Annotation
final class FieldResult implements MappingAttribute
{
/**
* Name of the column in the SELECT clause.

View File

@@ -13,16 +13,19 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class GeneratedValue implements Annotation
final class GeneratedValue implements MappingAttribute
{
/**
* The type of Id generator.
* The type of ID generator.
*
* @var string
* @psalm-var 'AUTO'|'SEQUENCE'|'IDENTITY'|'NONE'|'CUSTOM'
* @readonly
* @Enum({"AUTO", "SEQUENCE", "TABLE", "IDENTITY", "NONE", "UUID", "CUSTOM"})
*/
public $strategy = 'AUTO';
/** @psalm-param 'AUTO'|'SEQUENCE'|'IDENTITY'|'NONE'|'CUSTOM' $strategy */
public function __construct(string $strategy = 'AUTO')
{
$this->strategy = $strategy;

View File

@@ -11,6 +11,6 @@ use Attribute;
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class HasLifecycleCallbacks implements Annotation
final class HasLifecycleCallbacks implements MappingAttribute
{
}

View File

@@ -11,6 +11,6 @@ use Attribute;
* @Target("PROPERTY")
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
final class Id implements Annotation
final class Id implements MappingAttribute
{
}

View File

@@ -13,21 +13,36 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("ANNOTATION")
*/
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class Index implements Annotation
final class Index implements MappingAttribute
{
/** @var string|null */
/**
* @var string|null
* @readonly
*/
public $name;
/** @var array<string>|null */
/**
* @var array<string>|null
* @readonly
*/
public $columns;
/** @var array<string>|null */
/**
* @var array<string>|null
* @readonly
*/
public $fields;
/** @var array<string>|null */
/**
* @var array<string>|null
* @readonly
*/
public $flags;
/** @var array<string,mixed>|null */
/**
* @var array<string,mixed>|null
* @readonly
*/
public $options;
/**

View File

@@ -13,16 +13,19 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target("CLASS")
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class InheritanceType implements Annotation
final class InheritanceType implements MappingAttribute
{
/**
* The inheritance type used by the class and its subclasses.
*
* @var string
* @psalm-var 'NONE'|'JOINED'|'SINGLE_TABLE'|'TABLE_PER_CLASS'
* @readonly
* @Enum({"NONE", "JOINED", "SINGLE_TABLE", "TABLE_PER_CLASS"})
*/
public $value;
/** @psalm-param 'NONE'|'JOINED'|'SINGLE_TABLE'|'TABLE_PER_CLASS' $value */
public function __construct(string $value)
{
$this->value = $value;

View File

@@ -8,56 +8,7 @@ namespace Doctrine\ORM\Mapping;
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class InverseJoinColumn implements Annotation
final class InverseJoinColumn implements MappingAttribute
{
/** @var string|null */
public $name;
/** @var string */
public $referencedColumnName = 'id';
/** @var bool */
public $unique = false;
/** @var bool */
public $nullable = true;
/** @var mixed */
public $onDelete;
/** @var string|null */
public $columnDefinition;
/**
* Field name used in non-object hydration (array/scalar).
*
* @var string|null
*/
public $fieldName;
/** @var array<string, mixed> */
public $options = [];
/**
* @param array<string, mixed> $options
*/
public function __construct(
?string $name = null,
string $referencedColumnName = 'id',
bool $unique = false,
bool $nullable = true,
$onDelete = null,
?string $columnDefinition = null,
?string $fieldName = null,
array $options = []
) {
$this->name = $name;
$this->referencedColumnName = $referencedColumnName;
$this->unique = $unique;
$this->nullable = $nullable;
$this->onDelete = $onDelete;
$this->columnDefinition = $columnDefinition;
$this->fieldName = $fieldName;
$this->options = $options;
}
use JoinColumnProperties;
}

View File

@@ -13,54 +13,7 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
* @Target({"PROPERTY","ANNOTATION"})
*/
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
final class JoinColumn implements Annotation
final class JoinColumn implements MappingAttribute
{
/** @var string|null */
public $name;
/** @var string */
public $referencedColumnName = 'id';
/** @var bool */
public $unique = false;
/** @var bool */
public $nullable = true;
/** @var mixed */
public $onDelete;
/** @var string|null */
public $columnDefinition;
/**
* Field name used in non-object hydration (array/scalar).
*
* @var string|null
*/
public $fieldName;
/** @var array<string, mixed> */
public $options = [];
/** @param array<string, mixed> $options */
public function __construct(
?string $name = null,
string $referencedColumnName = 'id',
bool $unique = false,
bool $nullable = true,
$onDelete = null,
?string $columnDefinition = null,
?string $fieldName = null,
array $options = []
) {
$this->name = $name;
$this->referencedColumnName = $referencedColumnName;
$this->unique = $unique;
$this->nullable = $nullable;
$this->onDelete = $onDelete;
$this->columnDefinition = $columnDefinition;
$this->fieldName = $fieldName;
$this->options = $options;
}
use JoinColumnProperties;
}

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