Compare commits

...

149 Commits

Author SHA1 Message Date
flack
9c351e0444 Make sure MemcachedAdapter is supported before tring to use it (#9574) 2022-03-09 16:23:58 +01:00
Thomas Landauer
5ed5383338 Fixing :doc: link (#9569) 2022-03-08 22:31:56 +01:00
Thomas Landauer
eb1d54871b Adding PHP attributes (#9555)
Co-authored-by: Alexander M. Turek <me@derrabus.de>
Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2022-03-08 01:40:01 +01:00
Grégoire Paris
e148c838b0 Merge pull request #9563 from greg0ire/address-sqllogger-deprecation
Remove reference to removed class
2022-03-03 22:27:51 +01:00
Grégoire Paris
c73df2a7b4 Remove reference to removed class
EchoSQLLogger is deprecated in DBAL 2, and removed in DBAL 3.
2022-03-03 22:12:04 +01:00
Jan Barášek
38682e93db Parser: SimpleArithmeticExpression should return ArithmeticTerm (#9557) 2022-03-03 00:25:27 +01:00
Alexander M. Turek
84df37de97 Update baselines for Lexer 1.2.3 (#9546) 2022-02-28 14:06:31 +01:00
Grégoire Paris
856c3143f8 Merge pull request #9537 from kiler129/fix-bug-9536
Make error message suggestion accurate
2022-02-22 20:08:49 +01:00
kiler129
79f73a23f3 Fix bug-#9536
Wrong validation message is displayed when an incorrect bidirectional
bi-directional mapping is set up. When the owning side is configured
correctly and the target side is missing the back reference, the ORM
suggests adding inverseBy instead of mappedBy, with the field name
missing. This commit fixes this problem.
2022-02-22 12:05:35 -06:00
Grégoire Paris
4af912f712 Merge pull request #9539 from greg0ire/use-stable-dbal
Drop minor version number
2022-02-22 18:01:26 +01:00
Grégoire Paris
65f48e0ecd Drop minor version number
We should make it explicit that we mean to test with whatever is the
latest 3.x
2022-02-22 17:25:21 +01:00
Matthias Pigulla
193c3abf0e Bring FilterCollection to a "clean" state after hash computation (#9523)
Co-authored-by: Alexander M. Turek <me@derrabus.de>
2022-02-20 13:09:05 +00:00
Grégoire Paris
5b8263e8fb Merge pull request #9526 from greg0ire/better-model-setup
Make creating test models more straightforward and revert to swallowing exceptions
2022-02-20 09:35:21 +01:00
Grégoire Paris
26e85b8c88 Make creating test models more straightforward
In https://github.com/doctrine/orm/pull/8962, I established that
swallowing exceptions while creating model was bad because it could hide
other helpful exceptions.
As it turns out however, swallowing exceptions is really the way to go
here, because of the performance implication of calling dropSchema(). It
is possible to catch a more precise exception as well, which should
preserve the benefits of not swallowing them.

It looks like I based my grep on the comment inside the catch and today,
I found many other occurrences of this pattern, without the easy to grep
comment.

I decided to fix them as well, but in a lazier way: one no longer has to
remember to call dropSchema in tearDown() now, it is automated.
2022-02-20 09:26:57 +01:00
Grégoire Paris
152c04c03d Merge pull request #9519 from lcobucci/fix-pagination-test
Trigger the desired code path
2022-02-16 09:24:32 +01:00
Luís Cobucci
12ab6fa43f Trigger the desired code path
Since v2.7.0 the ORM avoids using extra queries via the paginator
component when maximum results isn't set on the original query. With
that change, this test was not executing the code path that it was
expected to run.

This makes sure we trigger the forcing of custom DBAL types when
hydrating the identifiers, making sure we don't introduce bugs.

More info:
- Forcing DBAL type conversion: https://github.com/doctrine/orm/pull/7905
- Issue on optimisation: https://github.com/doctrine/orm/issues/7829
- PR on optimisation: https://github.com/doctrine/orm/pull/7863
- Minor BC-break docs: https://github.com/doctrine/orm/blob/2.11.x/UPGRADE.md#minor-bc-break-paginator-output-walkers-arent-be-called-anymore-on-sub-queries-for-queries-without-max-results
2022-02-15 22:59:09 +01:00
Yann Rabiller
e8e61cbbd5 Fix syntax typo in attributes reference (#9513)
Curly brackets are the annotation way of declaring array. Probably a
mistake while copy pasting some examples from annotations.
2022-02-15 14:32:33 +00:00
Tony Lemke
8f847cb5aa Constructor-Argument "options" has the same type as the associated property. (#9501) 2022-02-13 21:42:15 +01:00
David Maicher
599832cb81 Fix AbstractQuery::setParameter phpdoc (#9504)
* Fix AbstractQuery::setParameter phpdoc

* Fix AbstractQuery::setParameter phpdoc
2022-02-12 21:49:30 +01:00
Dmytro Hordinskyi
530f515556 Added "false" value to $columnPrefix type declaration. (#9493) 2022-02-09 09:40:16 +01:00
Alexander M. Turek
1c55025b12 PHPStan 1.4.6, Psalm 4.20.0 (#9491) 2022-02-09 00:42:34 +01:00
Steve
0900d4bc97 Fix #[DiscriminatorMap] params (#9487)
Fix `#[DiscriminatorMap]` params
2022-02-08 08:35:46 +00:00
Alexander M. Turek
be2518d784 Run tests with stricter error handling (#9482) 2022-02-07 22:08:34 +00:00
Alexander M. Turek
bdd8883d12 Run Postgres 14 and MariaDB 10.6 in CI (#9470) 2022-02-05 19:28:13 +01:00
Alexander M. Turek
536b65f02b PDO is not a required extension (#9457) 2022-02-01 14:13:50 +01:00
Alexander M. Turek
103c42cdb7 Check requirements for metadata drivers (#9452) 2022-02-01 13:48:03 +01:00
Grégoire Paris
cdaf7b5308 Remove trailing underscore (#9446)
It looks like there was confusion between the syntax for external links
and the syntax for internal links, which does not mention underscores.
See https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-doc
versus https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#external-links
2022-01-30 23:01:35 +00:00
Claudio Zizza
4b88ce787d Introduce __unserialize behaviour in docs (#9390)
* Introduce __unserialize behaviour in docs

* Mention deprecation of Serializable interface

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

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

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

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

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

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

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

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

* Tutorial: Bump DBAL, YAML and Cache

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

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

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

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

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

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

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2022-01-16 00:06:59 +01:00
Andrii Dembitskyi
c0a1404e4c Add detach as of list cascade-all operations (#9357) 2022-01-12 22:33:11 +01:00
Alexander M. Turek
bfed8cb6ed Update branch metadata for 2.11 (#9364) 2022-01-12 14:20:33 +01:00
Alexander M. Turek
09a2648f7e Fix doc blocks on ID generators (#9368) 2022-01-12 12:10:23 +01:00
Alexander M. Turek
ee591195cf Use EntityManagerInterface in type declarations (#9325) 2022-01-12 11:00:07 +01:00
Alexander M. Turek
e974313523 Merge branch '2.10.x' into 2.11.x
* 2.10.x:
  Add errors caused by the lexer update to the baselines (#9360)
2022-01-12 10:11:19 +01:00
Alexander M. Turek
1e972b6e0e Add errors caused by the lexer update to the baselines (#9360) 2022-01-12 10:06:40 +01:00
Christian Mehldau
e369cb6e73 Generated/Virtual Columns: Insertable / Updateable (#9118)
* Generated/Virtual Columns: Insertable / Updateable

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

Closes #5728

* Apply suggestions from code review

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

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

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

* Rephrase comment in WhereInWalker as suggested from reviewer

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

* Rephrase comment in WhereInWalker as suggested from reviewer

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

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

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

* Update UPGRADE.md, fix wrong parameter

* Copy docblock to appease confused Psalm

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

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

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

* Synchronize Psalm baseline (#9296)

* Fix return type (#9295)

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

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

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

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

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

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

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

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

* Allow symfony/cache 6 (#9283)

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

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

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

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

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

Co-authored-by: Malte Wunsch <mw@webfactory.de>
2021-12-22 00:44:25 +01:00
Alexander M. Turek
1e9973a0c0 Merge release 2.10.4 into 2.11.x (#9280) 2021-12-21 11:01:59 +01:00
Alexander M. Turek
91761738fd Fix docblocks on nullable EM properties (#9273) 2021-12-20 22:31:57 +01:00
Alexander M. Turek
95d434d003 Merge 2.10.x into 2.11.x (#9276)
* Docs: consistency for FQCN, spacing, etc (#9232)

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

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

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

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

* Add missed FQCN for code snippets

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

This reverts commit bbb68d0072.

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

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

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

* Improve compatibility with Connection::quote() in DBAL 4
2021-12-19 13:19:30 +01:00
Benjamin Eberlei
6d306c1946 Support for nesting attributes with PHP 8.1 (#9241)
* [GH-9240] Refactor Association/AttributeOverrides to use @NamedConstructorArguments with AnnotationDriver.

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

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

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

* Apply review comments by gregooire.

* Add documentation for new attributes.

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

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

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

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

* Hosuekeeping: phpcs/psalm

* housekeeping

* Remove unused function imports.
2021-12-18 11:03:12 +01:00
Sergei Morozov
bea5e7166c Address more DBAL 3.2 deprecations (#9256)
* Instantiate comparator via the schema manager, if possible

* Do not use AbstractPlatform::getName()
2021-12-16 23:18:18 +01:00
Alexander M. Turek
003090b70c Deprecate Setup::registerAutoloadDirectory() (#9249) 2021-12-13 23:36:10 +01:00
Alexander M. Turek
56e0ac02af Merge 2.10.x into 2.11.x (#9248) 2021-12-13 22:21:00 +01:00
Simon Podlipsky
42195060e6 Add SchemaIgnoreClasses property for #8195. (#9202)
Co-authored-by: Simon Podlipsky <simon@podlipsky.net>

Co-authored-by: Iab Foulds <ianfoulds@x-act.co.uk>
2021-12-12 13:42:07 +01:00
Alexander M. Turek
ac5aea1c81 Merge release 2.10.3 into 2.11.x (#9224) 2021-12-03 22:40:00 +01:00
Alexander M. Turek
a75605b8c3 Merge pull request #9211 from derrabus/deprecate/convert-mapping
Add deprecation hints to `orm:convert-mapping` command
2021-12-03 15:50:09 +01:00
Grégoire Paris
66c95a65c5 Drop unneeded backslashes 2021-12-01 21:53:36 +01:00
Bruce
62a0d7359b Fix Hidden fields triggering error when using getSingleScalarResult() (#8340)
* Fix Hidden fields triggering error when using getSingleScalarResult()

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

* Fix Coding Standards (7.4)

* Fix Coding Standards (7.4) #2

* Fix Coding Standards (7.4) - Fix whitespaces

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

* Fix Coding Standards (7.4) - Fix more things

* Refactor tests into separate methods

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

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

Co-authored-by: Joe Mizzi <themizzi@me.com>
2021-12-01 21:52:29 +01:00
Alexander M. Turek
eabb7f84e9 Add deprecation hints to orm:convert-mapping command 2021-11-30 23:04:44 +01:00
Alexander M. Turek
f0a20dbc9c Merge 2.10.x into 2.11.x (#9213) 2021-11-30 22:37:08 +01:00
Alexander M. Turek
6857a2e8d4 Add missing deprecations for YAML metadata mapping (#9206) 2021-11-29 16:46:05 +01:00
Alexander M. Turek
5e8b34ae30 Merge pull request #9203 from derrabus/bump/dbal-3.2
Drop support for DBAL 3.1
2021-11-29 16:45:30 +01:00
Alexander M. Turek
a9b682b7c0 Drop support for DBAL 3.1 2021-11-29 10:37:05 +01:00
Alexander M. Turek
0aadc456dc Merge 2.10.x into 2.11.x (#9205)
* Adding Attributes code block (#9161)

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

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

* Add a psalm type for field mapping

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

* Psalm 4.13.1, PHPStan 1.2.0 (#9204)

Co-authored-by: Thomas Landauer <thomas@landauer.at>
Co-authored-by: Simon Podlipsky <simon@podlipsky.net>
Co-authored-by: Julien LARY <47776596+laryjulien@users.noreply.github.com>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2021-11-28 01:08:49 +01:00
Grégoire Paris
2488b4c50c Merge pull request #9196 from greg0ire/2.11.x
Merge 2.10.x up into 2.11.x
2021-11-20 15:38:36 +01:00
Grégoire Paris
ed642c72c9 Merge remote-tracking branch 'origin/2.10.x' into 2.11.x 2021-11-20 15:28:20 +01:00
Thomas Landauer
1e971d85c4 Merging two ~identical lists on event types (#9160)
* Merging two ~identical lists on event types

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

* Update docs/en/reference/events.rst

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

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

View File

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

View File

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

View File

@@ -30,7 +30,7 @@ jobs:
- php-version: "8.0"
dbal-version: "2.13"
- php-version: "8.1"
dbal-version: "3.2@dev"
dbal-version: "3@dev"
steps:
- name: "Checkout"
@@ -78,16 +78,16 @@ jobs:
strategy:
matrix:
php-version:
- "7.4"
- "8.1"
dbal-version:
- "default"
postgres-version:
- "9.6"
- "13"
- "14"
include:
- php-version: "8.0"
dbal-version: "2.13"
postgres-version: "13"
postgres-version: "14"
services:
postgres:
@@ -139,18 +139,18 @@ jobs:
strategy:
matrix:
php-version:
- "7.4"
- "8.1"
dbal-version:
- "default"
mariadb-version:
- "10.5"
- "10.6"
extension:
- "mysqli"
- "pdo_mysql"
include:
- php-version: "8.0"
dbal-version: "2.13"
mariadb-version: "10.5"
mariadb-version: "10.6"
extension: "pdo_mysql"
services:
@@ -205,7 +205,7 @@ jobs:
strategy:
matrix:
php-version:
- "7.4"
- "8.1"
dbal-version:
- "default"
mysql-version:

View File

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

View File

@@ -18,7 +18,7 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.0"
- "8.1"
dbal-version:
- "default"
- "2.13"
@@ -58,10 +58,7 @@ jobs:
fail-fast: false
matrix:
php-version:
- "8.0"
dbal-version:
- "default"
- "2.13"
- "8.1"
steps:
- name: "Checkout code"
@@ -73,10 +70,6 @@ jobs:
coverage: "none"
php-version: "${{ matrix.php-version }}"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v1"
with:

2
.gitignore vendored
View File

@@ -15,5 +15,5 @@ vendor/
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock
/.phpunit.result.cache
.phpunit.result.cache
/*.phpunit.xml

View File

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

View File

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

View File

@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../vendor/phpunit/phpunit/phpunit.xsd"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="mysqli"/>
<var name="db_host" value="127.0.0.1" />
<var name="db_port" value="3306"/>

View File

@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../vendor/phpunit/phpunit/phpunit.xsd"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pdo_mysql"/>
<var name="db_host" value="127.0.0.1" />
<var name="db_port" value="3306"/>

View File

@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../vendor/phpunit/phpunit/phpunit.xsd"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pdo_pgsql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="postgres" />

View File

@@ -1,12 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../vendor/phpunit/phpunit/phpunit.xsd"
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="pdo_sqlite"/>
<var name="db_memory" value="true"/>

View File

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

View File

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

View File

@@ -195,10 +195,7 @@ SQL Logger (***Optional***)
Gets or sets the logger to use for logging all SQL statements
executed by Doctrine. The logger class must implement the
``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default
implementation that logs to the standard output using ``echo`` and
``var_dump`` can be found at
``Doctrine\DBAL\Logging\EchoSQLLogger``.
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
Auto-generating Proxy Classes (***OPTIONAL***)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -440,6 +437,19 @@ That will be available for all entities without a custom repository class.
The default value is ``Doctrine\ORM\EntityRepository``.
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
Ignoring entities (***OPTIONAL***)
-----------------------------------
Specifies the Entity FQCNs to ignore.
SchemaTool will then skip these (e.g. when comparing schemas).
.. code-block:: php
<?php
$config->setSchemaIgnoreClasses([$fqcn]);
$config->getSchemaIgnoreClasses();
Setting up the Console
----------------------

View File

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

View File

@@ -82,9 +82,13 @@ be any regular PHP class observing the following restrictions:
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
- An entity class must not implement ``__wakeup`` or
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
Also consider implementing
`Serializable <https://php.net/manual/en/class.serializable.php>`_
instead.
You can also consider implementing
`Serializable <https://php.net/manual/en/class.serializable.php>`_,
but be aware that it is deprecated since PHP 8.1. We do not recommend its usage.
- PHP 7.4 introduces :doc:`the new magic method <https://php.net/manual/en/language.oop5.magic.php#object.unserialize>`
``__unserialize``, which changes the execution priority between
``__wakeup`` and itself when used. This can cause unexpected behaviour in
an Entity.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
@@ -93,6 +97,7 @@ be any regular PHP class observing the following restrictions:
- An entity cannot make use of func_get_args() to implement variable parameters.
Generated proxies do not support this for performance reasons and your code might
actually fail to work when violating this restriction.
- Entity cannot access private/protected properties/methods of another entity of the same class or :doc:`do so safely <../cookbook/accessing-private-properties-of-the-same-class-from-different-instance>`.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
@@ -161,7 +166,8 @@ possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us).
did not find a way yet, if you did, please contact us). The
``Serializable`` interface is also deprecated beginning with PHP 8.1.
The EntityManager
~~~~~~~~~~~~~~~~~

View File

@@ -10,6 +10,8 @@ annotation metadata supported since the first version 2.0.
Index
-----
- :ref:`#[AssociationOverride] <attrref_associationoverride]`
- :ref:`#[AttributeOverride] <attrref_attributeoverride]`
- :ref:`#[Column] <attrref_column>`
- :ref:`#[Cache] <attrref_cache>`
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
@@ -49,6 +51,93 @@ Index
Reference
---------
.. _attrref_associationoverride:
#[AssociationOverride]
~~~~~~~~~~~~~~~~~~~~~~
In an inheritance hierarchy this attribute allows to override the
assocation mapping definitions of the parent mappings. It needs to be nested
within a ``#[AssociationOverrides]`` on the class level.
Required parameters:
- **name**: Name of the association mapping to overwrite.
Optional parameters:
- **joinColumns**: A list of nested ``#[JoinColumn]`` declarations.
- **joinTable**: A nested ``#[JoinTable]`` declaration in case of a many-to-many overwrite.
- **inversedBy**: The name of the inversedBy field on the target entity side.
- **fetch**: The fetch strategy, one of: EAGER, LAZY, EXTRA_LAZY.
Examples:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\AssociationOverride;
use Doctrine\ORM\Mapping\AssociationOverrides;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[AssociationOverrides([
new AssociationOverride(
name: "groups",
joinTable: new JoinTable(
name: "ddc964_users_admingroups",
),
joinColumns: [new JoinColumn(name: "adminuser_id")],
inverseJoinColumns: [new JoinColumn(name: "admingroup_id")]
),
new AssociationOverride(
name: "address",
joinColumns: [new JoinColumn(name: "adminaddress_id", referencedColumnName: "id")]
)
])]
class DDC964Admin extends DDC964User
{
}
.. _attrref_attributeoverride:
#[AttributeOverride]
~~~~~~~~~~~~~~~~~~~~
In an inheritance hierarchy this attribute allows to override the
field mapping definitions of the parent mappings. It needs to be nested
within a ``#[AttributeOverrides]`` on the class level.
Required parameters:
- **name**: Name of the association mapping to overwrite.
- **column**: A nested ``#[Column]`` attribute with the overwritten field settings.
Examples:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\AttributeOverride;
use Doctrine\ORM\Mapping\AttributeOverrides;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[Entity]
#[AttributeOverrides([
new AttributeOverride(
name: "id",
column: new Column(name: "guest_id", type: "integer", length: 140)
),
new AttributeOverride(
name: "name",
column: new Column(name: "guest_name", nullable: false, unique: true, length: 240)
)]
)]
class DDC964Guest extends DDC964User
{
}
.. _attrref_column:
#[Column]
@@ -89,6 +178,18 @@ Optional parameters:
- **nullable**: Determines if NULL values allowed for this column.
If not specified, default value is ``false``.
- **insertable**: Boolean value to determine if the column should be
included when inserting a new row into the underlying entities table.
If not specified, default value is true.
- **updatable**: Boolean value to determine if the column should be
included when updating the row of the underlying entities table.
If not specified, default value is true.
- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
used after an INSERT or UPDATE statement to determine if the database
generated this value and it needs to be fetched using a SELECT statement.
- **options**: Array of additional options:
- ``default``: The default value to set for the column if no value
@@ -159,6 +260,15 @@ Examples:
)]
protected $loginCount;
// MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
#[Column(
type: "string",
name: "user_fullname",
insertable: false,
updatable: false
)]
protected $fullname;
.. _attrref_cache:
#[Cache]
@@ -460,7 +570,7 @@ Example with partial indexes:
<?php
use Doctrine\ORM\Mapping\Index;
#[Index(name: "search_idx", columns: {"category"},
#[Index(name: "search_idx", columns: ["category"],
options: [
"where": "((category IS NOT NULL))"
]
@@ -518,7 +628,7 @@ Examples:
#[Entity]
#[InheritanceType("SINGLE_TABLE")]
#[DiscriminatorColumn(name: "discr", type: "string")]
#[DiscriminatorMap({"person" = "Person", "employee" = "Employee"})]
#[DiscriminatorMap(["person" => "Person", "employee" => "Employee"])]
class Person
{
// ...
@@ -527,7 +637,7 @@ Examples:
#[Entity]
#[InheritanceType("JOINED")]
#[DiscriminatorColumn(name: "discr", type: "string")]
#[DiscriminatorMap({"person" = "Person", "employee" = "Employee"})]
#[DiscriminatorMap(["person" => "Person", "employee" => "Employee"])]
class Person
{
// ...
@@ -546,7 +656,8 @@ are missing they will be computed considering the field's name and the current
The ``#[InverseJoinColumn]`` is the same as ``#[JoinColumn]`` and is used in the context
of a ``#[ManyToMany]`` attribute declaration to specifiy the details of the join table's
column information used for the join to the inverse entity.
column information used for the join to the inverse entity. This is only required
on PHP 8.0, where nested attributes are not yet supported.
Optional parameters:
@@ -596,7 +707,7 @@ details of the database join table. If you do not specify
using the affected table and the column names.
A notable difference to the annotation metadata support, ``#[JoinColumn]``
and ``#[InverseJoinColumn]`` are specified at the property level and are not
and ``#[InverseJoinColumn]`` can be specified at the property level and are not
nested within the ``#[JoinTable]`` attribute.
Required attribute:

View File

@@ -14,17 +14,11 @@ After working through this guide you should know:
Mapping of associations will be covered in the next chapter on
:doc:`Association Mapping <association-mapping>`.
Guide Assumptions
-----------------
You should have already :doc:`installed and configure <configuration>`
Doctrine.
Creating Classes for the Database
---------------------------------
Every PHP object that you want to save in the database using Doctrine
is called an "Entity". The term "Entity" describes objects
is called an *Entity*. The term "Entity" describes objects
that have an identity over many independent requests. This identity is
usually achieved by assigning a unique identifier to an entity.
In this tutorial the following ``Message`` PHP class will serve as the
@@ -50,11 +44,11 @@ that describes your entity.
Doctrine provides several different ways to specify object-relational
mapping metadata:
- :doc:`Docblock Annotations <annotations-reference>`
- :doc:`Attributes <attributes-reference>`
- :doc:`Docblock Annotations <annotations-reference>`
- :doc:`XML <xml-mapping>`
- :doc:`YAML <yaml-mapping>`
- :doc:`PHP code <php-mapping>`
- :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.
@@ -62,8 +56,8 @@ many examples also show the equivalent configuration in YAML and XML.
.. note::
All metadata drivers perform equally. Once the metadata of a class has been
read from the source (annotations, xml or yaml) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are
read from the source (attributes, annotations, XML, etc.) it is stored in an instance
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class which are
stored in the metadata cache. If you're not using a metadata cache (not
recommended!) then the XML driver is the fastest.
@@ -71,9 +65,22 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Mapping\Entity;
#[Entity]
class Message
{
// ...
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Mapping\Entity;
/** @Entity */
class Message
{
@@ -100,9 +107,25 @@ You can change this by configuring information about the table:
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
#[Entity]
#[Table(name: 'message')]
class Message
{
// ...
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
/**
* @Entity
* @Table(name="message")
@@ -132,19 +155,38 @@ Now the class ``Message`` will be saved and fetched from the table ``message``.
Property Mapping
----------------
The next step after marking a PHP class as an entity is mapping its properties
to columns in a table.
The next step is mapping its properties to columns in the table.
To configure a property use the ``@Column`` docblock annotation. The ``type``
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.
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
use Doctrine\ORM\Mapping\Column;
use Doctrine\DBAL\Types\Types;
#[Entity]
class Message
{
#[Column(type: Types::INTEGER)]
private $id;
#[Column(length: 140)]
private $text;
#[Column(name: 'posted_at', type: Types::DATETIME)]
private $postedAt;
}
.. code-block:: annotation
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Column;
/** @Entity */
class Message
{
@@ -180,36 +222,34 @@ default.
column: posted_at
When we don't explicitly specify a column name via the ``name`` option, Doctrine
assumes the field name is also the column name. This means that:
assumes the field name is also the column name. So in this example:
* the ``id`` property will map to the column ``id`` using the type ``integer``;
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
The Column annotation has some more attributes. Here is a complete
list:
Here is a complete list of ``Column``s attributes (all optional):
- ``type``: (optional, defaults to 'string') The mapping type to
use for the column.
- ``name``: (optional, defaults to field name) The name of the
column in the database.
- ``length``: (optional, default 255) The length of the column in
the database. (Applies only if a string-valued column is used).
- ``unique``: (optional, default FALSE) Whether the column is a
unique key.
- ``nullable``: (optional, default FALSE) Whether the database
column is nullable.
- ``precision``: (optional, default 0) The precision for a decimal
(exact numeric) column (applies only for decimal column),
- ``type`` (default: 'string'): The mapping type to use for the column.
- ``name`` (default: name of property): The name of the column in the database.
- ``length`` (default: 255): The length of the column in the database.
Applies only if a string-valued column is used.
- ``unique`` (default: ``false``): Whether the column is a unique key.
- ``nullable`` (default: ``false``): Whether the column is nullable.
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
which is the maximum number of digits that are stored for the values.
- ``scale``: (optional, default 0) The scale for a decimal (exact
- ``scale`` (default: 0): The scale for a decimal (exact
numeric) column (applies only for decimal column), which represents
the number of digits to the right of the decimal point and must
not be greater than *precision*.
- ``columnDefinition``: (optional) Allows to define a custom
not be greater than ``precision``.
- ``columnDefinition``: Allows to define a custom
DDL snippet that is used to create the column. Warning: This normally
confuses the SchemaTool to always detect the column as changed.
- ``options``: (optional) Key-value pairs of options that get passed
confuses the :doc:`SchemaTool <tools>` to always detect the column as changed.
- ``options``: Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
.. _reference-php-mapping-types:
@@ -217,21 +257,35 @@ list:
PHP Types Mapping
_________________
Since version 2.9 Doctrine can determine usable defaults from property types
on entity classes. When property type is nullable this has no effect on
``nullable`` Column attribute at the moment for backwards compatibility
reasons.
.. versionadded:: 2.9
Additionally, Doctrine will map PHP types to ``type`` attribute as follows:
The column types can be inferred automatically from PHP's property types.
However, when the property type is nullable this has no effect on the ``nullable`` Column attribute.
- ``DateInterval``: ``dateinterval``
- ``DateTime``: ``datetime``
- ``DateTimeImmutable``: ``datetime_immutable``
- ``array``: ``json``
- ``bool``: ``boolean``
- ``float``: ``float``
- ``int``: ``integer``
- ``string`` or any other type: ``string``
These are the "automatic" mapping rules:
+-----------------------+-------------------------------+
| PHP property type | Doctrine column type |
+=======================+===============================+
| ``DateInterval`` | ``Types::DATEINTERVAL`` |
+-----------------------+-------------------------------+
| ``DateTime`` | ``Types::DATETIME_MUTABLE`` |
+-----------------------+-------------------------------+
| ``DateTimeImmutable`` | ``Types::DATETIME_IMMUTABLE`` |
+-----------------------+-------------------------------+
| ``array`` | ``Types::JSON`` |
+-----------------------+-------------------------------+
| ``bool`` | ``Types::BOOLEAN`` |
+-----------------------+-------------------------------+
| ``float`` | ``Types::FLOAT`` |
+-----------------------+-------------------------------+
| ``int`` | ``Types::INTEGER`` |
+-----------------------+-------------------------------+
| Any other type | ``Types::STRING`` |
+-----------------------+-------------------------------+
As of version 2.11 Doctrine can also automatically map typed properties using a
PHP 8.1 enum to set the right ``type`` and ``enumType``.
.. _reference-mapping-types:

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ as you want, or just pick a preferred one.
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
You should still use plain DQL when you can, as it is simpler and more readable.
More about this in the :doc:`FAQ <faq>`_.
More about this in the :doc:`FAQ <faq>`.
Constructing a new QueryBuilder object
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -172,7 +172,7 @@ To enable the second-level-cache, you should provide a cache factory.
<?php
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig */
/** @var \Doctrine\Common\Cache\Cache $cache */
/** @var \Psr\Cache\CacheItemPoolInterface $cache */
/** @var \Doctrine\ORM\Configuration $config */
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($cacheConfig, $cache);

View File

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

View File

@@ -256,6 +256,11 @@ Optional attributes:
table? Defaults to false.
- nullable - Should this field allow NULL as a value? Defaults to
false.
- insertable - Should this field be inserted? Defaults to true.
- updatable - Should this field be updated? Defaults to true.
- generated - Enum of the values ALWAYS, INSERT, NEVER that determines if
generated value must be fetched from database after INSERT or UPDATE.
Defaults to "NEVER".
- version - Should this field be used for optimistic locking? Only
works on fields with type integer or datetime.
- scale - Scale of a decimal type.
@@ -689,6 +694,7 @@ specified by their respective tags:
- ``<cascade-merge />``
- ``<cascade-remove />``
- ``<cascade-refresh />``
- ``<cascade-detach />``
Join Column Element
~~~~~~~~~~~~~~~~~~~

View File

@@ -81,10 +81,11 @@ that directory with the following contents:
{
"require": {
"doctrine/orm": "^2.10.2",
"doctrine/dbal": "^3.1.1",
"symfony/yaml": "2.*",
"symfony/cache": "^5.3"
"doctrine/orm": "^2.11.0",
"doctrine/dbal": "^3.2",
"doctrine/annotations": "1.13.2",
"symfony/yaml": "^5.4",
"symfony/cache": "^5.4"
},
"autoload": {
"psr-0": {"": "src/"}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ use Doctrine\ORM\Exception\NamedNativeQueryNotFound;
use Doctrine\ORM\Exception\NamedQueryNotFound;
use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating;
use Doctrine\ORM\Exception\UnknownEntityNamespace;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
@@ -38,11 +39,13 @@ use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
use Doctrine\Persistence\ObjectRepository;
use LogicException;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;
use function class_exists;
use function method_exists;
use function sprintf;
use function strtolower;
use function trim;
@@ -161,6 +164,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
{
if (! class_exists(AnnotationReader::class)) {
throw new LogicException(sprintf(
'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) {
@@ -256,7 +267,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function getResultCache(): ?CacheItemPoolInterface
{
// Compatibility with DBAL < 3.2
// Compatibility with DBAL 2
if (! method_exists(parent::class, 'getResultCache')) {
$cacheImpl = $this->getResultCacheImpl();
@@ -271,7 +282,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function setResultCache(CacheItemPoolInterface $cache): void
{
// Compatibility with DBAL < 3.2
// Compatibility with DBAL 2
if (! method_exists(parent::class, 'setResultCache')) {
$this->setResultCacheImpl(DoctrineProvider::wrap($cache));
@@ -510,6 +521,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
* Ensures that this Configuration instance contains settings that are
* suitable for a production environment.
*
* @deprecated
*
* @return void
*
* @throws ProxyClassesAlwaysRegenerating
@@ -518,6 +531,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
*/
public function ensureProductionSettings()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/9074',
'%s is deprecated',
__METHOD__
);
$queryCacheImpl = $this->getQueryCacheImpl();
if (! $queryCacheImpl) {
@@ -703,7 +723,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the custom hydrator modes in one pass.
*
* @param array<string, class-string> $modes An array of ($modeName => $hydrator).
* @param array<string, class-string<AbstractHydrator>> $modes An array of ($modeName => $hydrator).
*
* @return void
*/
@@ -722,7 +742,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
* @param string $modeName The hydration mode name.
*
* @return string|null The hydrator class name.
* @psalm-return ?class-string
* @psalm-return class-string<AbstractHydrator>|null
*/
public function getCustomHydrationMode($modeName)
{
@@ -734,7 +754,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @param string $modeName The hydration mode name.
* @param string $hydrator The hydrator class name.
* @psalm-param class-string $hydrator
* @psalm-param class-string<AbstractHydrator> $hydrator
*
* @return void
*/
@@ -1003,4 +1023,24 @@ class Configuration extends \Doctrine\DBAL\Configuration
{
$this->_attributes['defaultQueryHints'][$name] = $value;
}
/**
* Gets a list of entity class names to be ignored by the SchemaTool
*
* @return list<class-string>
*/
public function getSchemaIgnoreClasses(): array
{
return $this->_attributes['schemaIgnoreClasses'] ?? [];
}
/**
* Sets a list of entity class names to be ignored by the SchemaTool
*
* @param list<class-string> $schemaIgnoreClasses List of entity class names
*/
public function setSchemaIgnoreClasses(array $schemaIgnoreClasses): void
{
$this->_attributes['schemaIgnoreClasses'] = $schemaIgnoreClasses;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Id;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Serializable;
use function serialize;
@@ -51,7 +51,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
/**
* {@inheritDoc}
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue === $this->_maxValue) {
// Allocate new values

View File

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

View File

@@ -7,7 +7,7 @@ namespace Doctrine\ORM\Id;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\NotSupported;
use function method_exists;
@@ -38,7 +38,7 @@ class UuidGenerator extends AbstractIdGenerator
*
* @throws NotSupported
*/
public function generate(EntityManager $em, $entity)
public function generateId(EntityManagerInterface $em, $entity)
{
$connection = $em->getConnection();
$sql = 'SELECT ' . $connection->getDatabasePlatform()->getGuidExpression();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,9 +59,9 @@ class ClassMetadataBuilder
/**
* Adds and embedded class
*
* @param string $fieldName
* @param string $class
* @param string|null $columnPrefix
* @param string $fieldName
* @param string $class
* @param string|false|null $columnPrefix
*
* @return $this
*/

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,13 +15,13 @@ use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
#[Attribute(Attribute::TARGET_CLASS)]
final class DiscriminatorColumn implements Annotation
{
/** @var string */
/** @var string|null */
public $name;
/** @var string */
/** @var string|null */
public $type;
/** @var int */
/** @var int|null */
public $length;
/**

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