Compare commits

...

194 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
a5a6cc6630 Remove fragile assertions (#10239)
RuntimePublicReflectionProperty has been deprecated in favor of
RuntimeReflectionProperty.

Let us use a more robust assertion.
2022-11-20 19:53:31 +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
fc3201bded Merge pull request #10229 from greg0ire/fix-phpdoc-exception
Widen parameter type
2022-11-14 22:15:29 +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
3178b4ec4f Widen parameter type
This exception is used in two places, one where $generatedMode is
clearly a string, and another where typing it as a string will cause a
type error, because $generatedMode is supposed to be an int there.
2022-11-13 19:13:40 +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
465c02fe68 Reverse-engineer actual type from code (#10221)
The code contains tests for is_array(), is object(), and an else clause.
The type is wrong, and the variable name misleading.
2022-11-11 16:29:36 +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
Alexander M. Turek
7e45ad935c Psalm 4.30.0, PHPStan 1.9.2 (#10213) 2022-11-10 22:29:50 +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
Alexander M. Turek
0b14c01b93 PHPStan 1.9.0 (#10200) 2022-11-03 21:04:02 +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
552d98d554 Bump CI to PHP 8.2 and latest database versions (#10180) 2022-10-26 15:12:52 +02:00
Alexander M. Turek
8f605c652a Remove reference to deprecated DriverChain from docs (#10179) 2022-10-26 15:09:12 +02:00
Alexander M. Turek
1edfa91714 Merge 2.13.x into 2.14.x (#10181) 2022-10-26 11:53:28 +02:00
Alexander M. Turek
75e42abfdf PHPStan 1.8.11 (#10182) 2022-10-26 11:36:22 +02:00
Romain Monteil
3133bf06c2 Add isMemberOf and isInstanceOf to Expr helper list (#10104)
* Add isMemberOf and isInstanceOf to Expr helper list

* Apply suggestions from code review

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-10-26 11:04:17 +02:00
Grégoire Paris
7cf4074d3a Migrate more references to annotations (#10176) 2022-10-26 10:42:42 +02:00
Jonny Eom
bb1deba510 Fix grammer in working-with-objects (#10120) 2022-10-26 10:39:05 +02:00
HypeMC
c828a3814b Automap events in AttachEntityListenersListener (#10122) 2022-10-26 10:36:06 +02:00
Grégoire Paris
a5553a0786 Adapt use statements to the code (#10174) 2022-10-25 23:25:36 +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
Alexander M. Turek
0b9c949590 Fix build with DBAL 3.5 (#10163) 2022-10-23 01:11:03 +02:00
Grégoire Paris
3ee7d96179 Adjust comments (#10160) 2022-10-22 15:34:10 +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
Grégoire Paris
9f926f04ba Merge pull request #10157 from greg0ire/followup-migrate-docs-to-attributes
Migrate more documentation towards attributes
2022-10-21 21:25:08 +02:00
Grégoire Paris
4e445feb6c Migrate more documentation towards attributes
The previous rework missed some details / left the documentation in an
inconsistent state. Searching for "annotation" case insensitively allows
to find discrepancies and forgotten things.
2022-10-21 12:31:10 +02:00
Grégoire Paris
d79e61f8d9 Remove wrong sentence 2022-10-21 12:31:10 +02:00
Simon Podlipsky
06eafd82ac Do not export phpstan stuff (#10154) 2022-10-19 13:36:12 +02:00
Alexander M. Turek
bac784c9ba Merge 2.13.x up into 2.14.x (#10149) 2022-10-17 23:19:27 +02:00
Grégoire Paris
5f4b76b88f Merge pull request #10126 from greg0ire/migrate-docs-to-attributes
Modernize documentation code
2022-10-17 23:11:33 +02:00
Grégoire Paris
794777b21f Modernize documentation code
- migrate to attributes;
- add helpful phpdoc annotations;
- use typed properties;
- add type declarations.

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-10-17 22:56:34 +02:00
Alexander M. Turek
119c378a3a Add CI jobs for SQLite3 driver (#10141) 2022-10-17 22:48:07 +02:00
Grégoire Paris
90bc6dc300 Merge pull request #10136 from greg0ire/trigger-sa-workflows-less
Stop triggering static analysis workflows on tests
2022-10-17 22:45:22 +02:00
Alexander M. Turek
1ad936a448 Fix type doc blocks in annotation classes (#10145) 2022-10-17 22:20:08 +02:00
Ondřej Mirtes
e93e8e0bdf Fix FieldMapping for generated key (#10144) 2022-10-17 15:37:06 +02:00
Alexander M. Turek
7e75807918 Merge 2.13.x into 2.14.x (#10139) 2022-10-17 10:14:22 +02:00
Grégoire Paris
bdf067b58a Stop triggering static analysis workflows on tests
In my opinion it is not great that we do not run static analysis tools on
tests, but since we do not, let us stop triggering extra jobs for no reason.
2022-10-15 23:59:57 +02:00
Grégoire Paris
f08b67f0cc Merge pull request #10135 from greg0ire/remove-extra-file
Remove file suffixed with singular
2022-10-15 11:59:05 +02:00
Grégoire Paris
5f8504b5cf Remove file suffixed with singular
It was wrongly preserved after a conflict that followed a rename.
2022-10-15 11:57:22 +02:00
Grégoire Paris
16e25656d9 Merge pull request #10134 from greg0ire/lighter-builds-💚🌍
Run only relevant workflows
2022-10-15 11:48:21 +02:00
Grégoire Paris
cacdc56b6e Run only relevant workflows
The downside of this is that we will have to tweak the settings so that
no job is required anymore.
The upside is that builds should be faster, and less resource-intensive.
2022-10-15 11:44:34 +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
Alexander M. Turek
13d1c7b286 Psalm 4.29 (#10128) 2022-10-13 10:03:12 +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
Pierre du Plessis
06c77cebb5 Make code blocks consistent (#10103) 2022-10-10 23:31:20 +02:00
HypeMC
1ed0057276 Fix change set computation with enums (#10074)
* add failing testcase test enum Change sets

* Fix change set computation with enums

Co-authored-by: Olda Salek <mzk@mozektevidi.net>
2022-10-10 11:59:04 +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
Alexander M. Turek
5b6f3cd598 PHPStan 1.8.8, Psalm 4.28.0 (#10115) 2022-10-09 23:31:52 +02:00
Grégoire Paris
38271d4aeb Merge pull request #10110 from davidromani/2.13.x
Fix deprecated trigger help comment
2022-10-08 22:42:56 +02:00
David Romaní
84213b9f05 fix deprecated trigger help comment 2022-10-07 18:03:42 +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
Grégoire Paris
e750360bd5 Merge pull request #10101 from greg0ire/followup-9488
Assert that serialization leaves PersistentCollection usable
2022-10-07 08:37:17 +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
e8ac1169ad Bump CI workflows (#10106) 2022-10-06 16:57:00 +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
dd8c7003b8 Assert that serialization leaves PersistentCollection usable
That feature is no longer covered since support for merging entities in
the entity manager was removed.
2022-10-06 10:40:06 +02:00
Grégoire Paris
9bec416bb0 Merge pull request #10100 from greg0ire/forward-compat-attributes
Forward compat attributes
2022-10-05 22:44:49 +02:00
Grégoire Paris
021722acc7 Remove ignored annotation
Making it a separate, valid annotation breaks the tests
2022-10-05 22:15:04 +02:00
Grégoire Paris
5a528bef5d Use short class name
It is more friendly with conversion to attributes
2022-10-05 22:00:44 +02:00
Grégoire Paris
5fbcb18334 Spell cities properly 2022-10-05 21:55:24 +02:00
Grégoire Paris
8280f41fa5 Merge pull request #10099 from greg0ire/fix-attribute-docs
Fix attribute reference
2022-10-05 21:48:46 +02:00
Grégoire Paris
d95d8d0a19 Fix broken links 2022-10-05 21:40:09 +02:00
Grégoire Paris
d36004f825 Remove trailing whitespace 2022-10-05 21:39:31 +02:00
Grégoire Paris
18366db578 Remove reference to JoinColumns
While that class can be an annotation, it cannot be an attribute because
it is not necessary when using attributes.

See https://github.com/doctrine/orm/issues/9986
2022-10-05 21:38:38 +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
1b5bef3d4d Merge pull request #10094 from doctrine/fix_doc_toc 2022-10-04 15:55:16 +02:00
Christophe Coevoet
880b622fe6 Fix heading levels for the embeddable tutorial 2022-10-04 14:13:44 +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
cbf141f7ed Add missing variable name in param phpdoc and switch to psalm-assert (#10092)
* Add missing variable name in param phpdoc

* Use psalm-assert instead of psalm-param

The parameter can in fact be any string, but if an exception is not
thrown, then it was a valid value.
2022-10-03 21:17:21 +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
Grégoire Paris
536bb4f678 Merge pull request #10090 from greg0ire/update-psalm-baseline
Update Psalm baseline
2022-10-02 16:23:14 +02:00
Grégoire Paris
3f2f42277e Update Psalm baseline 2022-10-02 16:02:57 +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
757d950128 Merge pull request #10080 from greg0ire/add-missing-template-annotation
Address changes from doctrine/collections 1.8.0
2022-09-29 22:58:03 +02:00
Grégoire Paris
f07840b10c Address changes from doctrine/collections 1.8.0
Not quite sure why this annotation is not inherited.
2022-09-29 17:07:57 +02:00
Grégoire Paris
6e8afeeb60 Merge pull request #10072 from greg0ire/fix-return-type 2022-09-27 13:50:49 +02:00
Grégoire Paris
9130b27aca Remove return type override
In the parent class, all 3 methods are documented to return mixed.
This was not forward compatible with the addition of return types.
2022-09-27 13:41:09 +02:00
Grégoire Paris
2e7c2bb385 Merge pull request #10058 from HypeMC/enum-with-query-builder-fix
Fix using enums with the QueryBuilder
2022-09-23 22:08:15 +02:00
HypeMC
d69a0fa2cf Fix using enums with the QueryBuilder 2022-09-22 22:39:52 +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
501 changed files with 6605 additions and 3111 deletions

3
.gitattributes vendored
View File

@@ -16,5 +16,8 @@ phpcs.xml.dist export-ignore
phpbench.json export-ignore
phpstan.neon export-ignore
phpstan-baseline.neon export-ignore
phpstan-dbal2.neon export-ignore
phpstan-params.neon export-ignore
phpstan-persistence2.neon export-ignore
psalm.xml export-ignore
psalm-baseline.xml export-ignore

View File

@@ -1,13 +0,0 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
push:
branches:
- "*.x"
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@2.0.0"

27
.github/workflows/coding-standards.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: "Coding Standards"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- lib/**
- phpcs.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/coding-standards.yml
- bin/**
- composer.*
- lib/**
- phpcs.xml.dist
- tests/**
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@3.0.0"

View File

@@ -4,9 +4,23 @@ on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- lib/**
- phpunit.xml.dist
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/continuous-integration.yml
- ci/**
- composer.*
- lib/**
- phpunit.xml.dist
- tests/**
env:
fail-fast: true
@@ -24,19 +38,31 @@ jobs:
- "7.4"
- "8.0"
- "8.1"
- "8.2"
dbal-version:
- "default"
extension:
- "pdo_sqlite"
proxy:
- "common"
include:
- php-version: "8.0"
dbal-version: "2.13"
- php-version: "8.1"
dbal-version: "3@dev"
extension: "pdo_sqlite"
- php-version: "8.2"
dbal-version: "3@dev"
extension: "pdo_sqlite"
- 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"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -44,33 +70,38 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "apcu, pdo, pdo_sqlite"
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@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage-no-cache.xml"
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/sqlite.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
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@v2"
uses: "actions/upload-artifact@v3"
with:
name: "phpunit-sqlite-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
@@ -82,19 +113,19 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
postgres-version:
- "9.6"
- "14"
- "15"
include:
- php-version: "8.0"
dbal-version: "2.13"
postgres-version: "14"
- php-version: "8.2"
dbal-version: "3@dev"
postgres-version: "14"
dbal-version: "default"
postgres-version: "9.6"
services:
postgres:
@@ -110,7 +141,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -119,14 +150,17 @@ 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@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -134,7 +168,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -148,11 +182,12 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
mariadb-version:
- "10.6"
- "10.9"
extension:
- "mysqli"
- "pdo_mysql"
@@ -161,14 +196,6 @@ jobs:
dbal-version: "2.13"
mariadb-version: "10.6"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mariadb-version: "10.6"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mariadb-version: "10.6"
extension: "mysqli"
services:
mariadb:
@@ -185,7 +212,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -193,16 +220,19 @@ 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"
uses: "ramsey/composer-install@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -210,7 +240,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -224,9 +254,10 @@ jobs:
strategy:
matrix:
php-version:
- "8.1"
- "8.2"
dbal-version:
- "default"
- "3@dev"
mysql-version:
- "5.7"
- "8.0"
@@ -238,14 +269,6 @@ jobs:
dbal-version: "2.13"
mysql-version: "8.0"
extension: "pdo_mysql"
- php-version: "8.2"
dbal-version: "3@dev"
mysql-version: "8.0"
extension: "mysqli"
- php-version: "8.2"
dbal-version: "3@dev"
mysql-version: "8.0"
extension: "pdo_mysql"
services:
mysql:
@@ -261,7 +284,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -270,15 +293,18 @@ 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@v1"
uses: "ramsey/composer-install@v2"
with:
composer-options: "--ignore-platform-req=php+"
@@ -293,7 +319,7 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v2"
uses: "actions/upload-artifact@v3"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
@@ -313,7 +339,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -321,15 +347,15 @@ 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@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "${{ matrix.deps }}"
- name: "Run PHPUnit"
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml"
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_sqlite.xml"
upload_coverage:
@@ -343,16 +369,16 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v2"
uses: "actions/download-artifact@v3"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v1"
uses: "codecov/codecov-action@v3"
with:
directory: reports

View File

@@ -5,9 +5,21 @@ on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- lib/**
- phpbench.json
- tests/**
push:
branches:
- "*.x"
paths:
- .github/workflows/phpbench.yml
- composer.*
- lib/**
- phpbench.json
- tests/**
env:
fail-fast: true
@@ -24,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
with:
fetch-depth: 2
@@ -33,10 +45,10 @@ 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@v2"
uses: "actions/cache@v3"
with:
path: "~/.composer/cache"
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"

View File

@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@2.0.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,13 +1,26 @@
name: "Static Analysis"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- lib/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
push:
branches:
- "*.x"
paths:
- .github/workflows/static-analysis.yml
- composer.*
- lib/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
jobs:
static-analysis-phpstan:
@@ -17,40 +30,38 @@ 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:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
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@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"
@@ -72,22 +83,25 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version:
- "8.1"
steps:
- name: "Checkout code"
uses: "actions/checkout@v2"
uses: "actions/checkout@v3"
- name: "Install PHP"
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@v1"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"

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

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
<!-- use an in-memory sqlite database -->
<var name="db_driver" value="sqlite3"/>
<var name="db_memory" value="true"/>
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">../../../lib/Doctrine</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>

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.8.5",
"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.27.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

@@ -32,59 +32,39 @@ The entity class:
namespace Geo\Entity;
/**
* @Entity
*/
use Geo\ValueObject\Point;
#[Entity]
class Location
{
/**
* @Column(type="point")
*
* @var \Geo\ValueObject\Point
*/
private $point;
#[Column(type: 'point')]
private Point $point;
/**
* @Column(type="string")
*
* @var string
*/
private $address;
#[Column]
private string $address;
/**
* @param \Geo\ValueObject\Point $point
*/
public function setPoint(\Geo\ValueObject\Point $point)
public function setPoint(Point $point): void
{
$this->point = $point;
}
/**
* @return \Geo\ValueObject\Point
*/
public function getPoint()
public function getPoint(): Point
{
return $this->point;
}
/**
* @param string $address
*/
public function setAddress($address)
public function setAddress(string $address): void
{
$this->address = $address;
}
/**
* @return string
*/
public function getAddress()
public function getAddress(): string
{
return $this->address;
}
}
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
We use the custom type ``point`` in the ``#[Column]`` attribute of the
``$point`` field. We will create this custom mapping type in the next chapter.
The point class:
@@ -97,29 +77,18 @@ The point class:
class Point
{
/**
* @param float $latitude
* @param float $longitude
*/
public function __construct($latitude, $longitude)
{
$this->latitude = $latitude;
$this->longitude = $longitude;
public function __construct(
private float $latitude,
private float $longitude,
) {
}
/**
* @return float
*/
public function getLatitude()
public function getLatitude(): float
{
return $this->latitude;
}
/**
* @return float
*/
public function getLongitude()
public function getLongitude(): float
{
return $this->longitude;
}
@@ -227,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

@@ -23,48 +23,32 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
namespace Test;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
"cd" = "Test\Decorator\ConcreteDecorator"})
*/
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['cc' => Component\ConcreteComponent::class,
'cd' => Decorator\ConcreteDecorator::class])]
abstract class Component
{
/**
* @Id @Column(type="integer")
* @GeneratedValue(strategy="AUTO")
*/
protected $id;
#[Id, Column]
#[GeneratedValue(strategy: 'AUTO')]
protected int|null $id = null;
/** @Column(type="string", nullable=true) */
#[Column(type: 'string', nullable: true)]
protected $name;
/**
* Get id
* @return integer $id
*/
public function getId()
public function getId(): int|null
{
return $this->id;
}
/**
* Set name
* @param string $name
*/
public function setName($name)
public function setName(string $name): void
{
$this->name = $name;
}
/**
* Get name
* @return string $name
*/
public function getName()
public function getName(): string
{
return $this->name;
}
@@ -86,7 +70,7 @@ purpose of keeping this example simple).
use Test\Component;
/** @Entity */
#[Entity]
class ConcreteComponent extends Component
{}
@@ -103,14 +87,11 @@ use a ``MappedSuperclass`` for this.
namespace Test;
/** @MappedSuperclass */
#[MappedSuperclass]
abstract class Decorator extends Component
{
/**
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
* @JoinColumn(name="decorates", referencedColumnName="id")
*/
#[OneToOne(targetEntity: Component::class, cascade: ['all'])]
#[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
protected $decorates;
/**
@@ -126,25 +107,19 @@ use a ``MappedSuperclass`` for this.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
public function getName(): string
{
return 'Decorated ' . $this->getDecorates()->getName();
}
/**
* the component being decorated
* @return Component
*/
protected function getDecorates()
/** the component being decorated */
protected function getDecorates(): Component
{
return $this->decorates;
}
/**
* sets the component being decorated
* @param Component $c
*/
protected function setDecorates(Component $c)
/** sets the component being decorated */
protected function setDecorates(Component $c): void
{
$this->decorates = $c;
}
@@ -187,27 +162,19 @@ of the getSpecial() method to its return value.
use Test\Decorator;
/** @Entity */
#[Entity]
class ConcreteDecorator extends Decorator
{
/** @Column(type="string", nullable=true) */
protected $special;
#[Column(type: 'string', nullable: true)]
protected string|null $special = null;
/**
* Set special
* @param string $special
*/
public function setSpecial($special)
public function setSpecial(string|null $special): void
{
$this->special = $special;
}
/**
* Get special
* @return string $special
*/
public function getSpecial()
public function getSpecial(): string|null
{
return $this->special;
}
@@ -216,7 +183,7 @@ of the getSpecial() method to its return value.
* (non-PHPdoc)
* @see Test.Component::getName()
*/
public function getName()
public function getName(): string
{
return '[' . $this->getSpecial()
. '] ' . parent::getName();
@@ -270,4 +237,3 @@ objects
echo $d->getName();
// prints: [Really] Decorated Test Component 2

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

@@ -29,15 +29,15 @@ implement the ``NotifyPropertyChanged`` interface from the
<?php
use Doctrine\Persistence\NotifyPropertyChanged;
use Doctrine\Persistence\PropertyChangedListener;
abstract class DomainObject implements NotifyPropertyChanged
{
private $listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener) {
$this->listeners[] = $listener;
}
/** Notifies listeners of a change. */
protected function onPropertyChanged($propName, $oldValue, $newValue) {
if ($this->listeners) {
@@ -55,12 +55,12 @@ listeners:
.. code-block:: php
<?php
// Mapping not shown, either in annotations, xml or yaml as usual
// Mapping not shown, either in attributes, annotations, xml or yaml as usual
class MyEntity extends DomainObject
{
private $data;
// ... other fields as usual
public function setData($data) {
if ($data != $this->data) { // check: is it actually modified?
$this->onPropertyChanged('data', $this->data, $data);
@@ -73,5 +73,3 @@ The check whether the new value is different from the old one is
not mandatory but recommended. That way you can avoid unnecessary
updates and also have full control over when you consider a
property changed.

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

@@ -154,8 +154,8 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine ORM)
*
* This is a doctrine field, so make sure that you use an @column annotation or setup your
* yaml or xml files correctly
* This is a doctrine field, so make sure that you use a
#[Column] attribute or setup your yaml or xml files correctly
* @var string
*/
protected $strategyClassName;
@@ -251,5 +251,3 @@ This might look like this:
In this example, even some variables are set - like a view object
or a specific configuration object.

View File

@@ -36,12 +36,12 @@ are allowed to:
public function assertCustomerAllowedBuying()
{
$orderLimit = $this->customer->getOrderLimit();
$amount = 0;
foreach ($this->orderLines as $line) {
$amount += $line->getAmount();
}
if ($amount > $orderLimit) {
throw new CustomerOrderLimitExceededException();
}
@@ -53,7 +53,21 @@ code, enforcing it at any time is important so that customers with
a unknown reputation don't owe your business too much money.
We can enforce this constraint in any of the metadata drivers.
First Annotations:
First Attributes:
.. code-block:: php
<?php
#[Entity]
#[HasLifecycleCallbacks]
class Order
{
#[PrePersist, PreUpdate]
public function assertCustomerAllowedBuying() {}
}
As Annotations:
.. code-block:: php
@@ -83,9 +97,6 @@ In XML Mappings:
</entity>
</doctrine-mapping>
YAML needs some little change yet, to allow multiple lifecycle
events for one method, this will happen before Beta 1 though.
Now validation is performed whenever you call
``EntityManager#persist($order)`` or when you call
``EntityManager#flush()`` and an order is about to be updated. Any
@@ -101,19 +112,17 @@ validation callbacks.
<?php
class Order
{
/**
* @PrePersist @PreUpdate
*/
#[PrePersist, PreUpdate]
public function validate()
{
if (!($this->plannedShipDate instanceof DateTime)) {
throw new ValidateException();
}
if ($this->plannedShipDate->format('U') < time()) {
throw new ValidateException();
}
if ($this->customer == null) {
throw new OrderRequiresCustomerException();
}

View File

@@ -15,13 +15,16 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
.. code-block:: php
<?php
/** @Entity */
use DateTime;
#[Entity]
class Article
{
/** @Column(type="datetime") */
private $updated;
#[Column(type='datetime')]
private DateTime $updated;
public function setUpdated()
public function setUpdated(): void
{
// will NOT be saved in the database
$this->updated->modify("now");
@@ -33,12 +36,14 @@ The way to go would be:
.. code-block:: php
<?php
use DateTime;
class Article
{
public function setUpdated()
public function setUpdated(): void
{
// WILL be saved in the database
$this->updated = new \DateTime("now");
$this->updated = new DateTime("now");
}
}
@@ -84,16 +89,14 @@ the UTC time at the time of the booking and the timezone the event happened in.
namespace DoctrineExtensions\DBAL\Types;
use DateTimeZone;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\ConversionException;
use Doctrine\DBAL\Types\DateTimeType;
class UTCDateTimeType extends DateTimeType
{
/**
* @var \DateTimeZone
*/
private static $utc;
private static DateTimeZone $utc;
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
@@ -126,10 +129,10 @@ the UTC time at the time of the booking and the timezone the event happened in.
return $converted;
}
private static function getUtc(): \DateTimeZone
private static function getUtc(): DateTimeZone
{
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
return self::$utc ??= new DateTimeZone('UTC');
}
}

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

@@ -12,6 +12,7 @@ steps of configuration.
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\ORM\ORMSetup;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
@@ -28,7 +29,7 @@ steps of configuration.
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
@@ -40,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
--------------------
@@ -113,29 +114,30 @@ 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 AnnotationDriver is
used in the examples. For information on the usage of the XmlDriver
or YamlDriver please refer to the dedicated chapters
``XML Mapping`` and ``YAML Mapping``.
Throughout the most part of this manual the AttributeDriver is
used in the examples. For information on the usage of the
AnnotationDriver, XmlDriver or YamlDriver please refer to the dedicated
chapters ``Annotation Reference``, ``XML Mapping`` and ``YAML Mapping``.
The annotation driver can be configured with a factory method on
the ``Doctrine\ORM\Configuration``:
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
.. code-block:: php
<?php
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = ORMSetup::createDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the annotation
The path information to the entities is required for the attribute
driver, because otherwise mass-operations on all entities through
the console could not work correctly. All of metadata drivers
accept either a single directory as a string or an array of
@@ -152,7 +154,7 @@ Metadata Cache (***RECOMMENDED***)
$config->getMetadataCache();
Gets or sets the cache adapter to use for caching metadata
information, that is, all the information you supply via
information, that is, all the information you supply via attributes,
annotations, xml or yaml, so that they do not need to be parsed and
loaded from scratch on every single request which is a waste of
resources. The cache implementation must implement the PSR-6
@@ -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
@@ -404,15 +405,15 @@ Multiple Metadata Sources
When using different components using Doctrine ORM you may end up
with them using two different metadata drivers, for example XML and
YAML. You can use the DriverChain Metadata implementations to
YAML. You can use the MappingDriverChain Metadata implementations to
aggregate these drivers based on namespaces:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Driver\DriverChain;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
$chain = new DriverChain();
$chain = new MappingDriverChain();
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');

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

@@ -37,7 +37,26 @@ A many-to-one association is the most common association between objects. Exampl
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
#[ManyToOne(targetEntity: Address::class)]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
private Address|null $address = null;
}
#[Entity]
class Address
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -49,7 +68,7 @@ A many-to-one association is the most common association between objects. Exampl
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
private $address;
private Address|null $address = null;
}
/** @Entity */
@@ -82,9 +101,11 @@ A many-to-one association is the most common association between objects. Exampl
.. note::
The above ``@JoinColumn`` is optional as it would default
The above ``#[JoinColumn]`` is optional as it would default
to ``address_id`` and ``id`` anyways. You can omit it and let it
use the defaults.
Likewise, inside the ``#[ManyToOne]`` attribute you can omit the
``targetEntity`` argument and it will default to ``Address``.
Generated MySQL Schema:
@@ -111,7 +132,29 @@ references one ``Shipment`` entity.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Product
{
// ...
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
// ...
}
#[Entity]
class Shipment
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -124,7 +167,7 @@ references one ``Shipment`` entity.
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
private Shipment|null $shipment = null;
// ...
}
@@ -156,7 +199,7 @@ references one ``Shipment`` entity.
name: shipment_id
referencedColumnName: id
Note that the @JoinColumn is not really necessary in this example,
Note that the ``#[JoinColumn]`` is not really necessary in this example,
as the defaults would be the same.
Generated MySQL Schema:
@@ -188,7 +231,35 @@ object.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Customer
{
// ...
/** One Customer has One Cart. */
#[OneToOne(targetEntity: Cart::class, mappedBy: 'customer')]
private Cart|null $cart = null;
// ...
}
#[Entity]
class Cart
{
// ...
/** One Cart has One Customer. */
#[OneToOne(targetEntity: Customer::class, inversedBy: 'cart')]
#[JoinColumn(name: 'customer_id', referencedColumnName: 'id')]
private Customer|null $customer = null;
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -200,7 +271,7 @@ object.
* One Customer has One Cart.
* @OneToOne(targetEntity="Cart", mappedBy="customer")
*/
private $cart;
private Cart|null $cart = null;
// ...
}
@@ -215,7 +286,7 @@ object.
* @OneToOne(targetEntity="Customer", inversedBy="cart")
* @JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
private Customer|null $customer = null;
// ...
}
@@ -281,17 +352,15 @@ below.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class Student
{
// ...
/**
* One Student has One Mentor.
* @OneToOne(targetEntity="Student")
* @JoinColumn(name="mentor_id", referencedColumnName="id")
*/
private $mentor;
/** One Student has One Mentor. */
#[OneToOne(targetEntity: Student::class)]
#[JoinColumn(name: 'mentor_id', referencedColumnName: 'id')]
private Student|null $mentor = null;
// ...
}
@@ -326,7 +395,40 @@ bidirectional many-to-one.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Product
{
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
*/
#[OneToMany(targetEntity: Feature::class, mappedBy: 'product')]
private Collection $features;
// ...
public function __construct() {
$this->features = new ArrayCollection();
}
}
#[Entity]
class Feature
{
// ...
/** Many features have one product. This is the owning side. */
#[ManyToOne(targetEntity: Product::class, inversedBy: 'features')]
#[JoinColumn(name: 'product_id', referencedColumnName: 'id')]
private Product|null $product = null;
// ...
}
.. code-block:: annotation
<?php
use Doctrine\Common\Collections\ArrayCollection;
@@ -337,9 +439,10 @@ bidirectional many-to-one.
// ...
/**
* One product has many features. This is the inverse side.
* @var Collection<int, Feature>
* @OneToMany(targetEntity="Feature", mappedBy="product")
*/
private $features;
private Collection $features;
// ...
public function __construct() {
@@ -356,7 +459,7 @@ bidirectional many-to-one.
* @ManyToOne(targetEntity="Product", inversedBy="features")
* @JoinColumn(name="product_id", referencedColumnName="id")
*/
private $product;
private Product|null $product = null;
// ...
}
@@ -421,7 +524,39 @@ The following example sets up such a unidirectional one-to-many association:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Phonenumbers.
* @var Collection<int, Phonenumber>
*/
#[JoinTable(name: 'users_phonenumbers')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)]
#[ManyToMany(targetEntity: 'Phonenumber')]
private Collection $phonenumbers;
public function __construct()
{
$this->phonenumbers = new ArrayCollection();
}
// ...
}
#[Entity]
class Phonenumber
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -436,8 +571,9 @@ The following example sets up such a unidirectional one-to-many association:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
* )
* @var Collection<int, Phonenumber>
*/
private $phonenumbers;
private Collection $phonenumbers;
public function __construct()
{
@@ -523,7 +659,32 @@ database perspective is known as an adjacency list approach.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class Category
{
// ...
/**
* One Category has Many Categories.
* @var Collection<int, Category>
*/
#[OneToMany(targetEntity: Category::class, mappedBy: 'parent')]
private Collection $children;
/** Many Categories have One Category. */
#[ManyToOne(targetEntity: Category::class, inversedBy: 'children')]
#[JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
private Category|null $parent = null;
// ...
public function __construct() {
$this->children = new ArrayCollection();
}
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -533,15 +694,16 @@ database perspective is known as an adjacency list approach.
/**
* One Category has Many Categories.
* @OneToMany(targetEntity="Category", mappedBy="parent")
* @var Collection<int, Category>
*/
private $children;
private Collection $children;
/**
* Many Categories have One Category.
* @ManyToOne(targetEntity="Category", inversedBy="children")
* @JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
private Category|null $parent = null;
// ...
public function __construct() {
@@ -594,7 +756,38 @@ entities:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
public function __construct() {
$this->groups = new ArrayCollection();
}
}
#[Entity]
class Group
{
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -609,8 +802,9 @@ entities:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
// ...
@@ -695,7 +889,48 @@ one is bidirectional.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[ManyToMany(targetEntity: Group::class, inversedBy: 'users')]
#[JoinTable(name: 'users_groups')]
private Collection $groups;
public function __construct() {
$this->groups = new ArrayCollection();
}
// ...
}
#[Entity]
class Group
{
// ...
/**
* Many Groups have Many Users.
* @var Collection<int, User>
*/
#[ManyToMany(targetEntity: User::class, mappedBy: 'groups')]
private Collection $users;
public function __construct() {
$this->users = new ArrayCollection();
}
// ...
}
.. code-block:: annotation
<?php
/** @Entity */
@@ -707,8 +942,9 @@ one is bidirectional.
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group", inversedBy="users")
* @JoinTable(name="users_groups")
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
@@ -724,8 +960,9 @@ one is bidirectional.
/**
* Many Groups have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="groups")
* @var Collection<int, User>
*/
private $users;
private Collection $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
@@ -806,9 +1043,9 @@ understandable:
<?php
class Article
{
private $tags;
private Collection $tags;
public function addTag(Tag $tag)
public function addTag(Tag $tag): void
{
$tag->addArticle($this); // synchronously updating inverse side
$this->tags[] = $tag;
@@ -817,9 +1054,9 @@ understandable:
class Tag
{
private $articles;
private Collection $articles;
public function addArticle(Article $article)
public function addArticle(Article $article): void
{
$this->articles[] = $article;
}
@@ -847,30 +1084,31 @@ field named ``$friendsWithMe`` and ``$myFriends``.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class User
{
// ...
/**
* Many Users have Many Users.
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
* @var Collection<int, User>
*/
private $friendsWithMe;
#[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')]
private Collection $friendsWithMe;
/**
* Many Users have many Users.
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* @JoinTable(name="friends",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
* @var Collection<int, User>
*/
private $myFriends;
#[JoinTable(name: 'friends')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')]
private Collection $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
$this->friendsWithMe = new ArrayCollection();
$this->myFriends = new ArrayCollection();
}
// ...
@@ -910,11 +1148,17 @@ As an example, consider this mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[OneToOne(targetEntity: Shipment::class)]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/** @OneToOne(targetEntity="Shipment") */
private $shipment;
private Shipment|null $shipment = null;
.. code-block:: xml
@@ -937,7 +1181,15 @@ mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
private Shipment|null $shipment = null;
.. code-block:: annotation
<?php
/**
@@ -945,7 +1197,7 @@ mapping:
* @OneToOne(targetEntity="Shipment")
* @JoinColumn(name="shipment_id", referencedColumnName="id")
*/
private $shipment;
private Shipment|null $shipment = null;
.. code-block:: xml
@@ -973,14 +1225,29 @@ similar defaults. As an example, consider this mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
/** @ManyToMany(targetEntity="Group") */
private $groups;
/** @var Collection<int, Group> */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @var Collection<int, Group>
*/
private Collection $groups;
// ...
}
@@ -1004,7 +1271,25 @@ This is essentially the same as the following, more verbose, mapping:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
/**
* Many Users have Many Groups.
* @var Collection<int, Group>
*/
#[JoinTable(name: 'User_Group')]
#[JoinColumn(name: 'User_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'Group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
// ...
}
.. code-block:: annotation
<?php
class User
@@ -1017,8 +1302,9 @@ This is essentially the same as the following, more verbose, mapping:
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
private $groups;
private Collection $groups;
// ...
}
@@ -1069,7 +1355,13 @@ attribute on ``JoinColumn`` will be inherited from PHP type. So that:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[OneToOne]
private Shipment $shipment;
.. code-block:: annotation
<?php
/** @OneToOne */
@@ -1094,7 +1386,7 @@ Is essentially the same as following:
.. configuration-block::
.. code-block:: php
.. code-block:: annotation
<?php
/**
@@ -1104,6 +1396,14 @@ Is essentially the same as following:
*/
private Shipment $shipment;
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
@@ -1163,22 +1463,19 @@ and ``@ManyToMany`` associations in the constructor of your entities:
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
#[Entity]
class User
{
/**
* Many Users have Many Groups.
* @var Collection
* @ManyToMany(targetEntity="Group")
*/
private $groups;
/** Many Users have Many Groups. */
#[ManyToMany(targetEntity: Group::class)]
private Collection $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
}
public function getGroups()
public function getGroups(): Collection
{
return $this->groups;
}

View File

@@ -10,8 +10,8 @@ annotation metadata supported since the first version 2.0.
Index
-----
- :ref:`#[AssociationOverride] <attrref_associationoverride]`
- :ref:`#[AttributeOverride] <attrref_attributeoverride]`
- :ref:`#[AssociationOverride] <attrref_associationoverride>`
- :ref:`#[AttributeOverride] <attrref_attributeoverride>`
- :ref:`#[Column] <attrref_column>`
- :ref:`#[Cache] <attrref_cache>`
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
@@ -27,7 +27,6 @@ Index
- :ref:`#[Id] <attrref_id>`
- :ref:`#[InheritanceType] <attrref_inheritancetype>`
- :ref:`#[JoinColumn] <attrref_joincolumn>`
- :ref:`#[JoinColumns] <attrref_joincolumns>`
- :ref:`#[JoinTable] <attrref_jointable>`
- :ref:`#[ManyToOne] <attrref_manytoone>`
- :ref:`#[ManyToMany] <attrref_manytomany>`
@@ -179,7 +178,7 @@ Optional parameters:
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
@@ -367,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,13 +45,15 @@ 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 docblock annotations, though
many examples also show the equivalent configuration in YAML and XML.
This manual will usually show mapping metadata via attributes, though
many examples also show the equivalent configuration in annotations,
YAML and XML.
.. note::
@@ -157,10 +159,10 @@ Property Mapping
The next step is mapping its properties to columns in the table.
To configure a property use the ``Column`` docblock annotation. The ``type``
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
to use for the field. If the type is not specified, ``string`` is used as the
default.
To configure a property use the ``Column`` attribute. The ``type``
argument specifies the :ref:`Doctrine Mapping Type
<reference-mapping-types>` to use for the field. If the type is not
specified, ``string`` is used as the default.
.. configuration-block::
@@ -287,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
@@ -360,12 +369,23 @@ Identifiers / Primary Keys
--------------------------
Every entity class must have an identifier/primary key. You can select
the field that serves as the identifier with the ``@Id``
annotation.
the field that serves as the identifier with the ``#[Id]`` attribute.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class Message
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue]
private int|null $id = null;
// ...
}
.. code-block:: annotation
<?php
class Message
@@ -375,7 +395,7 @@ annotation.
* @Column(type="integer")
* @GeneratedValue
*/
private $id;
private int|null $id = null;
// ...
}
@@ -402,7 +422,7 @@ annotation.
fields:
# fields here
In most cases using the automatic generator strategy (``@GeneratedValue``) is
In most cases using the automatic generator strategy (``#[GeneratedValue]``) is
what you want. It defaults to the identifier generation mechanism your current
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
and Oracle and so on.
@@ -438,8 +458,8 @@ Here is the list of possible generation strategies:
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
thus generated) by your code. The assignment must take place before
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the @GeneratedValue entirely.
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
same as leaving off the ``#[GeneratedValue]`` entirely.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
Sequence Generator
@@ -451,7 +471,19 @@ besides specifying the sequence's name:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class Message
{
#[Id]
#[GeneratedValue(strategy: 'SEQUENCE')]
#[SequenceGenerator(sequenceName: 'message_seq', initialValue: 1, allocationSize: 100)]
protected int|null $id = null;
// ...
}
.. code-block:: annotation
<?php
class Message
@@ -461,7 +493,7 @@ besides specifying the sequence's name:
* @GeneratedValue(strategy="SEQUENCE")
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
*/
protected $id = null;
protected int|null $id = null;
// ...
}
@@ -526,11 +558,12 @@ need to access the sequence once to generate the identifiers for
Composite Keys
~~~~~~~~~~~~~~
With Doctrine ORM you can use composite primary keys, using ``@Id`` on more then
one column. Some restrictions exist opposed to using a single identifier in
this case: The use of the ``@GeneratedValue`` annotation is not supported,
which means you can only use composite keys if you generate the primary key
values yourself before calling ``EntityManager#persist()`` on the entity.
With Doctrine ORM you can use composite primary keys, using ``#[Id]`` on
more than one column. Some restrictions exist opposed to using a single
identifier in this case: The use of the ``#[GeneratedValue]`` attribute
is not supported, which means you can only use composite keys if you
generate the primary key values yourself before calling
``EntityManager#persist()`` on the entity.
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
<../tutorials/composite-primary-keys>`.
@@ -546,7 +579,8 @@ needs to be done explicitly using ticks in the definition.
.. code-block:: php
<?php
/** @Column(name="`number`", type="integer") */
#[Column(name: '`number`', type: 'integer')]
private $number;
Doctrine will then quote this column name in all SQL statements

View File

@@ -74,11 +74,13 @@ collections in entities in the constructor. Example:
<?php
namespace MyProject\Model;
use Doctrine\Common\Collections\ArrayCollection;
class User {
private $addresses;
private $articles;
/** @var Collection<int, Address> */
private Collection $addresses;
/** @var Collection<int, Article> */
private Collection $articles;
public function __construct() {
$this->addresses = new ArrayCollection;
$this->articles = new ArrayCollection;

View File

@@ -109,8 +109,9 @@ Metadata Cache
~~~~~~~~~~~~~~
Your class metadata can be parsed from a few different sources like
YAML, XML, Annotations, etc. Instead of parsing this information on
each request we should cache it using one of the cache drivers.
YAML, XML, Attributes, Annotations etc. Instead of parsing this
information on each request we should cache it using one of the cache
drivers.
Just like the query and result cache we need to configure it
first.
@@ -199,5 +200,3 @@ not letting your users' requests populate the cache.
You can read more about cache slams
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.

View File

@@ -49,10 +49,9 @@ This policy can be configured as follows:
.. code-block:: php
<?php
/**
* @Entity
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
#[Entity]
#[ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
class User
{
// ...
@@ -78,18 +77,16 @@ follows:
<?php
use Doctrine\Persistence\NotifyPropertyChanged,
Doctrine\Persistence\PropertyChangedListener;
/**
* @Entity
* @ChangeTrackingPolicy("NOTIFY")
*/
#[Entity]
#[ChangeTrackingPolicy('NOTIFY')]
class MyEntity implements NotifyPropertyChanged
{
// ...
private $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener)
private array $_listeners = array();
public function addPropertyChangedListener(PropertyChangedListener $listener): void
{
$this->_listeners[] = $listener;
}
@@ -104,12 +101,12 @@ behaviour:
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
// ...
protected function _onPropertyChanged($propName, $oldValue, $newValue)
protected function _onPropertyChanged($propName, $oldValue, $newValue): void
{
if ($this->_listeners) {
foreach ($this->_listeners as $listener) {
@@ -117,8 +114,8 @@ behaviour:
}
}
}
public function setData($data)
public function setData($data): void
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
@@ -134,18 +131,18 @@ The check whether the new value is different from the old one is
not mandatory but recommended. That way you also have full control
over when you consider a property changed.
If your entity contains an embeddable, you will need to notify
separately for each property in the embeddable when it changes
If your entity contains an embeddable, you will need to notify
separately for each property in the embeddable when it changes
for example:
.. code-block:: php
<?php
// ...
class MyEntity implements NotifyPropertyChanged
{
public function setEmbeddable(MyValueObject $embeddable)
public function setEmbeddable(MyValueObject $embeddable): void
{
if (!$embeddable->equals($this->embeddable)) {
// notice the entityField.embeddableField notation for referencing the property
@@ -178,5 +175,3 @@ The positive point and main advantage of this policy is its
effectiveness. It has the best performance characteristics of the 3
policies with larger units of work and a flush() operation is very
cheap when nothing has changed.

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::createAnnotationMetadataConfiguration($paths, $isDevMode);
$entityManager = EntityManager::create($dbParams, $config);
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
$connection = DriverManager::getConnection($dbParams, $config);
$entityManager = new EntityManager($connection, $config);
.. note::
@@ -68,24 +70,26 @@ 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`:
::
"symfony/yaml": "*"
Inside the ``ORMSetup`` methods several assumptions are made:

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:
@@ -672,18 +672,18 @@ The same restrictions apply for the reference of related entities.
DQL DELETE statements are ported directly into an SQL DELETE statement.
Therefore, some limitations apply:
- Lifecycle events for the affected entities are not executed.
- A cascading ``remove`` operation (as indicated e. g. by ``cascade={"remove"}``
or ``cascade={"all"}`` in the mapping configuration) is not being performed
- A cascading ``remove`` operation (as indicated e. g. by ``cascade: ['remove']``
or ``cascade: ['all']`` in the mapping configuration) is not being performed
for associated entities. You can rely on database level cascade operations by
configuring each join column with the ``onDelete`` option.
- Checks for the version column are bypassed if they are not explicitly added
to the WHERE clause of the query.
When you rely on one of these features, one option is to use the
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
It means collections and related entities are fetched into memory
When you rely on one of these features, one option is to use the
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
It means collections and related entities are fetched into memory
(even if they are marked as lazy). Pulling object graphs into memory on cascade
can cause considerable performance overhead, especially when the cascaded collections
are large. Make sure to weigh the benefits and downsides.
@@ -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
{
@@ -864,36 +864,26 @@ scenario it is a generic Person and Employee example:
<?php
namespace Entities;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
class Person
{
/**
* @Id @Column(type="integer")
* @GeneratedValue
*/
protected $id;
#[Id, Column(type: 'integer')]
#[GeneratedValue]
protected int|null $id = null;
/**
* @Column(type="string", length=50)
*/
protected $name;
#[Column(type: 'string', length: 50)]
protected string $name;
// ...
}
/**
* @Entity
*/
#[Entity]
class Employee extends Person
{
/**
* @Column(type="string", length=50)
*/
#[Column(type: 'string', length: 50)]
private $department;
// ...
@@ -959,12 +949,11 @@ table, you just need to change the inheritance type from
.. code-block:: php
<?php
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
class Person
{
// ...
@@ -1068,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
@@ -1841,5 +1830,3 @@ Functions
"LOWER" "(" StringPrimary ")" |
"UPPER" "(" StringPrimary ")" |
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"

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
@@ -827,39 +827,84 @@ you need to map the listener method using the event type mapping:
.. configuration-block::
.. code-block:: php
.. 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, PrePersistEventArgs $event): void { // ... }
#[PostPersist]
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
#[PreUpdate]
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
#[PostUpdate]
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
#[PostRemove]
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
#[PreRemove]
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
#[PreFlush]
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
#[PostLoad]
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: annotation
<?php
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\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Event\PreRemoveEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
class UserListener
{
/** @PrePersist */
public function prePersistHandler(User $user, LifecycleEventArgs $event) { // ... }
public function prePersistHandler(User $user, PrePersistEventArgs $event): void { // ... }
/** @PostPersist */
public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postPersistHandler(User $user, PostPersistEventArgs $event): void { // ... }
/** @PreUpdate */
public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
/** @PostUpdate */
public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postUpdateHandler(User $user, PostUpdateEventArgs $event): void { // ... }
/** @PostRemove */
public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postRemoveHandler(User $user, PostRemoveEventArgs $event): void { // ... }
/** @PreRemove */
public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
public function preRemoveHandler(User $user, PreRemoveEventArgs $event): void { // ... }
/** @PreFlush */
public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
/** @PostLoad */
public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
public function postLoadHandler(User $user, PostLoadEventArgs $event): void { // ... }
}
.. code-block:: xml
<doctrine-mapping>
@@ -971,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:
@@ -980,7 +1025,7 @@ Load ClassMetadata Event
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
mapping metadata for a class has been loaded from a mapping source
(annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
(attributes/annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
You can hook in to this process and manipulate the instance.
This event is not a lifecycle callback.
@@ -1071,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

@@ -13,10 +13,11 @@ Database Schema
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can't set these values inside the annotations, yml or xml mapping files. To make a database
work with the default charset and collation you should configure MySQL to use it as default charset,
or create the database with charset and collation details. This way they get inherited to all newly
created database tables and columns.
You can't set these values with attributes, annotations or inside yml or
xml mapping files. To make a database work with the default charset and
collation you should configure MySQL to use it as default charset, or
create the database with charset and collation details. This way they
get inherited to all newly created database tables and columns.
Entity Classes
--------------
@@ -32,11 +33,12 @@ upon insert:
class User
{
const STATUS_DISABLED = 0;
const STATUS_ENABLED = 1;
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
private $algorithm = "sha1";
private $status = self:STATUS_DISABLED;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self:STATUS_DISABLED;
}
.

View File

@@ -42,7 +42,7 @@ proper quoting of parameters.
class MyLocaleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {

View File

@@ -38,38 +38,36 @@ Example:
use Doctrine\ORM\Mapping\MappedSuperclass;
use Doctrine\ORM\Mapping\Entity;
/** @MappedSuperclass */
#[MappedSuperclass]
class Person
{
/** @Column(type="integer") */
protected $mapped1;
/** @Column(type="string") */
protected $mapped2;
/**
* @OneToOne(targetEntity="Toothbrush")
* @JoinColumn(name="toothbrush_id", referencedColumnName="id")
*/
protected $toothbrush;
#[Column(type: 'integer')]
protected int $mapped1;
#[Column(type: 'string')]
protected string $mapped2;
#[OneToOne(targetEntity: Toothbrush::class)]
#[JoinColumn(name: 'toothbrush_id', referencedColumnName: 'id')]
protected Toothbrush|null $toothbrush = null;
// ... more fields and methods
}
/** @Entity */
#[Entity]
class Employee extends Person
{
/** @Id @Column(type="integer") */
private $id;
/** @Column(type="string") */
private $name;
#[Id, Column(type: 'integer')]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
// ... more fields and methods
}
/** @Entity */
#[Entity]
class Toothbrush
{
/** @Id @Column(type="integer") */
private $id;
#[Id, Column(type: 'integer')]
private int|null $id = null;
// ... more fields and methods
}
@@ -99,11 +97,31 @@ Example:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace MyProject\Model;
#[Entity]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
#[Entity]
class Employee extends Person
{
// ...
}
.. code-block:: annotation
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
@@ -114,7 +132,7 @@ Example:
{
// ...
}
/**
* @Entity
*/
@@ -124,7 +142,7 @@ Example:
}
.. code-block:: yaml
MyProject\Model\Person:
type: entity
inheritanceType: SINGLE_TABLE
@@ -134,29 +152,30 @@ Example:
discriminatorMap:
person: Person
employee: Employee
MyProject\Model\Employee:
type: entity
Things to note:
- The @InheritanceType and @DiscriminatorColumn must be specified
on the topmost class that is part of the mapped entity hierarchy.
- The @DiscriminatorMap specifies which values of the
- The ``#[InheritanceType]`` and ``#[DiscriminatorColumn]`` must be
specified on the topmost class that is part of the mapped entity
hierarchy.
- The ``#[DiscriminatorMap]`` specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
type ``Person`` and "employee" identifies a row as being of type
``Employee``.
- All entity classes that is part of the mapped entity hierarchy
(including the topmost class) should be specified in the
@DiscriminatorMap. In the case above Person class included.
``#[DiscriminatorMap]``. In the case above Person class included.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
Design-time considerations
@@ -212,19 +231,17 @@ Example:
<?php
namespace MyProject\Model;
/**
* @Entity
* @InheritanceType("JOINED")
* @DiscriminatorColumn(name="discr", type="string")
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
#[Entity]
#[InheritanceType('JOINED')]
#[DiscriminatorColumn(name: 'discr', type: 'string')]
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
class Person
{
// ...
}
/** @Entity */
#[Entity]
class Employee extends Person
{
// ...
@@ -233,10 +250,10 @@ Example:
Things to note:
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
must be specified on the topmost class that is part of the mapped
entity hierarchy.
- The @DiscriminatorMap specifies which values of the
- The ``#[InheritanceType]``, ``#[DiscriminatorColumn]`` and
``#[DiscriminatorMap]`` must be specified on the topmost class that is
part of the mapped entity hierarchy.
- The ``#[DiscriminatorMap]`` specifies which values of the
discriminator column identify a row as being of which type. In the
case above a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type
@@ -246,7 +263,7 @@ Things to note:
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
.. note::
@@ -290,7 +307,7 @@ be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
There is also another important performance consideration that it is *NOT POSSIBLE*
There is also another important performance consideration that it is *NOT POSSIBLE*
to query for the base entity without any LEFT JOINs to the sub-types.
SQL Schema considerations
@@ -330,7 +347,52 @@ Example:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
class User
{
// other fields mapping
/** @var Collection<int, Group> */
#[JoinTable(name: 'users_groups')]
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
#[ManyToMany(targetEntity: 'Group', inversedBy: 'users')]
protected Collection $groups;
#[ManyToOne(targetEntity: 'Address')]
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
protected Address|null $address = null;
}
// admin mapping
namespace MyProject\Model;
#[Entity]
#[AssociationOverrides([
new AssociationOverride(
name: 'groups',
joinTable: new JoinTable(
name: 'users_admingroups',
),
joinColumns: [new JoinColumn(name: 'adminuser_id')],
inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')]
),
new AssociationOverride(
name: 'address',
joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')]
)
])]
class Admin extends User
{
}
.. code-block:: annotation
<?php
// user mapping
@@ -348,14 +410,15 @@ Example:
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
* @var Collection<int, Group>
*/
protected $groups;
protected Collection $groups;
/**
* @ManyToOne(targetEntity="Address")
* @JoinColumn(name="address_id", referencedColumnName="id")
*/
protected $address;
protected Address|null $address = null;
}
// admin mapping
@@ -490,7 +553,51 @@ Could be used by an entity that extends a mapped superclass to override a field
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// user mapping
namespace MyProject\Model;
#[MappedSuperclass]
class User
{
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
protected int|null $id = null;
#[Column(name: 'user_name', nullable: true, unique: false, length: 250)]
protected string $name;
// other fields mapping
}
// guest mapping
namespace MyProject\Model;
#[Entity]
#[AttributeOverrides([
new AttributeOverride(
name: 'id',
column: new Column(
name: 'guest_id',
type: 'integer',
length: 140
)
),
new AttributeOverride(
name: 'name',
column: new Column(
name: 'guest_name',
nullable: false,
unique: true,
length: 240
)
)
])]
class Guest extends User
{
}
.. code-block:: annotation
<?php
// user mapping
@@ -501,10 +608,10 @@ Could be used by an entity that extends a mapped superclass to override a field
class User
{
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
protected $id;
protected int|null $id = null;
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
protected $name;
protected string $name;
// other fields mapping
}

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

@@ -7,7 +7,7 @@ reduce the verbosity of the mapping document, eliminating repetitive noise (eg:
.. warning
The naming strategy is always overridden by entity mapping such as the `Table` annotation.
The naming strategy is always overridden by entity mapping such as the `Table` attribute.
Configuring a naming strategy
-----------------------------

View File

@@ -90,8 +90,8 @@ Static Function
In addition to the PHP files you can also specify your mapping
information inside of a static function defined on the entity class
itself. This is useful for cases where you want to keep your entity
and mapping information together but don't want to use annotations.
For this you just need to use the ``StaticPHPDriver``:
and mapping information together but don't want to use attributes or
annotations. For this you just need to use the ``StaticPHPDriver``:
.. code-block:: php
@@ -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()``
@@ -325,5 +325,3 @@ entities themselves.
- ``setIdentifierValues($entity, $id)``
- ``setFieldValue($entity, $field, $value)``
- ``getFieldValue($entity, $field)``

View File

@@ -434,6 +434,12 @@ complete list of supported helper methods available:
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
public function isNotNull($x); // Returns string
// Example - $qb->expr()->isMemberOf('?1', 'u.groups') => ?1 MEMBER OF u.groups
public function isMemberOf($x, $y); // Returns Expr\Comparison instance
// Example - $qb->expr()->isInstanceOf('u', Employee::class) => u INSTANCE OF Employee
public function isInstanceOf($x, $y); // Returns Expr\Comparison instance
/** Arithmetic objects **/

View File

@@ -277,26 +277,44 @@ level cache region.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
#[Cache(usage: 'READ_ONLY', region: 'my_entity_region')]
class Country
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
#[Column(unique: true)]
protected string $name;
// other properties and methods
}
.. code-block:: annotation
<?php
/**
* @Entity
* @Cache(usage="READ_ONLY", region="my_entity_region")
* @Cache("NONSTRICT_READ_WRITE")
*/
class Country
class State
{
/**
* @Id
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected $name;
protected string $name;
// other properties and methods
}
@@ -340,7 +358,35 @@ It caches the primary keys of association and cache each element will be cached
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
class State
{
#[Id]
#[GeneratedValue]
#[Column]
protected int|null $id = null;
#[Column(unique: true)]
protected string $name;
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[ManyToOne(targetEntity: Country::class)]
#[JoinColumn(name: 'country_id', referencedColumnName: 'id')]
protected Country|null $country = null;
/** @var Collection<int, City> */
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
#[OneToMany(targetEntity: City::class, mappedBy: 'state')]
protected Collection $cities;
// other properties and methods
}
.. code-block:: annotation
<?php
/**
@@ -354,25 +400,26 @@ It caches the primary keys of association and cache each element will be cached
* @GeneratedValue
* @Column(type="integer")
*/
protected $id;
protected int|null $id = null;
/**
* @Column(unique=true)
*/
protected $name;
protected string $name;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @ManyToOne(targetEntity="Country")
* @JoinColumn(name="country_id", referencedColumnName="id")
*/
protected $country;
protected Country|null $country;
/**
* @Cache("NONSTRICT_READ_WRITE")
* @OneToMany(targetEntity="City", mappedBy="state")
* @var Collection<int, City>
*/
protected $cities;
protected Collection $cities;
// other properties and methods
}
@@ -673,23 +720,18 @@ For performance reasons the cache API does not extract from composite primary ke
.. code-block:: php
<?php
/**
* @Entity
*/
#[Entity]
class Reference
{
/**
* @Id
* @ManyToOne(targetEntity="Article", inversedBy="references")
* @JoinColumn(name="source_id", referencedColumnName="article_id")
*/
private $source;
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'source_id', referencedColumnName: 'article_id')]
private Article|null $source = null;
/**
* @Id
* @ManyToOne(targetEntity="Article")
* @JoinColumn(name="target_id", referencedColumnName="article_id")
*/
#[Id]
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
#[JoinColumn(name: 'target_id', referencedColumnName: 'article_id')]
private $target;
}

View File

@@ -98,19 +98,20 @@ entity might look like this:
<?php
/**
* @Entity
*/
#[Entity]
class InsecureEntity
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
/** @Column */
private $email;
/** @Column(type="boolean") */
private $isAdmin;
#[Id, Column, GeneratedValue]
private int|null $id = null;
public function fromArray(array $userInput)
#[Column]
private string $email;
#[Column]
private bool $isAdmin;
/** @param array<string, mixed> $userInput */
public function fromArray(array $userInput): void
{
foreach ($userInput as $key => $value) {
$this->$key = $value;
@@ -118,7 +119,7 @@ entity might look like this:
}
}
Now the possiblity of mass-asignment exists on this entity and can
Now the possiblity of mass-assignment exists on this entity and can
be exploited by attackers to set the "isAdmin" flag to true on any
object when you pass the whole request data to this method like:

View File

@@ -189,14 +189,25 @@ example we'll use an integer.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'integer')]
private int $version;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="integer") */
private $version;
private int $version;
// ...
}
@@ -222,14 +233,25 @@ timestamp or datetime):
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
class User
{
// ...
#[Version, Column(type: 'datetime')]
private DateTime $version;
// ...
}
.. code-block:: annotation
<?php
class User
{
// ...
/** @Version @Column(type="datetime") */
private $version;
private DateTime $version;
// ...
}
@@ -279,15 +301,15 @@ either when calling ``EntityManager#find()``:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
@@ -300,16 +322,16 @@ Or you can use ``EntityManager#lock()`` to find out:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
$entity = $em->find('User', $theEntityId);
try {
// assert version
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
} catch(OptimisticLockException $e) {
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
}
@@ -348,7 +370,7 @@ See the example code, The form (GET Request):
<?php
$post = $em->find('BlogPost', 123456);
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
@@ -359,7 +381,7 @@ And the change headline action (POST Request):
<?php
$postId = (int)$_GET['id'];
$postVersion = (int)$_GET['version'];
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
.. _transactions-and-concurrency_pessimistic-locking:
@@ -390,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
@@ -402,8 +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

@@ -17,7 +17,7 @@ ask for an entity with a specific ID twice, it will return the same instance:
.. code-block:: php
public function testIdentityMap()
public function testIdentityMap(): void
{
$objectA = $this->entityManager->find('EntityName', 1);
$objectB = $this->entityManager->find('EntityName', 1);
@@ -34,11 +34,11 @@ will still end up with the same reference:
.. code-block:: php
public function testIdentityMapReference()
public function testIdentityMapReference(): void
{
$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);
@@ -104,7 +104,7 @@ How Doctrine Detects Changes
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
This means you map php objects into a relational database that don't
necessarily know about the database at all. A natural question would now be,
"how does Doctrine even detect objects have changed?".
"how does Doctrine even detect objects have changed?".
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
an object from the database Doctrine will keep a copy of all the properties and
@@ -202,4 +202,3 @@ ClassMetadataFactory
~~~~~~~~~~~~~~~~~~~~
tbr

View File

@@ -32,62 +32,62 @@ information about its type and if it's the owning or inverse side.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class User
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
#[Id, GeneratedValue, Column]
private int|null $id = null;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments")
* @var Collection<int, Comment>
*/
private $favorites;
#[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')]
#[JoinTable(name: 'user_favorite_comments')]
private Collection $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments")
* @var Collection<int, Comment>
*/
private $commentsRead;
#[ManyToMany(targetEntity: Comment::class)]
#[JoinTable(name: 'user_read_comments')]
private Collection $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
* @var Collection<int, Comment>
*/
private $commentsAuthored;
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
private Collection $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
/** Unidirectional - Many-To-One */
#[ManyToOne(targetEntity: Comment::class)]
private Comment|null $firstComment = null;
}
/** @Entity */
#[Entity]
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
#[Id, GeneratedValue, Column]
private string $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
* @var Collection<int, User>
*/
private $userFavorites;
#[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')]
private Collection $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
*/
private $author;
#[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')]
private User|null $author = null;
}
This two entities generate the following MySQL Schema (Foreign Key
@@ -132,11 +132,12 @@ relations of the ``User``:
class User
{
// ...
public function getReadComments() {
/** @return Collection<int, Comment> */
public function getReadComments(): Collection {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
public function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
}
@@ -172,11 +173,13 @@ fields on both sides:
{
// ..
public function getAuthoredComments() {
/** @return Collection<int, Comment> */
public function getAuthoredComments(): Collection {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
/** @return Collection<int, Comment> */
public function getFavoriteComments(): Collection {
return $this->favorites;
}
}
@@ -185,11 +188,12 @@ fields on both sides:
{
// ...
public function getUserFavorites() {
/** @return Collection<int, User> */
public function getUserFavorites(): Collection {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
public function setAuthor(User|null $author = null): void {
$this->author = $author;
}
}
@@ -292,12 +296,12 @@ example that encapsulate much of the association management code:
class User
{
// ...
public function markCommentRead(Comment $comment) {
public function markCommentRead(Comment $comment): void {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
public function addComment(Comment $comment): void {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
@@ -305,16 +309,16 @@ example that encapsulate much of the association management code:
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
private function setFirstComment(Comment $c): void {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
public function addFavorite(Comment $comment): void {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
public function removeFavorite(Comment $comment): void {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
@@ -324,11 +328,11 @@ example that encapsulate much of the association management code:
{
// ..
public function addUserFavorite(User $user) {
public function addUserFavorite(User $user): void {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
public function removeUserFavorite(User $user): void {
$this->userFavorites->removeElement($user);
}
}
@@ -356,7 +360,8 @@ the details inside the classes can be challenging.
<?php
class User {
public function getReadComments() {
/** @return array<int, Comment> */
public function getReadComments(): array {
return $this->commentsRead->toArray();
}
}
@@ -437,8 +442,10 @@ only accessing it through the User entity:
// User entity
class User
{
private $id;
private $comments;
private int $id;
/** @var Collection<int, Comment> */
private Collection $comments;
public function __construct()
{
@@ -464,11 +471,8 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
class User
{
// ...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
/** Bidirectional - One-To-Many (INVERSE SIDE) */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author', cascade: ['persist', 'remove'])]
private $commentsAuthored;
// ...
}
@@ -577,31 +581,30 @@ and StandingData:
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
*/
#[Entity]
class Contact
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @OneToOne(targetEntity="StandingData", cascade={"persist"}, orphanRemoval=true) */
private $standingData;
#[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)]
private StandingData|null $standingData = null;
/** @OneToMany(targetEntity="Address", mappedBy="contact", cascade={"persist"}, orphanRemoval=true) */
private $addresses;
/** @var Collection<int, Address> */
#[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)]
private Collection $addresses;
public function __construct()
{
$this->addresses = new ArrayCollection();
}
public function newStandingData(StandingData $sd)
public function newStandingData(StandingData $sd): void
{
$this->standingData = $sd;
}
public function removeAddress($pos)
public function removeAddress(int $pos): void
{
unset($this->addresses[$pos]);
}

View File

@@ -95,28 +95,29 @@ from newly opened EntityManager.
.. code-block:: php
<?php
/** @Entity */
#[Entity]
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @Column(type="string") */
private $headline;
#[Column(type: 'string')]
private string $headline;
/** @ManyToOne(targetEntity="User") */
private $author;
#[ManyToOne(targetEntity: User::class)]
private User|null $author = null;
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
private $comments;
/** @var Collection<int, Comment> */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
private Collection $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getAuthor() { return $this->author; }
public function getComments() { return $this->comments; }
public function getAuthor(): User|null { return $this->author; }
public function getComments(): Collection { return $this->comments; }
}
$article = $em->find('Article', 1);
@@ -161,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()
{
// lazy loading code
}
public function getName()
{
$this->_load();
return parent::getName();
}
// .. other public methods of User
}
.. warning::
Traversing the object graph for parts that are lazy-loaded will
@@ -304,7 +283,7 @@ as follows:
- A removed entity X will be removed from the database as a result
of the flush operation.
After an entity has been removed its in-memory state is the same as
After an entity has been removed, its in-memory state is the same as
before the removal, except for generated identifiers.
Removing an entity will also automatically delete any existing
@@ -339,8 +318,8 @@ in multiple ways with very different performance impacts.
.. note::
Calling ``remove`` on an entity will remove the object from the identity
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
Detaching entities
@@ -413,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:
@@ -833,7 +804,7 @@ By default the EntityManager returns a default implementation of
``Doctrine\ORM\EntityRepository`` when you call
``EntityManager#getRepository($entityClass)``. You can overwrite
this behaviour by specifying the class name of your own Entity
Repository in the Annotation, XML or YAML metadata. In large
Repository in the Attribute, Annotation, XML or YAML metadata. In large
applications that require lots of specialized DQL queries using a
custom repository is one recommended way of grouping these queries
in a central location.
@@ -843,12 +814,11 @@ in a central location.
<?php
namespace MyDomain\Model;
use MyDomain\Model\UserRepository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
@@ -856,7 +826,8 @@ in a central location.
class UserRepository extends EntityRepository
{
public function getAllAdminUsers()
/** @return Collection<User> */
public function getAllAdminUsers(): Collection
{
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
->getResult();
@@ -871,5 +842,3 @@ You can access your repository now by calling:
// $em instanceof EntityManager
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();

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

@@ -23,7 +23,34 @@ and year of production as primary keys:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace VehicleCatalogue\Model;
#[Entity]
class Car
{
public function __construct(
#[Id, Column(type: 'string')]
private string $name,
#[Id, Column(type: 'integer')]
private int $year,
) {
}
public function getModelName(): string
{
return $this->name;
}
public function getYearOfProduction(): int
{
return $this->year;
}
}
.. code-block:: annotation
<?php
namespace VehicleCatalogue\Model;
@@ -34,9 +61,9 @@ and year of production as primary keys:
class Car
{
/** @Id @Column(type="string") */
private $name;
private string $name;
/** @Id @Column(type="integer") */
private $year;
private int $year;
public function __construct($name, $year)
{
@@ -44,17 +71,38 @@ and year of production as primary keys:
$this->year = $year;
}
public function getModelName()
public function getModelName(): string
{
return $this->name;
}
public function getYearOfProduction()
public function getYearOfProduction(): int
{
return $this->year;
}
}
.. code-block:: annotation
<?php
/**
* @Entity
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private int|null $id = null;
}
/**
* @Entity
*/
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private User|null $user = null;
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
@@ -131,7 +179,7 @@ of one or many parent entities.
The semantics of mapping identity through foreign entities are easy:
- Only allowed on Many-To-One or One-To-One associations.
- Plug an ``@Id`` annotation onto every association.
- Plug an ``#[Id]`` attribute onto every association.
- Set an attribute ``association-key`` with the field name of the association in XML.
- Set a key ``associationKey:`` with the field name of the association in YAML.
@@ -155,16 +203,17 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
class Article
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
private int|null $id = null;
/** @Column(type="string") */
private $title;
private string $title;
/**
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
* @var Collection<int, ArticleAttribute>
*/
private $attributes;
private Collection $attributes;
public function addAttribute($name, $value)
public function addAttribute($name, $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
@@ -176,13 +225,13 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
class ArticleAttribute
{
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
private $article;
private Article|null $article;
/** @Id @Column(type="string") */
private $attribute;
private string $attribute;
/** @Column(type="string") */
private $value;
private string $value;
public function __construct($name, $value, $article)
{
@@ -192,6 +241,51 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
}
}
.. code-block:: attribute
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Article
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $title;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column(type: 'string')]
private string $attribute;
#[Column(type: 'string')]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
@@ -202,7 +296,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
<entity name="Application\Model\ArticleAttribute">
<id name="article" association-key="true" />
<id name="attribute" type="string" />
<field name="value" type="string" />
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
@@ -237,25 +331,22 @@ One good example for this is a user-address relationship:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
/**
* @Entity
*/
#[Entity]
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
}
/**
* @Entity
*/
#[Entity]
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private $user;
#[Id, OneToOne(targetEntity: User::class)]
private User|null $user = null;
}
.. code-block:: yaml
@@ -288,68 +379,70 @@ of products purchased and maybe even the current price.
.. code-block:: php
<?php
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
/** @Entity */
#[Entity]
class Order
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @ManyToOne(targetEntity="Customer") */
private $customer;
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
/** @var ArrayCollection<int, OrderItem> */
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
private Collection $items;
/** @Column(type="boolean") */
private $paid = false;
/** @Column(type="boolean") */
private $shipped = false;
/** @Column(type="datetime") */
private $created;
#[Column(type: 'boolean')]
private bool $paid = false;
#[Column(type: 'boolean')]
private bool $shipped = false;
#[Column(type: 'datetime')]
private DateTime $created;
public function __construct(Customer $customer)
{
$this->customer = $customer;
public function __construct(
#[ManyToOne(targetEntity: Customer::class)]
private Customer $customer,
) {
$this->items = new ArrayCollection();
$this->created = new \DateTime("now");
$this->created = new DateTime("now");
}
}
/** @Entity */
#[Entity]
class Product
{
/** @Id @Column(type="integer") @GeneratedValue */
private $id;
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
/** @Column(type="string") */
private $name;
#[Column(type: 'string')]
private string $name;
/** @Column(type="decimal") */
private $currentPrice;
#[Column(type: 'decimal')]
private float $currentPrice;
public function getCurrentPrice()
public function getCurrentPrice(): float
{
return $this->currentPrice;
}
}
/** @Entity */
#[Entity]
class OrderItem
{
/** @Id @ManyToOne(targetEntity="Order") */
private $order;
#[Id, ManyToOne(targetEntity: Order::class)]
private Order|null $order = null;
/** @Id @ManyToOne(targetEntity="Product") */
private $product;
#[Id, ManyToOne(targetEntity: Product::class)]
private Product|null $product = null;
/** @Column(type="integer") */
private $amount = 1;
#[Column(type: 'integer')]
private int $amount = 1;
/** @Column(type="decimal") */
private $offeredPrice;
#[Column(type: 'decimal')]
private float $offeredPrice;
public function __construct(Order $order, Product $product, $amount = 1)
public function __construct(Order $order, Product $product, int $amount = 1)
{
$this->order = $order;
$this->product = $product;

View File

@@ -1,10 +1,10 @@
Separating Concerns using Embeddables
-------------------------------------
=====================================
Embeddables are classes which are not entities themselves, but are embedded
in entities and can also be queried in DQL. You'll mostly want to use them
to reduce duplication or separating concerns. Value objects such as date range
or address are the primary use case for this feature.
or address are the primary use case for this feature.
.. note::
@@ -17,7 +17,34 @@ instead of simply adding the respective columns to the ``User`` class.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class)]
private Address $address;
}
#[Embeddable]
class Address
{
#[Column(type: "string")]
private string $street;
#[Column(type: "string")]
private string $postalCode;
#[Column(type: "string")]
private string $city;
#[Column(type: "string")]
private string $country;
}
.. code-block:: annotation
<?php
@@ -25,23 +52,23 @@ instead of simply adding the respective columns to the ``User`` class.
class User
{
/** @Embedded(class = "Address") */
private $address;
private Address $address;
}
/** @Embeddable */
class Address
{
/** @Column(type = "string") */
private $street;
private string $street;
/** @Column(type = "string") */
private $postalCode;
private string $postalCode;
/** @Column(type = "string") */
private $city;
private string $city;
/** @Column(type = "string") */
private $country;
private string $country;
}
.. code-block:: xml
@@ -109,7 +136,18 @@ The following example shows you how to set your prefix to ``myPrefix_``:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class, columnPrefix: "myPrefix_")]
private Address $address;
}
.. code-block:: annotation
<?php
@@ -140,7 +178,18 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
#[Embedded(class: Address::class, columnPrefix: false)]
private Address $address;
}
.. code-block:: annotation
<?php
@@ -148,9 +197,15 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
class User
{
/** @Embedded(class = "Address", columnPrefix = false) */
private $address;
private Address $address;
}
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
.. code-block:: yaml
User:
@@ -160,12 +215,6 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
class: Address
columnPrefix: false
.. code-block:: xml
<entity name="User">
<embedded name="address" class="Address" use-column-prefix="false" />
</entity>
DQL
---
@@ -176,4 +225,3 @@ as if they were declared in the ``User`` class:
.. code-block:: sql
SELECT u FROM User u WHERE u.address.city = :myCity

View File

@@ -52,7 +52,20 @@ switch to extra lazy as shown in these examples:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\CMS;
#[Entity]
class CmsGroup
{
/** @var Collection<int, CmsUser> */
#[ManyToMany(targetEntity: CmsUser::class, mappedBy: 'groups', fetch: 'EXTRA_LAZY')]
public Collection $users;
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\CMS;
@@ -64,8 +77,9 @@ switch to extra lazy as shown in these examples:
{
/**
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
* @var Collection<int, CmsUser>
*/
public $users;
public Collection $users;
}
.. code-block:: xml
@@ -92,4 +106,3 @@ switch to extra lazy as shown in these examples:
targetEntity: CmsUser
mappedBy: groups
fetch: EXTRA_LAZY

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
-----------------------------
@@ -83,7 +84,6 @@ that directory with the following contents:
"require": {
"doctrine/orm": "^2.11.0",
"doctrine/dbal": "^3.2",
"doctrine/annotations": "1.13.2",
"symfony/yaml": "^5.4",
"symfony/cache": "^5.4"
},
@@ -137,38 +137,44 @@ step:
<?php
// bootstrap.php
use Doctrine\DBAL\DriverManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\ORMSetup;
require_once "vendor/autoload.php";
// Create a simple "default" Doctrine ORM configuration for Annotations
$isDevMode = true;
$proxyDir = null;
$cache = null;
$useSimpleAnnotationReader = false;
$config = ORMSetup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
// or if you prefer YAML or XML
// $config = ORMSetup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
// $config = ORMSetup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
// Create a simple "default" Doctrine ORM configuration for Attributes
$config = ORMSetup::createAttributeMetadataConfiguration(
paths: array(__DIR__."/src"),
isDevMode: true,
);
// or if you prefer annotation, YAML or XML
// $config = ORMSetup::createAnnotationMetadataConfiguration(
// paths: array(__DIR__."/src"),
// isDevMode: true,
// );
// $config = ORMSetup::createXMLMetadataConfiguration(
// paths: array(__DIR__."/config/xml"),
// isDevMode: true,
//);
// $config = ORMSetup::createYAMLMetadataConfiguration(
// paths: array(__DIR__."/config/yaml"),
// 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.
It is strongly recommended to switch to one of the other mappings.
.. note::
It is recommended not to use the SimpleAnnotationReader because its
usage will be removed for version 3.0.
The ``require_once`` statement sets up the class autoloading for Doctrine and
its dependencies using Composer's autoloader.
@@ -256,14 +262,8 @@ entity definition:
// src/Product.php
class Product
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $name;
private int|null $id = null;
private string $name;
}
When creating entity classes, all of the fields should be ``private``.
@@ -500,14 +500,35 @@ the ``Product`` entity to Doctrine using a metadata language. The metadata
language describes how entities, their properties and references should be
persisted and what constraints should be applied to them.
Metadata for an Entity can be configured using DocBlock annotations directly
in the Entity class itself, or in an external XML or YAML file. This Getting
Started guide will demonstrate metadata mappings using all three methods,
but you only need to choose one.
Metadata for an Entity can be configured using attributes directly in
the Entity class itself, or in an external XML or YAML file. This
Getting Started guide will demonstrate metadata mappings using all three
methods, but you only need to choose one.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/Product.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'products')]
class Product
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $name;
// .. (other code)
}
.. code-block:: annotation
<?php
// src/Product.php
@@ -525,11 +546,11 @@ but you only need to choose one.
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private $name;
private string $name;
// .. (other code)
}
@@ -708,49 +729,35 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="bugs")
*/
#[ORM\Entity]
#[ORM\Table(name: 'bugs')]
class Bug
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
* @var int
*/
private $id;
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int $id;
/**
* @ORM\Column(type="string")
* @var string
*/
private $description;
#[ORM\Column(type: 'string')]
private string $description;
/**
* @ORM\Column(type="datetime")
* @var DateTime
*/
private $created;
#[ORM\Column(type: 'datetime')]
private DateTime $created;
/**
* @ORM\Column(type="string")
* @var string
*/
private $status;
#[ORM\Column(type: 'string')]
private string $status;
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getDescription()
public function getDescription(): string
{
return $this->description;
}
public function setDescription($description)
public function setDescription(string $description): void
{
$this->description = $description;
}
@@ -760,17 +767,17 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
$this->created = $created;
}
public function getCreated()
public function getCreated(): DateTime
{
return $this->created;
}
public function setStatus($status)
public function setStatus($status): void
{
$this->status = $status;
}
public function getStatus()
public function getStatus():string
{
return $this->status;
}
@@ -783,37 +790,31 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="users")
*/
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @var int
*/
private $id;
/** @var int */
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int|null $id = null;
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
/** @var string */
#[ORM\Column(type: 'string')]
private string $name;
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getName()
public function getName(): string
{
return $this->name;
}
public function setName($name)
public function setName(string $name): void
{
$this->name = $name;
}
@@ -842,13 +843,16 @@ domain model to match the requirements:
<?php
// src/Bug.php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
class Bug
{
// ... (previous code)
private $products;
/** @var Collection<int, Product> */
private Collection $products;
public function __construct()
{
@@ -866,8 +870,10 @@ domain model to match the requirements:
{
// ... (previous code)
private $reportedBugs;
private $assignedBugs;
/** @var Collection<int, Bug> */
private Collection $reportedBugs;
/** @var Collection<int, Bug> */
private Collection $assignedBugs;
public function __construct()
{
@@ -884,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
@@ -942,27 +936,27 @@ the bi-directional reference:
{
// ... (previous code)
private $engineer;
private $reporter;
private User $engineer;
private User $reporter;
public function setEngineer(User $engineer)
public function setEngineer(User $engineer): void
{
$engineer->assignedToBug($this);
$this->engineer = $engineer;
}
public function setReporter(User $reporter)
public function setReporter(User $reporter): void
{
$reporter->addReportedBug($this);
$this->reporter = $reporter;
}
public function getEngineer()
public function getEngineer(): User
{
return $this->engineer;
}
public function getReporter()
public function getReporter(): User
{
return $this->reporter;
}
@@ -976,15 +970,17 @@ the bi-directional reference:
{
// ... (previous code)
private $reportedBugs;
private $assignedBugs;
/** @var Collection<int, Bug> */
private Collection $reportedBugs;
/** @var Collection<int, Bug> */
private Collection $assignedBugs;
public function addReportedBug(Bug $bug)
public function addReportedBug(Bug $bug): void
{
$this->reportedBugs[] = $bug;
}
public function assignedToBug(Bug $bug)
public function assignedToBug(Bug $bug): void
{
$this->assignedBugs[] = $bug;
}
@@ -1028,14 +1024,16 @@ the database that points from Bugs to Products.
{
// ... (previous code)
private $products;
/** @var Collection<int, Product> */
private Collection $products;
public function assignToProduct(Product $product)
public function assignToProduct(Product $product): void
{
$this->products[] = $product;
}
public function getProducts()
/** @return Collection<int, Product> */
public function getProducts(): Collection
{
return $this->products;
}
@@ -1046,7 +1044,46 @@ Lets add metadata mappings for the ``Bug`` entity, as we did for
the ``Product`` before:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/Bug.php
use DateTime;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'bugs')]
class Bug
{
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $description;
#[ORM\Column(type: 'datetime')]
private DateTime $created;
#[ORM\Column(type: 'string')]
private string $status;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'assignedBugs')]
private User|null $engineer = null;
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'reportedBugs')]
private User|null $reporter;
/** @var Collection<int, Product> */
#[ORM\ManyToMany(targetEntity: Product::class)]
private Collection $products;
// ... (other code)
}
.. code-block:: annotation
<?php
// src/Bug.php
@@ -1064,37 +1101,37 @@ the ``Product`` before:
* @ORM\Column(type="integer")
* @ORM\GeneratedValue
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
*/
private $description;
private string $description;
/**
* @ORM\Column(type="datetime")
*/
private $created;
private DateTime $created;
/**
* @ORM\Column(type="string")
*/
private $status;
private string $status;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
*/
private $engineer;
private User|null $engineer;
/**
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
*/
private $reporter;
private User|null $reporter;
/**
* @ORM\ManyToMany(targetEntity="Product")
*/
private $products;
private Collection $products;
// ... (other code)
}
@@ -1183,7 +1220,37 @@ Finally, we'll add metadata mappings for the ``User`` entity.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
// src/User.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int|null $id = null;
#[ORM\Column(type: 'string')]
private string $name;
/** @var Collection<int, Bug> An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'reporter')]
private Collection $reportedBugs;
/** @var Collection<int,Bug> An ArrayCollection of Bug objects. */
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'engineer')]
private $assignedBugs;
// .. (other code)
}
.. code-block:: annotation
<?php
// src/User.php
@@ -1202,29 +1269,28 @@ Finally, we'll add metadata mappings for the ``User`` entity.
* @ORM\Column(type="integer")
* @var int
*/
private $id;
private int|null $id = null;
/**
* @ORM\Column(type="string")
* @var string
*/
private $name;
private string $name;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private $reportedBugs;
private Collection $reportedBugs;
/**
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
* @var Bug[] An ArrayCollection of Bug objects.
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
*/
private $assignedBugs;
private Collection $assignedBugs;
// .. (other code)
}
.. code-block:: xml
<!-- config/xml/User.dcm.xml -->
@@ -1512,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:
@@ -1754,7 +1789,20 @@ we have to adjust the metadata slightly.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: BugRepository::class)]
#[ORM\Table(name: 'bugs')]
class Bug
{
// ...
}
.. code-block:: annotation
<?php
@@ -1763,7 +1811,7 @@ we have to adjust the metadata slightly.
/**
* @ORM\Entity(repositoryClass="BugRepository")
* @ORM\Table(name="bugs")
**/
*/
class Bug
{
// ...

View File

@@ -5,28 +5,42 @@ There are use-cases when you'll want to sort collections when they are
retrieved from the database. In userland you do this as long as you
haven't initially saved an entity with its associations into the
database. To retrieve a sorted collection from the database you can
use the ``@OrderBy`` annotation with a collection that specifies
use the ``#[OrderBy]`` attribute with a collection that specifies
a DQL snippet that is appended to all queries with this
collection.
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
can specify the ``@OrderBy`` in the following way:
Additional to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute you
can specify the ``#[OrderBy]`` in the following way:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
#[Entity]
class User
{
// ...
#[ManyToMany(targetEntity: Group::class)]
#[OrderBy(["name" => "ASC"])]
private Collection $groups;
}
.. code-block:: annotation
<?php
/** @Entity **/
class User
{
// ...
/**
* @ManyToMany(targetEntity="Group")
* @OrderBy({"name" = "ASC"})
**/
private $groups;
* @var Collection<int, Group>
*/
private Collection $groups;
}
.. code-block:: xml
@@ -62,7 +76,7 @@ The DQL Snippet in OrderBy is only allowed to consist of
unqualified, unquoted field names and of an optional ASC/DESC
positional statement. Multiple Fields are separated by a comma (,).
The referenced field names have to exist on the ``targetEntity``
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
class of the ``#[ManyToMany]`` or ``#[OneToMany]`` attribute.
The semantics of this feature can be described as follows:
@@ -106,5 +120,3 @@ You can reverse the order with an explicit DQL ORDER BY:
.. code-block:: sql
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC

View File

@@ -14,47 +14,43 @@ Suppose we have a class ExampleEntityWithOverride. This class uses trait Example
.. code-block:: php
<?php
/**
* @Entity
*
* @AttributeOverrides({
* @AttributeOverride(name="foo",
* column=@Column(
* name = "foo_overridden",
* type = "integer",
* length = 140,
* nullable = false,
* unique = false
* )
* )
* })
*
* @AssociationOverrides({
* @AssociationOverride(name="bar",
* joinColumns=@JoinColumn(
* name="example_entity_overridden_bar_id", referencedColumnName="id"
* )
* )
* })
*/
#[Entity]
#[AttributeOverrides([
new AttributeOverride('foo', [
'column' => new Column([
'name' => 'foo_overridden',
'type' => 'integer',
'length' => 140,
'nullable' => false,
'unique' => false,
]),
]),
])]
#[AssociationOverrides([
new AssociationOverride('bar', [
'joinColumns' => new JoinColumn([
'name' => 'example_entity_overridden_bar_id',
'referencedColumnName' => 'id',
]),
]),
])]
class ExampleEntityWithOverride
{
use ExampleTrait;
}
/**
* @Entity
*/
#[Entity]
class Bar
{
/** @Id @Column(type="string") */
#[Id, Column(type: 'string')]
private $id;
}
The docblock is showing metadata override of the attribute and association type. It
basically changes the names of the columns mapped for a property ``foo`` and for
the association ``bar`` which relates to Bar class shown above. Here is the trait
which has mapping metadata that is overridden by the annotation above:
which has mapping metadata that is overridden by the attribute above:
.. code-block:: php
@@ -64,19 +60,15 @@ which has mapping metadata that is overridden by the annotation above:
*/
trait ExampleTrait
{
/** @Id @Column(type="string") */
private $id;
#[Id, Column(type: 'integer')]
private int|null $id = null;
/**
* @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
*/
protected $foo;
#[Column(name: 'trait_foo', type: 'integer', length: 100, nullable: true, unique: true)]
protected int $foo;
/**
* @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
* @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
*/
protected $bar;
#[OneToOne(targetEntity: Bar::class, cascade: ['persist', 'merge'])]
#[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')]
protected Bar|null $bar = null;
}
The case for just extending a class would be just the same but:

View File

@@ -23,6 +23,7 @@ Mapping Indexed Associations
You can map indexed associations by adding:
* ``indexBy`` argument to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute.
* ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation.
* ``index-by`` attribute to any ``<one-to-many />`` or ``<many-to-many />`` xml element.
* ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files.
@@ -30,7 +31,66 @@ You can map indexed associations by adding:
The code and mappings for the Market entity looks like this:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\StockExchange;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[Entity]
#[Table(name: 'exchange_markets')]
class Market
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $name;
/** @var Collection<string, Stock> */
#[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')]
private Collection $stocks;
public function __construct(string $name)
{
$this->name = $name;
$this->stocks = new ArrayCollection();
}
public function getId(): int|null
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock(string $symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
}
return $this->stocks[$symbol];
}
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\StockExchange;
@@ -47,19 +107,19 @@ The code and mappings for the Market entity looks like this:
* @Id @Column(type="integer") @GeneratedValue
* @var int
*/
private $id;
private int|null $id = null;
/**
* @Column(type="string")
* @var string
*/
private $name;
private string $name;
/**
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
* @var Stock[]
* @var Collection<int, Stock>
*/
private $stocks;
private Collection $stocks;
public function __construct($name)
{
@@ -67,22 +127,22 @@ The code and mappings for the Market entity looks like this:
$this->stocks = new ArrayCollection();
}
public function getId()
public function getId(): int|null
{
return $this->id;
}
public function getName()
public function getName(): string
{
return $this->name;
}
public function addStock(Stock $stock)
public function addStock(Stock $stock): void
{
$this->stocks[$stock->getSymbol()] = $stock;
}
public function getStock($symbol)
public function getStock($symbol): Stock
{
if (!isset($this->stocks[$symbol])) {
throw new \InvalidArgumentException("Symbol is not traded on this market.");
@@ -91,7 +151,8 @@ The code and mappings for the Market entity looks like this:
return $this->stocks[$symbol];
}
public function getStocks()
/** @return array<string, Stock> */
public function getStocks(): array
{
return $this->stocks->toArray();
}
@@ -142,7 +203,38 @@ The ``Stock`` entity doesn't contain any special instructions that are new, but
here are the code and mappings for it:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Doctrine\Tests\Models\StockExchange;
#[Entity]
#[Table(name: 'exchange_stocks')]
class Stock
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string', unique: true)]
private string $symbol;
#[ManyToOne(targetEntity: Market::class, inversedBy: 'stocks')]
private Market|null $market;
public function __construct(string $symbol, Market $market)
{
$this->symbol = $symbol;
$this->market = $market;
$market->addStock($this);
}
public function getSymbol(): string
{
return $this->symbol;
}
}
.. code-block:: annotation
<?php
namespace Doctrine\Tests\Models\StockExchange;
@@ -157,18 +249,18 @@ here are the code and mappings for it:
* @Id @GeneratedValue @Column(type="integer")
* @var int
*/
private $id;
private int|null $id = null;
/**
* @Column(type="string", unique=true)
*/
private $symbol;
private string $symbol;
/**
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
* @var Market
*/
private $market;
private Market|null $market = null;
public function __construct($symbol, Market $market)
{
@@ -177,7 +269,7 @@ here are the code and mappings for it:
$market->addStock($this);
}
public function getSymbol()
public function getSymbol(): string
{
return $this->symbol;
}
@@ -249,7 +341,7 @@ now query for the market:
// $em is the EntityManager
$marketId = 1;
$symbol = "AAPL";
$market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId);
// Access the stocks by symbol now:

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

@@ -1077,7 +1077,8 @@ abstract class AbstractQuery
*
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
* @param string|int|null $hydrationMode The hydration mode to use.
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
* @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
*
* @return IterableResult
*/

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

@@ -54,14 +54,14 @@ use function strpos;
* the static create() method. The quickest way to obtain a fully
* configured EntityManager is:
*
* use Doctrine\ORM\Tools\Setup;
* use Doctrine\ORM\Tools\ORMSetup;
* use Doctrine\ORM\EntityManager;
*
* $paths = array('/path/to/entity/mapping/files');
* $paths = ['/path/to/entity/mapping/files'];
*
* $config = Setup::createAnnotationMetadataConfiguration($paths);
* $dbParams = array('driver' => 'pdo_sqlite', 'memory' => true);
* $entityManager = EntityManager::create($dbParams, $config);
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
* $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
@@ -42,7 +44,7 @@ class LifecycleEventArgs extends BaseLifecycleEventArgs
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/9875',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObjectManager() instead.',
'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use getObject() instead.',
__METHOD__
);

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

@@ -82,7 +82,7 @@ final class Events
/**
* The loadClassMetadata event occurs after the mapping metadata for a class
* has been loaded from a mapping source (annotations/xml/yaml).
* has been loaded from a mapping source (attributes/xml/yaml).
*/
public const loadClassMetadata = 'loadClassMetadata';

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

@@ -464,14 +464,14 @@ abstract class AbstractHydrator
break;
}
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
$value = $this->buildEnum($value, $cacheKeyInfo['enumType']);
}
$rowData['data'][$dqlAlias][$fieldName] = $type
? $type->convertToPHPValue($value, $this->_platform)
: $value;
if ($rowData['data'][$dqlAlias][$fieldName] !== null && isset($cacheKeyInfo['enumType'])) {
$rowData['data'][$dqlAlias][$fieldName] = $this->buildEnum($rowData['data'][$dqlAlias][$fieldName], $cacheKeyInfo['enumType']);
}
if ($cacheKeyInfo['isIdentifier'] && $value !== null) {
$id[$dqlAlias] .= '|' . $value;
$nonemptyComponents[$dqlAlias] = true;
@@ -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

@@ -76,12 +76,11 @@ class LazyCriteriaCollection extends AbstractLazyCollection implements Selectabl
}
/**
* {@inheritDoc}
*
* Do an optimized search of an element
*
* @param object $element
* @psalm-param TValue $element
*
* @return bool
* @template TMaybeContained
*/
public function contains($element)
{

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

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