Compare commits

...

335 Commits

Author SHA1 Message Date
Grégoire Paris
609647a51a Merge pull request #11015 from greg0ire/phase2-optim
Make phpdoc accurate
2023-10-21 19:35:18 +02:00
Grégoire Paris
293299a314 Make phpdoc accurate
When transforming these phpdoc types into native types, things break
down. They are correct according to the EBNF, but in practice, there are
so-called phase 2 optimizations that allow using ConditionalPrimary,
ConditionalFactor and ConditionalTerm instances in places where
ConditionalExpression is used.
2023-10-21 19:15:03 +02:00
Grégoire Paris
0b7fe1862e Merge pull request #11018 from stof/fix_result_set_builder_enum
Fix the support for enum types in the ResultSetMappingBuilder
2023-10-18 22:27:01 +02:00
Christophe Coevoet
866283d1a7 Fix the support for enum types in the ResultSetMappingBuilder 2023-10-18 10:04:20 +02:00
Grégoire Paris
3676e3c571 Merge pull request #11007 from greg0ire/refresh-archi-docs
Address split of doctrine/common
2023-10-17 21:41:52 +02:00
Grégoire Paris
d84f607487 Address split of doctrine/common
doctrine/common has been split in several packages. A lot of what was
true about doctrine/common is true about doctrine/persistence today, so
let us simply reuse the existing paragraphs and mention persistence
instead of common.
2023-10-17 20:02:34 +02:00
Alexander M. Turek
42af7cabb7 Cover calling AbstractQuery::setParameter() with an array parameter (#10996) 2023-10-11 16:04:47 +02:00
Serhii Petrov
32192c7b01 Test against php 8.3 (#10963) 2023-10-09 12:43:52 +02:00
Grégoire Paris
77843e45f3 Merge pull request #10972 from salehhashemi1992/ci/update-checkout-to-v4
update actions/checkout to v4
2023-10-08 18:44:13 +02:00
salehhashemi1992
1919eea0a9 update checkout version to version 4 2023-10-08 11:04:22 +03:30
Alexander M. Turek
62ed63bbbe PHPStan 1.10.35, Psalm 5.15.0 (#10958) 2023-09-29 08:56:45 +02:00
Danny van Kooten
081ec2ad26 docs: in text, refer to attributes when talking about metadata (#10956)
Co-authored-by: Danny van Kooten <dannyvankooten@users.noreply.github.com>
2023-09-28 21:49:15 +02:00
Adrien Crivelli
e9537f4cde Fix bullet list layout (#10951) 2023-09-19 21:01:37 +02:00
Marko Kaznovac
38ad3925e2 docs[query-builder]: fix rendering of Doctrine\DBAL\ParameterType::* (#10945) 2023-09-15 00:17:48 +02:00
Marko Kaznovac
858b01f85e tests[ORMSetupTest]: testCacheNamespaceShouldBeGeneratedForApcu requires enabled apc (#10940) 2023-09-10 22:57:36 +02:00
Grégoire Paris
9f555ea8fb Merge pull request #10933 from kaznovac/patch-2
docs: use modern named arguments syntax
2023-09-08 22:02:11 +02:00
Marko Kaznovac
1f8c02f345 docs: use modern named arguments syntax
use official named arguments syntax in example instead of pre php 8 codestyle for 'named' arguments
2023-09-08 13:47:11 +02:00
Grégoire Paris
d81afdb6e3 Merge pull request #10930 from greg0ire/improve-doc-job
Improve doc job
2023-09-02 23:15:56 +02:00
Grégoire Paris
0628204b43 Ignore "Unknown directive" error
We have a lot of errors about "Unknown directive" that we should make
known when implementing guides for Doctrine, but cannot address by
modifying the docs.

The unknown directives are:

- configuration-block
- toc
- tocheader
- sectionauthor
2023-09-02 19:29:05 +02:00
Grégoire Paris
816ecc6d6b Use a stable release
0.1.0 has been published 3 weeks ago. This means we no longer need to
  use dev stability
2023-09-02 19:02:59 +02:00
Grégoire Paris
f66263d859 Remove output directory argument
It is no actually necessary at all.
2023-09-02 19:01:46 +02:00
Grégoire Paris
8aa5aa2f57 Merge pull request #10929 from kaznovac/patch-1
tutorials[getting-started]: example fix bug id type definition
2023-09-02 18:52:43 +02:00
Marko Kaznovac
96e31a3b30 tutorials[getting-started]: example fix bug id type definition 2023-09-02 17:48:29 +02:00
Grégoire Paris
a60a273423 Merge pull request #10808 from oscmarb/verifiy-hint-defer-eager-load-is-true
Verify UnitOfWork::HINT_DEFEREAGERLOAD exists and is true
2023-09-01 07:52:11 +02:00
Grégoire Paris
17500f56ea Merge pull request #10923 from kaznovac/patch-1
basic-mapping: fix new-line rendered in output
2023-08-27 20:21:56 +02:00
Marko Kaznovac
fc2f724e2d basic-mapping: fix new-line rendered in output 2023-08-27 19:17:04 +02:00
Óscar Martínez
7986fc64dd Verify UnitOfWork::HINT_DEFEREAGERLOAD exists and is true 2023-08-25 09:51:02 +02:00
Grégoire Paris
2f9e98754b Merge pull request #10915 from mpdude/post-events-later
Mitigate problems with `EntityManager::flush()` reentrance since 2.16.0 (Take 2)
2023-08-25 07:47:25 +02:00
Sergii Dolgushev
bb5524099c Use required classes for Lifecycle Callback examples (#10916)
* Use required classes for Lifecycle Callback examples

* Coding Style fixes

---------

Co-authored-by: Sergii Dolgushev <Sergii.Dolgushev@secondwaveds.com>
2023-08-23 22:47:15 +02:00
Grégoire Paris
3a8cafe228 Add space before backquote (#10918)
According to the RST docs,

> [inline markup] it must be separated from surrounding text by non-word
> characters. Use a backslash escaped space to work around that: thisis\ *one*\ word.

Because we were missing a space before backquotes here, the links were
not rendered. Escaping the space allow not to actually produce a space
in the output.

See https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#inline-markup
2023-08-23 21:42:35 +02:00
Matthias Pigulla
8259a16681 Mitigate problems with EntityManager::flush() reentrance since 2.16.0 (Take 2)
The changes from #10547, which landed in 2.16.0, cause problems for users calling `EntityManager::flush()` from within `postPersist` event listeners.

* When `UnitOfWork::commit()` is re-entered, the "inner" (reentrant) call will start working through all changesets. Eventually, it finishes with all insertions being performed and `UoW::$entityInsertions` being empty. After return, the entity insertion order, an array computed at the beginning of `UnitOfWork::executeInserts()`, still contains entities that now have been processed already. This leads to a strange-looking SQL error where the number of parameters used does not match the number of parameters bound. This has been reported as #10869.

* The fixes made to the commit order computation may lead to a different entity insertion order than previously. `postPersist` listener code may be affected by this when accessing generated IDs for other entities than the one the event has been dispatched for. This ID may not yet be available when the insertion order is different from the one that was used before 2.16. This has been mentioned in https://github.com/doctrine/orm/pull/10906#issuecomment-1682417987.

This PR suggests to address both issues by dispatching the `postPersist` event only after _all_ new entities have their rows inserted into the database. Likewise, dispatch `postRemove` only after _all_ deletions have been executed.

This solves the first issue because the sequence of insertions or deletions has been processed completely _before_ we start calling event listeners. This way, potential changes made by listeners will no longer be relevant.

Regarding the second issue, I think deferring `postPersist` a bit until _all_ entities have been inserted does not violate any promises given, hence is not a BC break. In 2.15, this event was raised after all insertions _for a particular class_ had been processed - so, it was never an "immediate" event for every single entity. #10547 moved the event handling to directly after every single insertion. Now, this PR moves it back a bit to after _all_ insertions.
2023-08-23 07:55:21 +02:00
David Arenas
5577d51c44 Add back throws annotation to getSingleScalarResult (#10907)
Fix regression introduced in #10870

`$result = $this->execute(null, $hydrationMode);` in `getSingleResult` can still throw NoResultException exception.
2023-08-13 13:01:30 +02:00
Eduardo Rocha
d1922a3065 Fix link on known issues docs (#10904) 2023-08-10 21:41:31 +02:00
Alexander M. Turek
597a63a86c PHPStan 1.10.28, Psalm 5.14.1 (#10895) 2023-08-09 15:05:08 +02:00
Bei Xiao
6b220e3c90 Fix return type of getSingleScalarResult (#10870) 2023-08-09 11:42:00 +02:00
Matthias Pigulla
6de4b68705 Use a dedicated exception for the check added in #10785 (#10881)
This adds a dedicated exception for the case that objects with colliding identities are to be put into the identity map.

Implements #10872.
2023-08-09 11:38:35 +02:00
Matthias Pigulla
16c0151831 Document more clearly that the insert order is an implementation detail (#10883) 2023-08-09 11:36:05 +02:00
Matthias Pigulla
440b244ebc Fix broken changeset computation for entities loaded through fetch=EAGER + using inheritance (#10884)
#10880 reports a case where the changes from #10785 cause entity updates to be missed.

Upon closer inspection, this change seems to be causing it:

https://github.com/doctrine/orm/pull/10785/files#diff-55a900494fc8033ab498c53929716caf0aa39d6bdd7058e7d256787a24412ee4L2990-L3003

The code was changed to use `registerManaged()` instead, which basically does the same things, but (since #10785) also includes an additional check against duplicate entity instances.

But, one detail slipped through tests and reviews: `registerManaged()` also updates `\Doctrine\ORM\UnitOfWork::$originalEntityData`, which is used to compute entity changesets. An empty array `[]` was passed for $data here.

This will make the changeset computation assume that a partial object was loaded and effectively ignore all field updates here:

a616914887/lib/Doctrine/ORM/UnitOfWork.php (L762-L764)

I think that, effectively, it is sufficient to call `registerManaged()` only in the two cases where a proxy was created.

Calling `registerManaged()` with `[]` as data for a proxy object is consistent with e. g. `\Doctrine\ORM\EntityManager::getReference()`.

In the case that a full entity has to be loaded, we need not call `registerManaged()` at all, since that will already happen inside `EntityManager::find()` (or, more specifically, `UnitOfWork::createEntity()` called inside it).

Note that the test case has to make some provisions so that we actually reach this case:
* Load an entity that uses `fetch="EAGER"` on a to-one association
* That association being against a class that uses inheritance (why's that?)
2023-08-09 11:34:53 +02:00
Matthias Pigulla
a616914887 Turn identity map collisions from exception to deprecation notice (#10878)
In #10785, a check was added that prevents entity instances from getting into the identity map when another object for the same ID is already being tracked.

This caused regressions for users that work with application-provided IDs and expect this condition to fail with `UniqueConstraintViolationExceptions` when flushing to the database.

Thus, this PR turns the exception into a deprecation notice. Users can opt-in to the new behavior. In 3.0, the exception will be used.

Implements #10871.
2023-08-04 14:06:02 +02:00
Dieter Beck
fd0bdc69b0 Add possibility to set reportFieldsWhereDeclared to true in ORMSetup (#10865)
Otherwise it is impossible to avoid a deprecation warning when using ORMSetup::createAttributeMetadataConfiguration()
2023-08-02 14:34:13 +02:00
Michael Olšavský
f50803ccb9 Fix UnitOfWork->originalEntityData is missing not-modified collections after computeChangeSet (#9301)
* Fix original data incomplete after flush

* Apply suggestions from code review

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

---------

Co-authored-by: Alexander M. Turek <me@derrabus.de>
2023-08-02 13:44:15 +02:00
Matthias Pigulla
eeefc6bc0f Add an UPGRADE notice about the potential changes in commit order (#10866) 2023-08-02 13:42:49 +02:00
Grégoire Paris
710dde83aa Update branch metadata (#10862) 2023-08-01 14:56:34 +02:00
Grégoire Paris
495cd06b9a Merge pull request #10727 from derrabus/revert/symfony-7
Revert "Allow symfony/console 7"
2023-08-01 14:07:04 +02:00
Grégoire Paris
fd7a14ad22 Merge pull request #10547 from mpdude/commit-order-entity-level
Compute the commit order (inserts/deletes) on the entity level
2023-08-01 09:21:09 +02:00
Matthias Pigulla
0d3ce5d4f8 Exclude deprecated classes from Psalm checks (until 3.0) 2023-08-01 08:53:56 +02:00
Matthias Pigulla
f01d107edc Fix commit order computation for self-referencing entities with application-provided IDs
This excludes such associations from the commit order computation, since the foreign key constraint will be satisfied when inserting the row.

See https://github.com/doctrine/orm/pull/10735/ for more details about this edge case.
2023-08-01 08:43:16 +02:00
Alexander M. Turek
3cc30c4024 Merge branch '2.15.x' into 2.16.x
* 2.15.x:
  Fix static analysis
  Other solution
  Avoid self deprecation
  fix: use platform options instead of deprecated custom options (#10855)
2023-07-28 16:26:45 +02:00
Alexander M. Turek
d2de4ec03c Revert "Introduce FilterCollection#restore method (#10537)"
This reverts commit 8e20e1598e.
2023-07-28 16:08:17 +02:00
Vincent Langlet
64ee76e94e Introduce FilterCollection#restore method (#10537)
* Introduce FilterCollection#restore method

* Add suspend method instead

* Add more tests
2023-07-28 16:08:06 +02:00
Vincent Langlet
8e20e1598e Introduce FilterCollection#restore method (#10537)
* Introduce FilterCollection#restore method

* Add suspend method instead

* Add more tests
2023-07-28 16:05:51 +02:00
Grégoire Paris
24df74d61d Merge pull request #10856 from VincentLanglet/fixDeprecation
Fix/Self deprecation with getQueryCacheImpl
2023-07-26 23:41:39 +02:00
Vincent Langlet
442f073d25 Fix static analysis 2023-07-26 17:42:20 +02:00
Vincent Langlet
a5161e9485 Other solution 2023-07-26 16:00:37 +02:00
Vincent Langlet
ddc7d953b9 Avoid self deprecation 2023-07-26 12:37:42 +02:00
Kévin Dunglas
db51ed4f4c fix: use platform options instead of deprecated custom options (#10855) 2023-07-25 16:23:46 +02:00
Alexander M. Turek
6d27797b2e Merge release 2.15.4 into 2.16.x (#10850) 2023-07-23 23:49:30 +02:00
Nicolas Grekas
89250b8ca2 Use properties instead of getters to read property/class names via reflection (#10848) 2023-07-20 20:37:52 +02:00
Grégoire Paris
f7e4b61459 Merge pull request #10847 from greg0ire/remove-toc 2023-07-18 09:50:04 +02:00
Grégoire Paris
6b0afdbd58 Avoid triple colons
It confuses the guides, and is ugly.
2023-07-18 08:57:09 +02:00
Grégoire Paris
d3cf17b26d Remove toc
We already have the sidebar for this.
2023-07-18 08:56:24 +02:00
Alexander M. Turek
bc61d7d21e Merge branch '2.15.x' into 2.16.x
* 2.15.x:
  PHPStan 1.10.25, Psalm 5.13.1 (#10842)
2023-07-16 23:40:09 +02:00
Alexander M. Turek
5213228a64 PHPStan 1.10.25, Psalm 5.13.1 (#10842) 2023-07-16 23:38:29 +02:00
Nicolas Grekas
dca7ddf969 Decouple public API from Doctrine\Persistence\Proxy (#10832) 2023-07-15 10:55:35 +02:00
Grégoire Paris
e781639812 Merge pull request #10841 from doctrine/2.15.x 2023-07-12 15:54:17 +02:00
Grégoire Paris
7848417488 Merge pull request #10838 from greg0ire/remove-dummy-title 2023-07-12 11:07:46 +02:00
Grégoire Paris
56e5856ad7 Remove dummy title
This was never meant to be under version control. Did not spot it in the
diff.

Closes #10836
2023-07-12 11:06:06 +02:00
Grégoire Paris
b5987ad29a Merge pull request #10598 from opsway/fix-generated-for-joined-inheritance
Support not Insertable/Updateable columns for entities with `JOINED` inheritance type
2023-07-11 23:48:10 +02:00
Grégoire Paris
385bdd33f1 Merge pull request #10798 from greg0ire/less-partial-load
Resort on Query::HINT_FORCE_PARTIAL_LOAD less
2023-07-11 23:46:17 +02:00
Nicolas Grekas
8c513a6523 Cleanup psalm-type AutogenerateMode (#10833) 2023-07-11 23:01:02 +02:00
Grégoire Paris
1413b496d7 Merge pull request #10824 from greg0ire/fix-build 2023-07-11 12:10:23 +02:00
Alexandr Vronskiy
3b3056f910 mistake on final property 2023-07-11 09:38:34 +03:00
Alexandr Vronskiy
fa5c37e972 Add final to protected
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-07-11 09:38:34 +03:00
Yevhen Vilkhovchenko
f3e36debfe Fix persist notInsertable|notUpdatable fields of root entity of joined inheritance type
1. Inherit ClassMetadataInfo::requiresFetchAfterChange flag from root entity when process parent columns mapping (see ClassMetadataInfo::addInheritedFieldMapping(), it uses same condition as ClassMetadataInfo::mapField()) so JoinedSubclassPersister::assignDefaultVersionAndUpsertableValues() to be called in JoinedSubclassPersister::executeInserts().
2. Override JoinedSubclassPersister::fetchVersionAndNotUpsertableValues() to fetch all parent tables (see $this->getJoinSql() call) generated columns. So make protected BasicEntityPersister::identifierFlattener stateless service (use it flattenIdentifier() method) and BasicEntityPersister::extractIdentifierTypes() (to avoid copy-paste).
3. JoinedSubclassPersister::fetchVersionAndNotUpsertableValues() doesnt check empty $columnNames because it would be an error if ClassMetadataInfo::requiresFetchAfterChange is true while no generated columns in inheritance hierarchy.
4. Initialize JoinedInheritanceRoot not-nullable string properties with insertable=false attribute to avoid attempt to insert default null data which cause error:
    PDOException: SQLSTATE[23502]: Not null violation: 7 ERROR:  null value in column "rootwritablecontent" of relation "joined_inheritance_root" violates not-null constraint
    DETAIL:  Failing row contains (5, null, dbDefault, dbDefault, , nonUpdatable).
  while $rootTableStmt->executeStatement() because JoinedSubclassPersister::getInsertColumnList() have no $insertData (prepared later) to decide is generated column provided by client code or not (so need to skip column)
2023-07-11 09:38:34 +03:00
Yevhen Vilkhovchenko
ca7abd04a2 Fix persist notInsertable|notUpdatable columns of entity with joined inheritance type
1. Postgres gives error when insert root entity ($rootTableStmt->executeStatement()) in JoinedSubclassPersister::executeInserts():
   PDOException: SQLSTATE[08P01]: <<Unknown error>>: 7 ERROR:  bind message supplies 4 parameters, but prepared statement "" requires 6
  so exclude notInsertable columns from JoinedSubclassPersister::getInsertColumnList() like it done in parent::prepareInsertData() which call BasicEntityPersister::prepareUpdateData(isInsert: true) where we have condition:
    if ($isInsert && isset($fieldMapping['notInsertable']))
2. Try to get generated (notInsertable|notUpdatable) column value on flush() with JoinedSubclassPersister::executeInserts() also fails:
    Unexpected empty result for database query.
  because method it calls $this->assignDefaultVersionAndUpsertableValues() after insert root entity row, while generated columns in child-entity table, so move call just after insert child row
3. Use option['default'] = 'dbDefault' in functional test entities, to emulate generated value on insert, but declare as generated = 'ALWAYS' for tests purpose (correctness of JoinedSubclassPersister::fetchVersionAndNotUpsertableValues() sql-query)
4. Use JoinedInheritanceRoot::rootField to skip JoinedSubclassPersister::update() optimization for empty changeset in updatable:false columns tests
2023-07-11 09:38:34 +03:00
Matthias Pigulla
a555626150 Improve and add test to set to-one and to-many associations with reference objects (#10799) 2023-07-11 00:07:02 +02:00
Grégoire Paris
ec7a8a7a0f Merge pull request #10828 from greg0ire/matching-xml
Match namespace in XML file with namespace in XSD file
2023-07-09 23:02:43 +02:00
Grégoire Paris
e94fa8588d Match namespace in XML file with namespace in XSD file
In 7fa3e6ec7c, a global search and replace
was used for http and https.
This broke the documentation examples in that as soon as you turn on XSD
validation, it will fail because the namespace in the XML file does not
match the ones defined in the XSD file, which do not exhibit the https.

Note that this is not a security concern, because these URIs are
not meant to be actually resolved, but to serve as a unique identifier
for the namespace in which we define our elements.
2023-07-09 21:18:57 +02:00
Matthias Pigulla
f26946b477 Add @deprecated annotations in addition to runtime deprecation notices 2023-07-08 16:22:55 +02:00
Matthias Pigulla
efc83bce8e Make it possible to have non-NULLable self-referencing associations when using application-provided IDs (#10735)
This change improves scheduling of extra updates in the `BasicEntityPersister`.

Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).

As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.

I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.

One caveat, though:

In the absence of entity-level commit ordering (#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.

Fixes #7877, closes #7882.

Co-authored-by: Sylvain Fabre <sylvain.fabre@assoconnect.com>
2023-07-08 15:53:54 +02:00
Grégoire Paris
450cae2caa Add dummy title to the sidebar before checking docs
The way we have our docs, the sidebar is a separate document and as
such, needs a title. Let us prepend a dummy title until the guides-cli
provides a way to ignore that particular error.
2023-07-07 13:59:27 +02:00
Grégoire Paris
81ddeb426c Merge pull request #10823 from nicolas-grekas/mergeup 2023-07-07 13:09:11 +02:00
Nicolas Grekas
42e63bf358 Merge branch 2.15.x into 2.16.x
* 2.15.x: (23 commits)
  Fix cloning entities when using lazy-ghost proxies
  Fixes recomputation of single entity change set when entity contains enum attributes. Due to the fact that originalEntityData contains enum objects and ReflectionEnumProperty::getValue() returns value of enum, comparison of values are always falsy, resulting to update columns value even though it has not changes.
  Fix code style issues
  Use absolute references
  Avoid colon followed by double colon
  Use correct syntax for references
  Fix invalid reference syntax
  Use rst syntax
  Use internal link
  Escape pipes
  Introduce new workflow to test docs
  Remove lone dash (#10812)
  Treat id field proprites same as regular field
  Move three "Ticket/"-style tests to the right namespace
  Follow recommendation about multiline type
  Fix unserialize() errors when running tests on PHP 8.3 (#10803)
  Explain `EntityManager::getReference()` peculiarities (#10800)
  Upgrade to Psalm 5.13
  test: assert `postLoad` has data first
  distinct() updates QueryBuilder state correctly
  ...
2023-07-07 12:55:31 +02:00
Grégoire Paris
4978e0e336 Merge pull request #10819 from nicolas-grekas/fix-proxy-clone 2023-07-06 16:23:53 +02:00
Nicolas Grekas
eee87c376d Fix cloning entities when using lazy-ghost proxies 2023-07-06 15:08:59 +02:00
Grégoire Paris
0b9060c728 Merge pull request #10806 from rmikalkenas/fix-enum-recomputation 2023-07-06 10:23:59 +02:00
Grégoire Paris
514f6b8c28 Merge pull request #10813 from Greg0/fix-primary-id-mapping-xml-driver 2023-07-06 09:12:53 +02:00
Rokas Mikalkėnas
c1018fe299 Fixes recomputation of single entity change set when entity contains enum attributes.
Due to the fact that originalEntityData contains enum objects and
ReflectionEnumProperty::getValue() returns value of enum,
comparison of values are always falsy, resulting to update
columns value even though it has not changes.
2023-07-05 23:57:34 +03:00
Grzegorz Kuźnik
075824f5b5 Fix code style issues 2023-07-05 21:10:48 +02:00
Grégoire Paris
d6f4834476 Merge pull request #10815 from greg0ire/test-docs 2023-07-05 10:01:37 +02:00
Grégoire Paris
9dadffe270 Use absolute references
According to the Sphinx docs, when in reference/architecture.rst, a
reference to reference/inheritance-mapping would resolve to
reference/reference/inheritance-mapping.rst, because it is relative to
the current document
2023-07-04 17:44:44 +02:00
Grégoire Paris
b6e7e6d723 Avoid colon followed by double colon
It seems to confuse the guides-cli, if it is even valid.
2023-07-04 17:31:05 +02:00
Grégoire Paris
710937d6f8 Use correct syntax for references
doc was used when it is clearly a ref, plus there was a leading
underscore preventing the resolution.
2023-07-04 17:26:04 +02:00
Grégoire Paris
5d2d6642c8 Fix invalid reference syntax
Without this change, a hash is displayed for some reason.
2023-07-04 17:14:03 +02:00
Grégoire Paris
4d56711d8c Use rst syntax
Using underscore for emphasis is not an RST thing, in rst the difference
between emphasis and strong emphasis is in the number of asterisks.

Right now these underscores are just ignored and displayed on the
website.

See https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#emphasis
2023-07-04 17:11:46 +02:00
Grégoire Paris
a157bc3fb3 Use internal link 2023-07-04 17:05:47 +02:00
Grégoire Paris
1aeab391c7 Escape pipes
Pipes can be used to define substitutions, it is part of the rst
standard. This explains why some of the links in this document are not
displayed on the website.

See https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#substitution-definitions
2023-07-04 16:48:14 +02:00
Grégoire Paris
a4ecd02349 Introduce new workflow to test docs
This allows to check compatibility with phpDocumentor/guides, but also
should allow to spot embarassing mistakes in our existing docs.
2023-07-04 16:45:18 +02:00
Matthias Pigulla
bb21865cba Deprecate classes related to old commit order computation 2023-07-04 14:30:29 +02:00
Matthias Pigulla
606da9280d Un-prefix simple generics like list<> and array<>
... as suggested in GH review.
2023-07-04 14:17:14 +02:00
Grégoire Paris
21708e43c4 Merge pull request #10785 from mpdude/guard-duplicate-identity-map-entries 2023-07-04 10:03:04 +02:00
Grégoire Paris
8c59828f6c Remove lone dash (#10812) 2023-07-03 21:46:12 +02:00
Grzegorz K
0877ecbe56 Treat id field proprites same as regular field 2023-07-03 15:15:08 +02:00
Grégoire Paris
8eb69922e6 Merge pull request #10809 from mpdude/show-trouble-delete-before-insert
Add test to show why delete-before-insert may be challenging
2023-06-30 08:08:57 +02:00
Matthias Pigulla
e9b6fd89a4 Add test to show why delete-before-insert may be challenging
There are a few requests (#5742, #5368, #5109, #6776) that ask to change the order of operations in the UnitOfWork to perform "deletes before inserts", or where such a switch appears to solve a reported problem.

I don't want to say that this is not doable. But this PR at least adds two tricky examples where INSERTs need to be done before an UPDATE can refer to new database rows; and where the UPDATE needs to happen to release foreign key references to other entities before those can be DELETEd.

So, at least as long as all operations of a certain type are to be executed in blocks, this example allows no other order of operations than the current one.
2023-06-29 14:32:08 +02:00
Matthias Pigulla
01a14327d2 Add a safeguard against multiple objects competing for the same identity map entry
While trying to understand #3037, I found that it may happen that we have more entries in `\Doctrine\ORM\UnitOfWork::$entityIdentifiers` than in `\Doctrine\ORM\UnitOfWork::$identityMap`.

The former is a mapping from `spl_object_id` values to ID hashes, the latter an array first of entity class names and then from ID hash to entity object instances.

(Basically, "ID hash" is a concatenation of all field values making up the `@Id` for a given entity.)

This means that at some point, we must have _different_ objects representing the same entity, or at least over time different objects are used for the same entity without the UoW properly updating its `::$entityIdentifiers` structure.

I don't think it makes sense to overwrite an entity in the identity map, since that means a currently `MANAGED` entity is replaced with something else.

If it makes sense at all to _replace_ an entity, that should happen through dedicated management methods to first detach the old entity before persisting, merging or otherwise adding the new one. This way we could make sure the internal structures remain consistent.
2023-06-28 22:50:17 +02:00
Grégoire Paris
4887359827 Merge pull request #10807 from mpdude/move-tests
Move three "Ticket/"-style tests to the right namespace
2023-06-28 20:54:43 +02:00
Matthias Pigulla
2df1071e7a Remove references to the temporary branch in workflow definitions 2023-06-28 17:10:20 +02:00
Matthias Pigulla
5afe9b80a8 Move three "Ticket/"-style tests to the right namespace 2023-06-28 16:39:47 +02:00
Matthias Pigulla
7fc359c2bb Avoid unnecessarily passing entity lists into executeDeletions/executeInserts 2023-06-28 14:46:22 +02:00
Matthias Pigulla
853b9f75ae Merge remote-tracking branch 'origin/2.16.x' into commit-order-entity-level 2023-06-28 14:43:54 +02:00
Grégoire Paris
584c4aeed1 Merge pull request #10804 from greg0ire/parenthesized 2023-06-28 14:11:41 +02:00
Grégoire Paris
44d2a83e09 Merge pull request #10532 from mpdude/entity-persister-must-not-batch 2023-06-28 11:45:39 +02:00
Grégoire Paris
c9c5157fda Follow recommendation about multiline type
Apparently, there is consensus about multiline types between:

- PHPStan
- Psalm
- Slevomat Coding Standard

See https://github.com/slevomat/coding-standard/issues/1586#issuecomment-1610195706

Using parenthesis is less ambiguous, it makes it clear to the parser
where the type begins and where it ends.

The change has a positive impact on the Psalm baseline, showing
that psalm-return annotation was not really understood previously.
2023-06-28 07:46:35 +02:00
Grégoire Paris
dba90c1a91 Merge pull request #10786 from vuongxuongminh/fix-attach-entity-listener-when-reset-class-metadata-factory
Fix attach entity listener when reset class metadata factory
2023-06-27 23:53:01 +02:00
Grégoire Paris
5f079c2061 Merge pull request #10531 from mpdude/commit-order-must-be-entity-level
Add test: Commit order calculation must happen on the entity level
2023-06-27 23:52:33 +02:00
Nicolas Grekas
55d477dc50 Fix unserialize() errors when running tests on PHP 8.3 (#10803) 2023-06-27 17:47:09 +02:00
Grégoire Paris
4da8d3be96 Resort on Query::HINT_FORCE_PARTIAL_LOAD less
A lot of our tests mention it, but I do not think it is important to the
test. Maybe it was a way to have more efficient tests? Most times,
removing the hint does not affect the test outcome.
2023-06-27 08:37:05 +02:00
Matthias Pigulla
4aadba65ce Explain EntityManager::getReference() peculiarities (#10800)
* Explain `EntityManager::getReference()` peculiarities

As one takeaway from https://github.com/doctrine/orm/issues/3037#issuecomment-1605657003 and #843, we should look into better explaining the `EntityManager::getReference()` method, it’s semantics, caveats and potential responsibilities placed on the user.

This PR tries to do that, so it fixes #10797.

* Update docs/en/reference/advanced-configuration.rst

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

---------

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-06-27 00:16:55 +02:00
Grégoire Paris
814d8d4d39 Merge pull request #10802 from greg0ire/psalm-5-13
Upgrade to Psalm 5.13
2023-06-27 00:05:00 +02:00
Grégoire Paris
dc411954ad Upgrade to Psalm 5.13
This is a nice release as far as the ORM is concerned:
- a small baseline reduction;
- lots of useless calls to sprintf spotted.
2023-06-26 19:12:02 +02:00
Minh Vuong
da29eb675c test: assert postLoad has data first 2023-06-26 09:29:00 +07:00
Grégoire Paris
b17e52ba6b Merge pull request #10791 from mpdude/understand-3037
Avoid creating unmanaged proxy instances for referred-to entities during `merge()`
2023-06-25 23:52:53 +02:00
Grégoire Paris
7ef4afc688 Merge pull request #10743 from mpdude/post-insert-id-early
Make EntityPersisters tell the UoW about post insert IDs early
2023-06-25 23:51:15 +02:00
Grégoire Paris
4e138903d0 Merge pull request #10789 from macroparts/distinct-updates-state-correctly
distinct() updates QueryBuilder state correctly
2023-06-25 18:11:37 +02:00
Daniel Jurkovic
efb50b9bdd distinct() updates QueryBuilder state correctly
Previously calling distinct() when the QueryBuilder was in clean state would cause subsequent getDQL() calls to ignore the distinct queryPart

Fixes #10784
2023-06-25 17:53:24 +02:00
Grégoire Paris
70bcff7410 Merge pull request #10794 from doctrine/2.15.x
Merge 2.15.x up into 2.16.x
2023-06-23 23:22:31 +02:00
Matthias Pigulla
1989531d4f Update tests/Doctrine/Tests/ORM/Functional/Ticket/GH7407Test.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-06-23 22:47:34 +02:00
Grégoire Paris
f778d8cf98 Merge pull request #10792 from greg0ire/entity-level-commit-order
Entity level commit order
2023-06-23 22:37:10 +02:00
Grégoire Paris
18b32ab9db Merge remote-tracking branch 'origin/2.15.x' into entity-level-commit-order 2023-06-23 22:21:53 +02:00
Matthias Pigulla
d738ecfcfe Avoid creating unmanaged proxy instances for referred-to entities during merge()
This PR tries to improve the situation/problem explained in #3037:

Under certain conditions – there may be multiple and not all are known/well-understood – we may get inconsistencies between the `\Doctrine\ORM\UnitOfWork::$entityIdentifiers` and `\Doctrine\ORM\UnitOfWork::$identityMap` arrays.

Since the `::$identityMap` is a plain array holding object references, objects contained in it cannot be garbage-collected.
`::$entityIdentifiers`, however, is indexed by `spl_object_id` values. When those objects are destructed and/or garbage-collected, the OID may be reused and reassigned to other objects later on.

When the OID re-assignment happens to be for another entity, the UoW may assume incorrect entity states and, for example, miss INSERT or UPDATE operations.

One cause for such inconsistencies is _replacing_ identity map entries with other object instances: This makes it possible that the old object becomes GC'd, while its OID is not cleaned up. Since that is not a use case we need to support (IMHO), #10785 is about adding a safeguard against it.

In this test shown here, the `merge()` operation is currently too eager in creating a proxy object for another referred-to entity. This proxy represents an entity already present in the identity map at that time, potentially leading to this problem later on.
2023-06-23 22:19:06 +02:00
Matthias Pigulla
aa3ff458c7 Add test: Commit order calculation must happen on the entity level
Add tests for entity insertion and deletion that require the commit order calculation to happen on the entity level. This demonstrates the necessity for the changes in #10547.

This PR contains two tests with carefully constructed entity relationships, where we have a non-nullable `parent` foreign key relationships between entities stored in the same table.

Class diagram:

```mermaid
classDiagram
    direction LR
    class A
    class B
    A --> B : parent
    B --|> A
```

Object graph:

```mermaid
graph LR;
    b1 --> b2;
    b2 --> a;
    b3 --> b2;
```

 #### Situation before #10547

The commit order is computed by looking at the associations at the _table_ (= _class_) level. Once the ordering of _tables_ has been found, entities being mapped to the same table will be processed in the order they were given to `persist()` or `remove()`.

That means only a particular ordering of `persist()` or `remove()` calls (see comment in the test) works:

For inserts, the order must be `$a, $b2, $b1, $b3` (or `... $b3, $b1`), for deletions `$b1, $b3, $b2, $a`.

 #### Situation with entity-level commit order computation (as in #10547)

The ORM computes the commit order by considering associations at the _entity_ level. It will be able to find a working order by itself.
2023-06-23 22:14:04 +02:00
Grégoire Paris
f76bab2b73 Merge pull request #10790 from greg0ire/slevomat-cs-upgrade
Work around slevomat/coding-standard issues
2023-06-23 22:13:30 +02:00
Grégoire Paris
0e06d6b67d Apply latest coding standard rules 2023-06-23 18:11:22 +02:00
Grégoire Paris
5114dcee0b Work around slevomat/coding-standard issues
I tweaked the code so that it would not fall victim to
https://github.com/slevomat/coding-standard/issues/1585 or
https://github.com/slevomat/coding-standard/issues/1586, thus fixing the
phpcs job without losing information or breaking other jobs.
2023-06-23 18:11:07 +02:00
Matthias Pigulla
8bc74c624a Make EntityPersisters tell the UoW about post insert IDs early
This refactoring does two things:

* We can avoid collecting the post insert IDs in a cumbersome array structure that will be returned by the EntityPersisters and processed by the UoW right after. Instead, use a more expressive API: Make the EntityPersisters tell the UoW about the IDs immediately.
* IDs will be available in inserted entities a tad sooner. That may help to resolve #10735, where we can use the IDs to skip extra updates.
2023-06-23 09:08:07 +02:00
Grégoire Paris
6c0a5ecbf9 Merge pull request #10787 from doctrine/2.15.x-merge-up-into-2.16.x_XV3tWpNu 2023-06-22 16:01:49 +02:00
Grégoire Paris
5f6501f842 Merge remote-tracking branch 'origin/2.15.x' into 2.15.x-merge-up-into-2.16.x_XV3tWpNu 2023-06-22 14:44:27 +02:00
Alexander Dmitryuk
4c3bd20801 Fix missing setFilterSchemaAssetsExpression in phpdoc (#10776) 2023-06-22 14:36:06 +02:00
Grégoire Paris
f2abf6143b Merge pull request #10763 from mpdude/defer-collection-entity-removals-to-post-commit 2023-06-22 14:35:20 +02:00
Minh Vuong
338deacb58 fix: attach entity listener when reset metadata factory 2023-06-22 17:02:24 +07:00
Grégoire Paris
829d5fbb9f Merge pull request #10780 from greg0ire/avoid-partial 2023-06-22 10:04:27 +02:00
Matthias Pigulla
d220494edc Improve documentation on exact behaviour of many-to-many deletion operations 2023-06-21 19:14:55 +02:00
Matthias Pigulla
c235901544 Move a check up (continue early) 2023-06-21 19:14:55 +02:00
Matthias Pigulla
ba089e551a Avoid unnecessary changes 2023-06-21 19:14:55 +02:00
Matthias Pigulla
ee0b3f214d Write tests more concisely 2023-06-21 19:14:55 +02:00
Matthias Pigulla
930fa831b2 Test expected query counts in ManyToManyBasicAssociationTest
When collection updates/join table cleanups do not happen through specialized Entity-/CollectionPersister methods but instead as "plain" updates, we may issue a lot more queries than expected.
2023-06-21 19:14:55 +02:00
Matthias Pigulla
98b404875f Defer removing removed entities from to-many collections until after transaction commit 2023-06-21 19:14:55 +02:00
Grégoire Paris
fcc5c106b4 Rely on partial objects less when in tests
Partial objects are deprecated. They were handy to make the generated
SQL more legible, but we should refrain from relying on them.
2023-06-18 18:20:29 +02:00
Grégoire Paris
5132f0deb0 Stop using $message argument
It brings nothing over what PHPUnit now natively does.
2023-06-18 18:16:29 +02:00
Grégoire Paris
fe8e313731 Merge pull request #10747 from wtfzdotnet/feature/fix-one-to-many-custom-id-orphan-removal 2023-06-16 10:07:29 +02:00
Grégoire Paris
41f704cd96 Merge pull request #10775 from doctrine/2.15.x
Merge 2.15.x up into 2.16.x
2023-06-13 20:10:31 +02:00
Grégoire Paris
1adb5c0c70 Merge pull request #10774 from greg0ire/document-dto-in-rsm 2023-06-12 13:52:10 +02:00
Grégoire Paris
e5174af669 Document how to produce DTOs with a result set mapping 2023-06-11 18:02:18 +02:00
Vaidas
c3106f9fe7 Restore document proxy state to uninitialized on load exception (#10645) 2023-06-09 08:54:22 +02:00
Alexander M. Turek
cbf45dd97e PHPStan 1.10.18, Psalm 5.12.0 (#10771) 2023-06-08 16:57:07 +02:00
Michael Roterman
3c0d140e52 OneToManyPersister does not take custom identifier types into account for orphan removal.
In my case a custom doctrine type of Uuid object is converted to string by simply casting it, resulting in a hex DELETE FROM x WHERE id = ? query,
whilst it should've been converted along the way to it's binary representation. This leads to no deletions being made at all as you would expect making use of doctrine custom type's as an identifier.

This commit fixes usage of ramsey/uuid or symfony/uid as custom id types when making use of orphan removal.
2023-06-06 19:51:41 +02:00
Michael Roterman
33675ff4a9 fix: Update baseline and assertions for OneToManyPersister 2023-06-06 18:18:26 +02:00
Alexander M. Turek
5c74795893 Merge 2.15.x into 2.16.x (#10765) 2023-06-06 11:30:27 +02:00
Nicolas Grekas
3827dd769e Don't call deprecated getSQLResultCasing and usesSequenceEmulatedIdentityColumns when we know the platform (#10759) 2023-06-06 11:27:31 +02:00
Nicolas Grekas
6c9b29f237 Don't call canEmulateSchemas in SchemaTool when possible (#10762) 2023-06-05 21:42:13 +02:00
Grégoire Paris
2afe2dc8af Merge pull request #10758 from Gwemox/partial-revert-enum-id-hash 2023-06-05 11:41:32 +02:00
Thibault Buathier
cc3d872b95 revert: transform backed enum to value 2023-06-05 10:58:32 +02:00
Grégoire Paris
8961bfe90c Merge pull request #10756 from doctrine/2.15.x
Merge 2.15.x up into 2.16.x
2023-06-05 09:11:52 +02:00
Grégoire Paris
15e3a7e861 Merge pull request #10751 from mpdude/7180-fixed
Add test to show #7180 has been fixed
2023-06-05 08:35:40 +02:00
Grégoire Paris
65dc154ce5 Merge pull request #10754 from greg0ire/fix-build
Fix build
2023-06-05 08:35:26 +02:00
Grégoire Paris
1280e005b6 Merge pull request #10755 from mpdude/9192-fixed
Add test to show #9192 has been fixed
2023-06-04 22:08:17 +02:00
Matthias Pigulla
79f53d5dae Add test to show #9192 has been fixed
This test implements the situation described in #9192. The commit order computation will be fixed by #10547.
2023-06-04 19:46:16 +00:00
Grégoire Paris
caaa0d6192 Ignore error about get_class($metadata)
This is the result of the contradiction between the phpdoc
(ClassMetadata), and the condition, which guarantees $metadata is not a
ClassMetadata. Relaxing the phpdoc leads to other phpstan issues, about
properties that exist in ClassMetadata but not in
PersistentClassMetadata. The right way to fix this would be to switch
from a deprecation to an exception, but that is not the path we have
taken, and all this will disappear in 3.0.x anyway, so let's not bother.
2023-06-03 20:59:29 +02:00
Grégoire Paris
68d3a63957 Use class name directly
Static analysis points out $this->getName() may return null. But there
is worse: the message mentions a "command class", and getName() is not a
class.
2023-06-03 20:59:29 +02:00
Matthias Pigulla
bf2937e63a Add test: Entity insertions must not happen table-wise
Add tests for entity insertion and deletion that require writes to different tables in an interleaved fashion, and that have to re-visit a particular table.

 #### Background

In #10531, I've given an example where it is necessary to compute the commit order on the entity (instead of table) level.

Taking a closer look at the UoW to see how this could be achieved, I noticed that the current, table-level commit order manifests itself also in the API between the UoW and `EntityPersister`s.

 #### Current situation

The UoW computes the commit order on the table level. All entity insertions for a particular table are passed through `EntityPersister::addInsert()` and finally written through `EntityPersister::executeInserts()`.

 #### Suggested change

The test in this PR contains a carefully constructed set of four entities. Two of them are of the same class (are written to the same table), but require other entities to be processed first.

In order to be able to insert this set of entities, the ORM must be able to perform inserts for a given table repeatedly, interleaved with writing other entities to their respective tables.
2023-06-03 13:07:41 +00:00
Matthias Pigulla
76b8a215c2 Update docs that name in @Index/@UniqueConstraint is optional (#10748)
Fixes #4283.
2023-06-03 00:14:43 +02:00
Matthias Pigulla
b163cea61c Update the FAQ entry on setting MySQL charset and collation for columns (#10746)
Fixes #2986.
2023-06-03 00:13:07 +02:00
Grégoire Paris
ed212ab924 Merge pull request #10749 from mpdude/6499-fixed
Add tests to show #6499 has been fixed
2023-06-02 23:36:44 +02:00
Matthias Pigulla
dd0e02e912 Add test to show #7180 has been fixed
Tests suggested in https://github.com/doctrine/orm/pull/7180#issuecomment-380841413 and https://github.com/doctrine/orm/pull/7180#issuecomment-381067448 by @arnaud-lb.

Co-authored-by: Arnaud Le Blanc <arnaud.lb@gmail.com>
2023-06-02 21:28:11 +00:00
Matthias Pigulla
aad875eea1 Add tests to show #6499 has been fixed
@frikkle was the first to propose these tests in #6533.
@rvanlaak followed up in #8703, making some adjustments.

Co-authored-by: Gabe van der Weijde <gabe.vanderweijde@triasinformatica.nl>
Co-authored-by: Richard van Laak <rvanlaak@gmail.com>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-06-02 21:14:17 +00:00
Grégoire Paris
84ab535e56 Merge pull request #10750 from mpdude/7006-fixed
Add test to show #7006 has been fixed
2023-06-02 22:07:47 +02:00
Matthias Pigulla
a72a0c3597 Update tests/Doctrine/Tests/ORM/Functional/Ticket/GH7006Test.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-06-02 22:00:20 +02:00
Matthias Pigulla
6217285544 Add test to show #7006 has been fixed 2023-06-02 17:52:51 +00:00
Grégoire Paris
330c0bc67e Merge pull request #10689 from mpdude/insert-entity-level-topo-sort 2023-06-02 15:21:08 +02:00
Grégoire Paris
b5595ca041 Merge pull request #10732 from mpdude/10348-fixed
Add a test case to show #10348 has been fixed
2023-06-02 07:50:03 +02:00
Grégoire Paris
4dfbe13897 Merge pull request #10740 from greg0ire/remove-ignore-rule
Remove useless ignore rule
2023-06-02 00:14:27 +02:00
Grégoire Paris
b64e1c9b1f Remove useless ignore rule
This is useless since 152f91fa3
2023-06-01 21:03:58 +02:00
Grégoire Paris
b779b112f3 Merge pull request #10738 from doctrine/2.15.x-merge-up-into-2.16.x_PvK1bbO1 2023-06-01 11:45:08 +02:00
Grégoire Paris
bf449bef7d Merge pull request #10737 from nicolas-grekas/lexer-deprec 2023-06-01 11:35:50 +02:00
Nicolas Grekas
152f91fa33 Fix deprecations from doctrine/lexer 2023-06-01 11:22:36 +02:00
Grégoire Paris
14d1eb5340 Document pdo_sqlite requirement for tests (#10734) 2023-06-01 08:57:07 +02:00
Matthias Pigulla
04e08640fb Add a test case to show #10348 has been fixed by #10566
This is part of the series of issues fixed by #10547. In particular, the changes from #10566 were relevant.

See #10348 for the bug description.

Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-05-31 21:38:42 +00:00
Matthias Pigulla
aa27b3a35f Remove the now unused UnitOfWork::getCommitOrder() method 2023-05-31 07:31:56 +00:00
Matthias Pigulla
d76fc4ebf6 Compute entity-level commit order for entity insertions
This is the third step to break https://github.com/doctrine/orm/pull/10547 into smaller PRs suitable for reviewing. It uses the new topological sort implementation from #10592 and the refactoring from #10651 to compute the UoW's commit order for entity insertions not on the entity class level, but for single entities and their actual dependencies instead.

 #### Current situation

`UnitOfWork::getCommitOrder()` would compute the entity sequence on the class level with the following code:

70477d81e9/lib/Doctrine/ORM/UnitOfWork.php (L1310-L1325)

 #### Suggested change

* Instead of considering the classes of all entities that need to be inserted, updated or deleted, consider the new (inserted) entities only. We only need to find a sequence in situations where there are foreign key relationships between two _new_ entities.
* In the dependency graph, add edges for all to-one association target entities.
* Make edges "optional" when the association is nullable.

 #### Test changes

I have not tried to fully understand the few changes necessary to fix the tests. My guess is that those are edge cases where the insert order changed and we need to consider this during clean-up.

Keep in mind that many of the functional tests we have assume that entities have IDs assigned in the order that they were added to the EntityManager. That does not change – so the order of entities is generally stable, equal to the previous implementation.
2023-05-31 07:16:16 +00:00
Grégoire Paris
da0998c401 Add missing underscore to RST links (#10731) 2023-05-30 23:00:28 +02:00
Matthias Pigulla
ae60cf005f Commit order for removals has to consider SET NULL, not nullable (#10566)
When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken.

 #### Background

The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations.

For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances.

For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place.

_But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything.

 #### Example

Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`.

Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up.

All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them.

_Any_ insert order will work, so the improvements necessary to solve #10531 or #10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough.

For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation.

The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`.

 #### Related cases

These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support:
* #5665
* #10548
2023-05-30 22:58:36 +02:00
Alexander M. Turek
ddd3066bc4 Revert "Allow symfony/console 7 (#10724)"
This reverts commit 6662195936.
2023-05-25 08:48:53 +02:00
Alexander M. Turek
6662195936 Allow symfony/console 7 (#10724) 2023-05-25 08:47:35 +02:00
Grégoire Paris
cc011d8215 Merge pull request #10725 from doctrine/2.14.x
Merge 2.14.x up into 2.15.x
2023-05-24 14:08:21 +02:00
Grégoire Paris
d831c126c9 Merge pull request #10643 from htto/bugfix/sti-with-abstract-intermediate
Fix single table inheritance with intermediate abstract class(es)
2023-05-24 11:58:11 +02:00
Grégoire Paris
10eae1a7ff Merge pull request #10722 from phansys/query_single_result_exception
Fix and improve functional test cases expecting `NonUniqueResultException` from `AbstractQuery::getSingleScalarResult()`
2023-05-23 22:46:18 +02:00
Grégoire Paris
d951aa05b9 Merge pull request #10721 from phansys/query_single_result 2023-05-23 11:33:49 +02:00
Javier Spagnoletti
be297b9fd3 Narrow return types for AbstractQuery::getSingleScalarResult() 2023-05-23 05:47:39 -03:00
Javier Spagnoletti
125e18cf24 Fix and improve functional test cases expecting NonUniqueResultException from AbstractQuery::getSingleScalarResult() 2023-05-23 05:32:38 -03:00
Grégoire Paris
8fba9d6868 Merge pull request #10708 from mbabker/2.15-link-fix 2023-05-16 17:06:17 +02:00
Michael Babker
7f4f1cda71 Correct docs link 2023-05-16 09:38:09 -05:00
Grégoire Paris
4e137f77a5 Merge pull request #10704 from greg0ire/drop-useless-block
Remove unreachable piece of code
2023-05-16 08:19:42 +02:00
Grégoire Paris
a33aa15c79 Remove unreachable piece of code
mappedBy is never defined on an inverse relationship.
Bi-directional self-referencing should IMO still result in 2 separate
associations, on 2 separate fields of the same class, like mentor or
mentee.
2023-05-15 14:10:22 +02:00
Grégoire Paris
255ce51526 Merge pull request #10703 from doctrine/2.15.x 2023-05-15 12:05:48 +02:00
Grégoire Paris
1c905b0e0a Merge pull request #10702 from greg0ire/fix-build 2023-05-15 11:53:09 +02:00
Grégoire Paris
7901790b97 Adapt to latest coding standards 2023-05-15 11:42:13 +02:00
Grégoire Paris
cef1d2d740 Merge pull request #10666 from MatTheCat/inherited-readonly-properties
Create `ReflectionReadonlyProperty` from their declaring class so their value can be set
2023-05-12 07:50:03 +02:00
Grégoire Paris
8126882305 Merge pull request #10486 from mpdude/fix-to-many-update-on-delete
Fix to-many collections left in dirty state after entities are removed by the UoW
2023-05-12 00:11:56 +02:00
Matthias Pigulla
a9513692cb Fix to-many collections left in dirty state after entities are removed by the UoW 2023-05-12 00:02:44 +02:00
Grégoire Paris
52c3d9d82a Merge pull request #10508 from Gwemox/fix-enum-identity
Fix id hash of entity with enum as identifier
2023-05-12 00:00:20 +02:00
MatTheCat
2f46e5a130 Rename test class and method 2023-05-11 09:36:43 +02:00
Mathieu
a3fa1d7faa Replace assertions by @doesNotPerformAssertions 2023-05-11 09:33:12 +02:00
MatTheCat
c7c57be0c2 Create ReflectionReadonlyProperty from their declaring class so their value can be set 2023-05-11 09:33:12 +02:00
MatTheCat
721794fb9c Add test case 2023-05-11 09:33:12 +02:00
Grégoire Paris
38e47fdeab Merge pull request #10455 from mpdude/fix-mapping-driver-load
Make Annotations/Attribute mapping drivers report fields for the classes where they are declared
2023-05-08 15:46:54 +02:00
Matthias Pigulla
9ac063d879 Add a new topological sort implementation (#10592)
This is the first chunk to break #10547 into smaller PRs suitable for reviewing. It adds a new topological sort implementation.

 #### Background

Topological sort is an algorithm that sorts the vertices of a directed acyclic graph (DAG) in a linear order such that for every directed edge from vertex A to vertex B, vertex A comes before vertex B in the ordering. This ordering is called a topological order.

Ultimately (beyond the scope of this PR), in the ORM we'll need this to find an order in which we can insert new entities into the database. When one entity needs to refer to another one by means of a foreign key, the referred-to entity must be inserted before the referring entity. Deleting entities is similar.

A topological sorting can be obtained by running a depth first search (DFS) on the graph. The order in which the DFS finishes on the vertices is a topological order. The DFS is possible iif there are no cycles in the graph. When there are cycles, the DFS will find them.

For more information about topological sorting, as well as a description of an DFS-based topological sorting algorithm, see https://en.wikipedia.org/wiki/Topological_sorting.

 #### Current situation

There is a DFS-based topological sorting implemented in the `CommitOrderCalculator`. This implementation has two kinks:

1. It does not detect cycles

When there is a cycle in the DAG that cannot be resolved, we need to know about it. Ultimately, this means we will not be able to insert entities into the database in any order that allows all foreign keys constraints to be satisfied.

If you look at `CommitOrderCalculator`, you'll see that there is no code dealing with this situation.

2. It has an obscure concept of edge "weights"

To me, it is not clear how those are supposed to work. The weights are related to whether a foreign key is nullable or not, but can (could) be arbitrary integers. An edge will be ignored if it has a higher (lower) weight than another, already processed edge... 🤷🏻?

 #### Suggested change

In fact, when inserting entities into the database, we have two kinds of foreign key relationships: Those that are `nullable`, and those that are not.

Non-nullable foreign keys are hard requirements: Referred-to entities must be inserted first, no matter what. These are "non-optional" edges in the dependency graph.

Nullable foreign keys can be set to `NULL` when first inserting an entity, and then coming back and updating the foreign key value after the referred-to (related) entity has been inserted into the database. This is already implemented in `\Doctrine\ORM\UnitOfWork::scheduleExtraUpdate`, at the expense of performing one extra `UPDATE` query after all the `INSERT`s have been processed. These edges are "optional".

When finding a cycle that consists of non-optional edges only, treat it as a failure. We won't be able to insert entities with a circular dependency when all foreign keys are non-NULLable.

When a cycle contains at least one optional edge, we can use it to break the cycle: Use backtracking to go back to the point before traversing the last _optional_ edge. This omits the edge from the topological sort order, but will cost one extra UPDATE later on.

To make the transition easier, the new implementation is provided as a separate class, which is marked as `@internal`.

 #### Outlook

Backtracking to the last optional edge is the simplest solution for now. In general, it might be better to find _another_ (optional) edge that would also break the cycle, if that edge is also part of _other_ cycles.

Remember, every optional edge skipped costs an extra UPDATE query later on. The underlying problem is known as the "minimum feedback arc set" problem, and is not easily/efficiently solvable. Thus, for the time being, picking the nearest optional edge seems to be reasonable.
2023-05-08 13:30:07 +02:00
Matthias Pigulla
b52a8f8b9e Make Annotations/Attribute mapping drivers report fields for the classes where they are declared
This PR will make the annotations and attribute mapping drivers report mapping configuration for the classes where it is declared, instead of omitting it and reporting it for subclasses only. This is necessary to be able to catch mis-configurations in `ClassMetadataFactory`.

Fixes #10417, closes #10450, closes #10454.

#### ⚠️ Summary for users getting `MappingExceptions` with the new mode

When you set the `$reportFieldsWhereDeclared` constructor parameters to `true` for the AnnotationDriver and/or AttributesDriver and get `MappingExceptions`, you may be doing one of the following:

* Using `private` fields with the same name in different classes of an entity inheritance hierarchy (see #10450)
* Redeclaring/overwriting mapped properties inherited from mapped superclasses and/or other entities (see #10454)

As explained in these two PRs, the ORM cannot (or at least, was not designed to) support such configurations. Unfortunately, due to the old – now deprecated – driver behaviour, the misconfigurations could not be detected, and due to previously missing tests, this in turn was not noticed.

#### Current situation

The annotations mapping driver has the following condition to skip properties that are reported by the PHP reflection API:

69c7791ba2/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php (L345-L357)

This code has been there basically unchanged since the initial 2.0 release. The same condition can be found in the attribute driver, probably it has been copied when attributes were added.

I _think_ what the driver tries to do here is to deal with the fact that Reflection will also report `public`/`protected` properties inherited from parent classes. This is supported by the observation (see #5744) that e. g. YAML and XML drivers do not contain this logic.

The conditions are not precise enough for edge cases. They lead to some fields and configuration values not even being reported by the driver. 

Only since the fields would be "discovered" again when reflecting on subclasses, they eventually end up in class metadata structures for the subclasses. In one case of inherited ID generator mappings, the `ClassMetadataFactory` would also rely on this behaviour.

Two potential bugs that can result from this are demonstrated in #10450 and #10454.

#### Suggested solution

In order to find a more reliable way of separating properties that are merely reported again in subclasses from those that are actual re-declarations, use the information already available in `ClassMetadata`. In particular, `declared` tells us in which non-transient class a "field" was first seen.

Make the mapping driver skip only those properties for which we already know that they have been declared in parent classes, and skip them only when the observed declaring class matches the expectation. 

For all other properties, report them to `ClassMetadataFactory` and let that deal with consistency checking/error handling.

#10450 and #10454 are merged into this PR to show that they pass now.

#### Soft deprecation strategy

To avoid throwing new/surprising exceptions (even for misconfigurations) during a minor version upgrade, the new driver mode is opt-in.

Users will have to set the `$reportFieldsWhereDeclared` constructor parameters to `true` for the `AnnotationDriver` and/or `AttributesDriver`. Unless they do so, a deprecation warning will be raised.

In 3.0, the "new" mode will become the default. The constructor parameter can be deprecated (as of ORM 3.1, probably) and is a no-op.

We need to follow up in other places (DoctrineBundle, ... – what else?) to make this driver parameter an easy-to-change configuration setting.
2023-05-08 11:23:28 +00:00
Terence Eden
70477d81e9 Documentation typo (#10686)
The past tense of spin is spun, not "spinned"

https://en.wiktionary.org/wiki/spin#English
2023-05-08 12:22:09 +02:00
Grégoire Paris
1b2771f964 Merge pull request #10685 from doctrine/2.15.x-merge-up-into-2.16.x_vNvlgHpG
Merge release 2.15.1 into 2.16.x
2023-05-07 21:06:56 +02:00
Alexander M. Turek
9bc6f5b4ac Support unserializing 2.14 ParserResult instances (#10684) 2023-05-07 20:56:25 +02:00
Grégoire Paris
9766b6b03e Merge pull request #10651 from mpdude/unit-of-work-schedule-single-entities
Prepare entity-level commit order computation in the `UnitOfWork`
2023-05-05 20:43:23 +02:00
Grégoire Paris
37c8953015 Merge pull request #10678 from doctrine/2.15.x
Merge 2.15.x up into 2.16.x
2023-05-05 08:25:43 +02:00
Grégoire Paris
60c625be17 Upgrade to Psalm 5.11.0 (#10679) 2023-05-05 07:21:05 +02:00
Grégoire Paris
8b0d6a13c2 Merge pull request #10671 from BoShurik/fix-attribute-many-to-many-mapping
Fix attribute ManyToMany mapping
2023-05-04 23:52:47 +02:00
Grégoire Paris
a7ac6ff8d9 Merge pull request #10677 from greg0ire/psalm-5-10
Upgrade to Psalm 5.10.0
2023-05-04 23:49:02 +02:00
Grégoire Paris
222d544b0c Upgrade to Psalm 5.10.0 2023-05-04 22:12:31 +02:00
Grégoire Paris
a199ca3002 Merge pull request #10676 from doctrine/2.15.x
Merge 2.15.x up into 2.16.x
2023-05-04 19:35:13 +02:00
ixitheblue
6f1fd7a81e Fix iteration index initialisation (#10675)
In the code examples of the use of the ``Query#toIterable()`` method, an index ``$i`` is used to control the size of the current batch, which ends when the condition ``($i % $batchSize) === 0`` becomes true.

Because this condition is preceded by ``++$i`` and ``$i`` is initialized to 1, the first batch is actually of size ``$batchSize - 1`` instead of ``$batchSize``.

To solve this, ``$i`` should be initialized to 0 instead of 1.
2023-05-04 14:43:56 +02:00
BoShurik
2af6e4db38 Take into account join columns specifications.
Currently, the AttributeDriver ignores any join column attribute specified on a many to many relationship.
Let's copy code from the AnnotationDriver to fix that.

Fixes #9902
2023-05-04 15:36:29 +03:00
BoShurik
b48dafded8 Update doc-block for AttributeDriverTest's fixtures 2023-05-04 15:35:40 +03:00
Grégoire Paris
ffe1550a68 Bump version numbers in the README (#10674)
2.15.0 has been released.
2023-05-04 13:44:58 +02:00
Grégoire Paris
eef5a1db81 Merge pull request #10668 from stollr/doc_attr_uniqueconstraint
Added doc for the fields parameter of the UniqueConstraint attribute
2023-05-04 08:13:33 +02:00
Grégoire Paris
c2d053d185 Update branch metadata (#10672) 2023-05-03 22:41:13 +02:00
stollr
77822d623e Added doc for the fields parameter of the UniqueConstraint attribute 2023-05-03 10:45:07 +02:00
Grégoire Paris
7b5fafffbe Merge pull request #10654 from mpdude/join-column-does-not-make-it-own
Deprecate usage of `@JoinColumn` on the inverse side of one-to-one associations
2023-04-26 20:52:02 +02:00
Matthias Pigulla
aba8d74017 Deprecate usage of @JoinColumn on the inverse side of one-to-one associations
Following up on #10652:

 #### Current situation

The implementation of `\Doctrine\ORM\Mapping\ClassMetadataInfo::_validateAndCompleteOneToOneMapping` will consider a field with a one-to-one association to be the owning side also when it configures `@JoinColumn` settings.

 #### Suggested change

For a one to one association, a field should be the inverse side when it uses the `mappedBy` attribute, and be the owning side otherwise. The `JoinColumn` may be configured on the owning side only.

This PR adds a deprecation notice when `@JoinColumn` is used on the side of a one-to-one association where `mappedBy` occurs.

In 3.0, this will throw a `MappingException`.
2023-04-25 15:29:17 +02:00
Alexander M. Turek
a056552db9 Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  PHPStan 1.10.14 (#10655)
  Remove JoinColumn from inverse side (#10652)
  Apply `SlevomatCodingStandard.Commenting.AnnotationName` CS rule (#10653)
2023-04-25 13:43:39 +02:00
Alexander M. Turek
1142a396d3 PHPStan 1.10.14 (#10655) 2023-04-25 13:32:39 +02:00
Grégoire Paris
f01b7d254d Remove JoinColumn from inverse side (#10652)
This makes no sense because it describes a one-to-one where each table
references the other one. I do not think this is deliberately supported,
and it probably will not be supported at all in 3.0.
2023-04-25 13:16:20 +02:00
Matthias Pigulla
ed34327941 More precisely state conditions for the postPersist event 2023-04-25 09:48:41 +02:00
Javier Spagnoletti
1ea8424e07 Apply SlevomatCodingStandard.Commenting.AnnotationName CS rule (#10653) 2023-04-25 08:56:58 +02:00
Matthias Pigulla
b42cf99402 Prepare entity-level commit order computation in the UnitOfWork
This is the second chunk to break #10547 into smaller PRs suitable for reviewing. It prepares the `UnitOfWork` to work with a commit order computed on the entity level, as opposed to a class-based ordering as before.

#### Background

#10531 and #10532 show that it is not always possible to run `UnitOfWork::commit()` with a commit order given in terms of  entity _classes_. Instead it is necessary to process entities in an order computed on the _object_ level. That may include

* a particular order for multiple entities of the _same_ class
* a particular order for entities of _different_ classes, possibly even going back and forth (entity `$a1` of class `A`, then `$b` of class `B`, then `$a2` of class `A` – revisiting that class).

This PR is about preparing the `UnitOfWork` so that its methods will be able to perform inserts and deletions on that level of granularity. Subsequent PRs will deal with implementing that particular order computation.

#### Suggested change

Change the private `executeInserts` and `executeDeletions` methods so that they do not take a `ClassMetadata` identifying the class of entities that shall be processed, but pass them the list of entities directly.

The lists of entities are built in two dedicated methods. That happens basically as previously, by iterating over `$this->entityInsertions` or `$this->entityDeletions` and filtering those by class.

#### Potential (BC breaking?) changes that need review scrutiny

* `\Doctrine\ORM\Persisters\Entity\EntityPersister::addInsert()` was previously called multiple times, before the insert would be performed by a call to `\Doctrine\ORM\Persisters\Entity\EntityPersister::executeInserts()`. With the changes here, this batching effectively no longer takes place. `executeInserts()` will always see one entity/insert only, and there may be multiple `executeInserts()` calls during a single `UoW::commit()` phase.
* The caching in `\Doctrine\ORM\Cache\Persister\Entity\AbstractEntityPersister` previously would cache entities from the last executed insert batch only. Now it will collect entities across multiple batches. I don't know if that poses a problem.
* Overhead in `\Doctrine\ORM\Persisters\Entity\BasicEntityPersister::executeInserts` is incurred multiple times; that may, however, only be about SQL statement preparation and might be salvageable.
* The `postPersist` event previously was not dispatched before _all_ instances of a particular entity class had been written to the database. Now, it will be dispatched immediately after every single entity that has been inserted.
2023-04-24 13:32:09 +02:00
Grégoire Paris
ba9f51a363 Merge pull request #10648 from doctrine/2.14.x-merge-up-into-2.15.x_VZV5I0St
Merge release 2.14.3 into 2.15.x
2023-04-22 15:45:17 +02:00
Heiko Przybyl
1ae74b8ec5 Fix single table inheritance with intermediate abstract class(es)
Fixes #10625
2023-04-20 15:37:42 +02:00
Grégoire Paris
a64f315dfe Merge pull request #10642 from yobrx/patch-1 2023-04-20 11:46:32 +02:00
Yoann B
6ca319a6f4 fix array association on partial index 2023-04-20 10:59:39 +02:00
Grégoire Paris
fceb279947 Merge pull request #10630 from monadial/fix/fqcn-type-in-xml-mapping
Fixed xsd schema for support FQCN type
2023-04-16 10:26:37 +02:00
Alexander M. Turek
2977933119 Run tests on SQLite with foreign keys enabled (#10632) 2023-04-15 10:54:31 +02:00
Tomas Mihalicka
5ac6fadf29 Fixed xsd schema for support FQCN type
After update to orm 2.14.2 invalid xsd schema error is occured, when in field,id or attribute-override have type is FQCN
2023-04-14 18:16:35 +02:00
Grégoire Paris
e59ed88251 Merge pull request #10620 from ecourtial/fix-doc-typo
fix typo in HydrationCompleteHandler doc
2023-04-12 21:21:42 +02:00
Eric COURTIAL
a16aeaeac8 fix typo in HydrationCompleteHandler doc 2023-04-12 21:02:36 +02:00
Mathieu
fca1ef78a7 Handle null comparisons in ManyToManyPersister (#10587)
* Add test case for https://github.com/doctrine/orm/issues/7717

* Do not hide null equality checks in `SqlValueVisitor::walkComparison`

* Annotate `GH7717Parent::$children` type
2023-04-12 17:31:38 +02:00
Grégoire Paris
a78e5bcf65 Merge pull request #10519 from mpdude/deprecate-override-association
Deprecate overriding associations not inherited from a mapped superclass
2023-04-07 20:03:39 +02:00
Thibault Buathier
09b4a75ed3 Fix id hash of entity with enum as identifier
When an entity have a backed enum as identifier, `UnitOfWork` tries to
cast to string when generating the hash of the id.
This fix calls `->value` when identifier is a `BackedEnum`.
Fixes #10471
Fixes #10334
2023-04-03 17:20:48 +02:00
Grégoire Paris
b984b567b8 Merge pull request #10599 from amina-seraoui/2.14.x
fix(persistent-collection): check association is not nullable before using it as an array
2023-04-02 23:26:11 +02:00
tasmim-concept
92c56164ee throw exception if association is null 2023-04-01 08:50:06 +02:00
Grégoire Paris
b2707509fc Merge pull request #10605 from greg0ire/2.15.x
Merge 2.14.x up into 2.15.x
2023-03-30 20:37:50 +02:00
Grégoire Paris
1d02139d2a Merge remote-tracking branch 'origin/2.14.x' into 2.15.x 2023-03-30 17:22:56 +02:00
Grégoire Paris
7ed28dba50 Merge pull request #10601 from JanTvrdik/discriminated-column-options 2023-03-30 13:31:59 +02:00
Jan Tvrdík
f215515bab Support options like charset and collation on DiscriminatedColumn
[closes #10462]
2023-03-30 11:58:29 +02:00
Grégoire Paris
ffbfbfcda1 Merge pull request #10602 from greg0ire/remove-or
Remove duplicate array shape
2023-03-29 23:30:10 +02:00
Grégoire Paris
4a30f622ec Remove duplicate array shape
DiscriminatorColumnMapping is just a specialization of the array shape
that is right of the pipe: it has the same fields, except fewer fields
are nullable. The union of that is the same thing as the array shape.
2023-03-29 21:58:36 +02:00
Grégoire Paris
aec3556502 Merge pull request #10554 from mpdude/mapped-superclass-resolve-to-many-lazy-check
Make "targetEntity must not be a mapped superclass" a lazy check
2023-03-24 08:27:38 +01:00
Grégoire Paris
33a19f1bf3 Merge pull request #10593 from mpdude/workflows
Run workflows also for the `entity-level-commit-order` branch
2023-03-23 09:15:33 +01:00
Matthias Pigulla
b6669746b7 Run workflows also for the entity-level-commit-order branch 2023-03-23 08:01:15 +00:00
Gabriel Ostrolucký
25bd41a504 Merge branch '2.14.x' into 2.15.x 2023-03-19 22:46:07 +01:00
Grégoire Paris
04573fc283 Address deprecation of fetchAll() (#10569)
The methods Connection::fetchAll() and Result::fetchAll() have been
deprecated in favor of more their precise counterparts.
2023-03-07 13:17:42 +01:00
Alexander M. Turek
82362ee65e Merge 2.14.x into 2.15.x (#10564) 2023-03-06 09:30:02 +01:00
Alexander M. Turek
9ff0440aac Merge 2.14.x into 2.15.x (#10561) 2023-03-05 22:35:31 +01:00
Matthias Pigulla
2c40e917c8 Revert unnecessary changes from #10473 2023-03-01 21:30:22 +00:00
Matthias Pigulla
5c06d46874 Add tests for the schema validator check 2023-03-01 21:30:22 +00:00
Matthias Pigulla
24c4ac4dd8 Do not check at runtime that associations do not refer to mapped superclasses 2023-03-01 20:40:55 +00:00
Grégoire Paris
4fad7a1190 Merge pull request #10473 from mpdude/mapped-superclass-resolve-to-many
Allow to-many associations on mapped superclasses w/ ResolveTargetEntityListener
2023-03-01 08:16:07 +01:00
Alexander M. Turek
f36a8c879c Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  Ignore the cache dir of PHPUnit 10 (#10546)
  Make data providers static (#10544)
  Bump dev tools (#10541)
  Mark SqlWalker methods as not deprecated (#10540)
  docs: consistency order for docblock in association mapping (#10534)
  Correct use of PHP attribute
  fix typo in faq.rst (#10526)
  fix: use executeStatement in SchemaTool (#10516)
  Write a test in a more specific way
  Put up a warning sign that mapping may not be inherited from transient classes (#10392)
  Avoid unnecessary information in query hints to improve query cache hit ratio
2023-02-28 13:50:36 +01:00
Alexander M. Turek
fa4b945b94 Make data providers static (#10545) 2023-02-28 08:29:47 +01:00
Matthias Pigulla
7814cbf4ec Fix a Markdown/RST formatting glitch 2023-02-24 17:59:02 +01:00
Grégoire Paris
979b3dcb8d Merge pull request #10513 from greg0ire/use-array-shapes
Use array shapes where appropriate
2023-02-13 23:57:57 +01:00
Matthias Pigulla
8efdcb0555 Deprecate overriding associations not inherited from a mapped superclass 2023-02-13 20:46:11 +00:00
Grégoire Paris
3810a0b6f9 Merge pull request #10470 from mpdude/prevent-entity-override
Deprecate overriding fields/associations inherited from other entities
2023-02-12 18:30:46 +01:00
Matthias Pigulla
c9b644dced Trigger a deprecation notice when entity fields/associations are overridden
This was brought up in #8348, but seemingly forgotten to be implenented in later versions.

Closes #10289.
2023-02-09 22:15:05 +00:00
Grégoire Paris
ae6de13c01 Use array shapes where appropriate
Working on converting these array shapes to DTO allowed me to find every
signature where they are supposed to be used.

The Psalm baseline gets worse because it considers accessing an array
key differently depending on whether it is defined vaguely, as
array<string, mixed>, or precisely, as array{my-key?: string}.
2023-02-09 19:44:47 +01:00
Matthias Pigulla
072c40357f Add mapping configurations for classes that were used in tests as entities, but never declared
Now that we validate association targets, that's an error.
2023-02-08 21:11:59 +00:00
Matthias Pigulla
ca94e82828 Allow to-many associations on mapped superclasses w/ ResolveTargetEntityListener
Allow to-many associations to be used on mapped superclasses when the owning (inverse) side does not refer back to the mapped superclass, thanks to `ResolveTargetEntityListener`.

 #### Current situation

The [documentation states](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html):

> No database table will be created for a mapped superclass itself

> [...] persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all.

That's a though limitation.

~Obviously~ ~apparently~ Probably the limitation comes from the fact that in a to-many association the "many" side has to hold a foreign key. Since the mapped superclass does not have a database table (it's not an entity), no such backreference can be established.

Currently, to-many associations trigger an exception as soon as they are seen on a mapped superclass:

d6c0031d44/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php (L459-L461)

 #### `ResolveTargetEntityListener`

The `ResolveTargetEntityListener` can be used to substitute interface or class names in mapping configuration at runtime, during the metadata load phase.

When this gimmick is used to replace _all_ references to the mapped superclass with an entity class in time, it should be possible to have to-many associations on the inheriting entity classes.

 #### Suggested solution

Instead of rejecting to-many associations on mapped superclasses right away, validate that at the end of the day (after the `loadClassMetadata` event has been processed) no association may target at a non-entity class. That includes mapped superclasses as well as transient classes.

 #### Motivating example

Consider a library that comes with a `User` base class. This class is `abstract` and has to be subclassed/filled when the library is used.

By making this a mapped superclass, library users have the freedom to either have a simple user entity class or a user class hierarchy, but we do not impose any requirements on them. (NB we also don't want to have a root entity in the library, because that would have to declare the entire class hierarchy, including library users' classes.)

The actual user class to be used will be configured through the `ResolveTargetEntityListener`.

The library also includes a `SocialMediaAccount` entity. A `User` can have multiple of these accounts, and we want to be able to navigate the accounts from the user side.

To make the example even more fancy, there is a self-referencing association on the `User`: A `User` has been created by another user, and holds a collection of all other `User`s it created.

The test case contained in this PR contains this example and validates that all association mappings look just as if the final user class had been written as an entity directly, without the superclass.

 #### Potential review talking points

- Am I missing other reasons why to-many is not feasible?
- We now reject association mappings with `targetEntity`s that are not entities; relevant BC break? (IMHO: no.)

 #### Review tip

Review commit by commit, not all files at once. The last commit adds a lot of entity declarations that were previously missed in tests and now raised exceptions; that's a lot of clutter in the PR.
2023-02-08 21:11:58 +00:00
Grégoire Paris
db0b9d13c6 Merge pull request #10511 from doctrine/2.14.x
Merge 2.14.x up into 2.15.x
2023-02-08 22:07:14 +01:00
Alexander M. Turek
6c17e47624 Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  Remove calls to assertObjectHasAttribute() (#10502)
  Remove calls to withConsecutive() (#10501)
  Use recognized array key
  Fix #9095 by re-applying #9096
  Use linebreaks
2023-02-07 01:21:39 +01:00
Grégoire Paris
83d56d75e1 Merge remote-tracking branch 'origin/2.14.x' into 2.15.x 2023-02-04 10:09:15 +01:00
Grégoire Paris
d593c33ffa Merge pull request #10478 from greg0ire/better-psalm-type-location
Move psalm types to ClassMetadata
2023-01-28 16:35:37 +01:00
Grégoire Paris
6c925f5b60 Move psalm types to ClassMetadata
This spares us from referencing ClassMetadataInfo from other classes,
which is a good thing since it is deprecated. It also means merging up
is easier.
2023-01-28 15:40:27 +01:00
Grégoire Paris
82bf68d482 Remove underscore prefix on private variables (#10477) 2023-01-28 15:33:34 +01:00
Alexander M. Turek
2ee936acb4 Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  Psalm 5.6.0, PHPStan 1.9.14 (#10468)
2023-01-26 19:08:35 +01:00
Matthias Pigulla
843bff4971 Fix some tests that were missed in #10431 (#10464)
In #10431, some invalid inheritance declarations in our test base were fixed. However, the change missed to update XML, PHP and static PHP mapping configurations as well.

Apparently, this did not raise any flags because the misconfiguration only caused a deprecation notice in 2.15.x. Running the tests against 3.0 (where the misconfiguration will be an error) unveiled the mistake.
2023-01-26 19:04:57 +01:00
Matthias Pigulla
d679292861 Remove commented-out code sections (#10465)
This removes comments added in #10431 on the 2.15.x branch, as suggested in https://github.com/doctrine/orm/pull/10460#issuecomment-1404889903.
2023-01-26 14:09:35 +01:00
Matthias Pigulla
65687821a7 Deprecate undeclared entity inheritance (#10431)
Inheritance has to be declared as soon as one entity class extends (directly or through middle classes) another one.

This is also pointed out in the opening comment for #8348:

> Entities are not allowed to extend from entities without an inheritence mapping relationship (Single Table or Joined Table inheritance). [...] While Doctrine so far allowed these things, they are fragile and will break on certain scenarios.

Throwing an exception in case of this misconfiguration is nothing we should do light-heartedly, given that it may surprise users in a bugfix or feature release. So, we should start with a deprecation notice  and make this an exception in 3.0. The documentation is updated accordingly at #10429.

Catching missing inheritance declarations early on is important to avoid weird errors further down the road, giving users a clear indication of the root cause.

In case you are affected by this, please understand that although things "previously worked" for you, you have been using the ORM outside of what it was designed to do. That may have worked in simple cases, but may also have caused invalid results (wrong or missing data after hydration?) that possibly went unnoticed in subtle cases.
2023-01-24 22:47:27 +01:00
Grégoire Paris
ca0dffb53e Merge remote-tracking branch 'origin/2.14.x' into 2.15.x 2023-01-23 19:27:03 +01:00
Grégoire Paris
01028cf3b8 Merge commit '8b2854393' into 2.15.x 2023-01-23 19:20:22 +01:00
Grégoire Paris
84bfe7cbb9 Merge pull request #10446 from greg0ire/update-psalm-baseline
Update Psalm baseline
2023-01-23 19:20:00 +01:00
Grégoire Paris
1b3978e8c9 Update Psalm baseline 2023-01-23 19:17:40 +01:00
Grégoire Paris
7f783b59c8 Merge pull request #10442 from greg0ire/embedded-class-array-shape 2023-01-23 10:58:11 +01:00
Grégoire Paris
68662f5920 Add embedded class mapping array shape
This should be replaced with a DTO in the next major.
To be able to have something usable, I had to move the validation of the
DTO (checking that it has a "class" attribute) to mapEmbedded, which
happens earlier than doLoadMetadata(). It is unclear to me why it was
not put here in the first place.
2023-01-22 23:49:43 +01:00
Adrien Crivelli
769b161dff Identity map cannot contain null value (#10156) 2023-01-22 14:15:20 +07:00
Grégoire Paris
9857cf971b Merge pull request #10438 from greg0ire/2.15.x
Merge 2.14.x up into 2.15.x
2023-01-20 23:58:44 +01:00
Grégoire Paris
f73dae9bc4 Merge remote-tracking branch 'origin/2.14.x' into 2.15.x 2023-01-20 21:19:28 +01:00
Grégoire Paris
1c357b9fb3 Merge pull request #10426 from mpdude/parent-classes-docblock
Slight docblock improvements for `CM::parentClasses`
2023-01-20 12:46:34 +01:00
Matthias Pigulla
3d031ed541 Slight docblock improvements for CM::parentClasses 2023-01-20 11:26:28 +00:00
Grégoire Paris
15ed97b7b1 Merge pull request #10437 from greg0ire/update-baseline
Update Psalm baseline
2023-01-20 12:03:42 +01:00
Grégoire Paris
9fd4af23e1 Update Psalm baseline 2023-01-20 12:00:34 +01:00
Grégoire Paris
4206f01e7b Use FieldMapping array shape even more (#10430)
I experimented on converting FieldMapping to a DTO, and it allowed me to
find more places where it should be used.
2023-01-19 10:06:03 +01:00
Matthias Pigulla
7d4052c9e7 Fix version number in UPGRADE.md (#10428)
This was a mistake in #10423.
2023-01-19 08:23:27 +01:00
Grégoire Paris
fc6feb5938 Merge pull request #10423 from mpdude/deprecate-table-type-per-class
Add deprecations for "table per class" inheritance
2023-01-18 23:23:53 +01:00
Matthias Pigulla
89b98bdff9 Add deprecations for "table per class" inheritance 2023-01-18 16:30:57 +00:00
Grégoire Paris
37572802cc Reuse association mapping array shape (#10403) 2023-01-17 11:27:42 +01:00
Grégoire Paris
a5bdc619c7 Merge pull request #10408 from greg0ire/field-mapping-improvements
Field mapping improvements
2023-01-17 09:08:33 +01:00
Grégoire Paris
65a7c8882f Merge pull request #10413 from doctrine/2.14.x-merge-up-into-2.15.x_2sgwy6Oi
Merge release 2.14.1 into 2.15.x
2023-01-16 20:44:54 +01:00
Grégoire Paris
2d6295c9db Merge remote-tracking branch 'origin/2.14.x' into 2.15.x 2023-01-16 20:24:44 +01:00
Alexander M. Turek
f0616626e0 Test with a stable PHPUnit (#10406) 2023-01-16 15:53:19 +07:00
Matthias Pigulla
a8ef69dbe6 Factor out logic that tracks mapping inheritance (#10397) 2023-01-16 06:33:54 +04:00
Grégoire Paris
c0a7317e8d Reuse array shape 2023-01-15 21:55:32 +01:00
Grégoire Paris
843b0fcc16 Add missing fields 2023-01-15 21:55:32 +01:00
Grégoire Paris
b56de5b0e2 Type TypedFieldMapper API more precisely 2023-01-15 21:55:32 +01:00
Alexander M. Turek
0b35e637b8 Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  Stop allowing phpbench's master branch
2023-01-15 14:54:24 +07:00
Grégoire Paris
277614d9c2 Merge pull request #10400 from doctrine/2.14.x
Merge 2.14.x up into 2.15.x
2023-01-14 11:10:20 +01:00
Alexander M. Turek
29b8b0bffb Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  PHPStan 1.9.8, Psalm 5.4.0 (#10382)
  fix typo for missing a comma (#10377)
  Docs: Removing `type: 'integer'` from mappings (#10368)
  Docs: Moving *attributes* mapping to first position (#10364)
  Docs: Deleting duplicate mapping example (#10363)
2023-01-09 11:32:00 +07:00
Grégoire Paris
b7ba018f0a Use more precise types for class strings (#10381) 2023-01-09 11:29:56 +07:00
Grégoire Paris
72b1bf0c02 Use the same type as in the DBAL (#10372)
* Use the same type as in the DBAL

Implementations pass this parameter unchanged to DBAL methods.

* Update Psalm baseline
2023-01-04 19:32:28 +01:00
Grégoire Paris
70241d3407 Merge pull request #10365 from greg0ire/precise-return-type
Address new behavior of $firstResult
2023-01-03 23:55:01 +01:00
Grégoire Paris
1d5d47964c Address new behavior of $firstResult
Following 1915dcd1e8, 0 is now used as a
default value, and Query::$firstResult is no longer nullable, but it
seems getFirstResult() was overlooked. The class is final, so this is no
breaking change.
2023-01-03 21:29:09 +01:00
Alexander M. Turek
c9c4203a1e Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  PHPStan 1.9.5 (#10359)
2023-01-02 23:14:10 +01:00
Grégoire Paris
091da8c420 Merge pull request #10329 from greg0ire/drop-lexer-1-backport
Drop doctrine/lexer 1
2023-01-02 21:07:13 +01:00
Alexander M. Turek
515a3d8b8b Merge branch '2.14.x' into 2.15.x
* 2.14.x:
  Shorter deprecation message (#10357)
  Add Fully-Qualified class name in UnrecognizedField exception to ease debugging (#10342)
  Include parameter types in hydration cache key generation (#10355)
2022-12-31 17:45:59 +01:00
Grégoire Paris
603ab9a185 Drop doctrine/lexer 1 2022-12-31 00:20:27 +01:00
Alexander M. Turek
10d27c18ea Allow doctrine/instantiator 2 (#10351) 2022-12-30 19:52:12 +01:00
Alexander M. Turek
ae9fb8ca21 Merge 2.14.x into 2.15.x (#10344) 2022-12-28 17:23:15 +01:00
Rémi San
c7f2a1d580 Support of NOT expression from doctrine/collections ^2.1 (#10234) 2022-12-28 17:22:39 +01:00
Grégoire Paris
b3f441cb8b Merge pull request #10330 from doctrine/2.14.x
Merge 2.14.x up into 2.15.x
2022-12-20 20:07:49 +01:00
457 changed files with 9746 additions and 3340 deletions

View File

@@ -12,21 +12,33 @@
"upcoming": true
},
{
"name": "2.15",
"branchName": "2.15.x",
"slug": "2.15",
"name": "2.17",
"branchName": "2.17.x",
"slug": "2.17",
"upcoming": true
},
{
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"name": "2.16",
"branchName": "2.16.x",
"slug": "2.16",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"name": "2.15",
"branchName": "2.15.x",
"slug": "2.15",
"maintained": false
},
{
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"maintained": false
},
{
"name": "2.13",
"branchName": "2.13.x",

View File

@@ -39,6 +39,7 @@ jobs:
- "8.0"
- "8.1"
- "8.2"
- "8.3"
dbal-version:
- "default"
extension:
@@ -62,7 +63,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -111,6 +112,7 @@ jobs:
matrix:
php-version:
- "8.2"
- "8.3"
dbal-version:
- "default"
- "3@dev"
@@ -143,7 +145,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -183,6 +185,7 @@ jobs:
matrix:
php-version:
- "8.2"
- "8.3"
dbal-version:
- "default"
- "3@dev"
@@ -212,7 +215,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -252,6 +255,7 @@ jobs:
matrix:
php-version:
- "8.2"
- "8.3"
dbal-version:
- "default"
- "3@dev"
@@ -281,7 +285,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -333,7 +337,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -363,7 +367,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
with:
fetch-depth: 2

48
.github/workflows/documentation.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: "Documentation"
on:
pull_request:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
push:
branches:
- "*.x"
paths:
- .github/workflows/documentation.yml
- docs/**
jobs:
validate-with-guides:
name: "Validate documentation with phpDocumentor/guides"
runs-on: "ubuntu-22.04"
steps:
- name: "Checkout code"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
- name: "Remove existing composer file"
run: "rm composer.json"
- name: "Require phpdocumentor/guides-cli"
run: "composer require --dev phpdocumentor/guides-cli --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
dependency-versions: "highest"
- name: "Add dummy title to the sidebar"
run: |
printf '%s\n%s\n\n%s\n' "Dummy title" "===========" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
- name: "Run guides-cli"
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'Unknown directive' | ( ! grep WARNING )"

View File

@@ -36,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
with:
fetch-depth: 2

View File

@@ -42,7 +42,7 @@ jobs:
steps:
- name: "Checkout code"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -83,7 +83,7 @@ jobs:
steps:
- name: "Checkout code"
uses: "actions/checkout@v3"
uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"

View File

@@ -40,6 +40,11 @@ cd orm
composer install
```
You will also need to enable the PHP extension that provides the SQLite driver
for PDO: `pdo_sqlite`. How to do so depends on your system, but checking that it
is enabled can universally be done with `php -m`: that command should list the
extension.
To run the testsuite against another database, copy the ``phpunit.xml.dist``
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
take a look at the ``ci/github/phpunit`` directory for some examples. Then run:

View File

@@ -1,7 +1,7 @@
| [3.0.x][3.0] | [2.14.x][2.14] | [2.13.x][2.13] |
| [3.0.x][3.0] | [2.16.x][2.16] | [2.15.x][2.15] |
|:----------------:|:----------------:|:----------:|
| [![Build status][3.0 image]][3.0] | [![Build status][2.14 image]][2.14] | [![Build status][2.13 image]][2.13] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.14 coverage image]][2.14 coverage] | [![Coverage Status][2.13 coverage image]][2.13 coverage] |
| [![Build status][3.0 image]][3.0] | [![Build status][2.16 image]][2.16] | [![Build status][2.15 image]][2.15] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.16 coverage image]][2.16 coverage] | [![Coverage Status][2.15 coverage image]][2.15 coverage] |
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
@@ -22,11 +22,11 @@ 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.14 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.14.x
[2.14]: https://github.com/doctrine/orm/tree/2.14.x
[2.14 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.14.x/graph/badge.svg
[2.14 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.14.x
[2.13 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.13.x
[2.13]: https://github.com/doctrine/orm/tree/2.13.x
[2.13 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.13.x/graph/badge.svg
[2.13 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.13.x
[2.16 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.16.x
[2.16]: https://github.com/doctrine/orm/tree/2.16.x
[2.16 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.16.x/graph/badge.svg
[2.16 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.16.x
[2.15 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.15.x
[2.15]: https://github.com/doctrine/orm/tree/2.15.x
[2.15 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.15.x/graph/badge.svg
[2.15 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.15.x

View File

@@ -1,3 +1,87 @@
# Upgrade to 2.16
## Deprecated accepting duplicate IDs in the identity map
For any given entity class and ID value, there should be only one object instance
representing the entity.
In https://github.com/doctrine/orm/pull/10785, a check was added that will guard this
in the identity map. The most probable cause for violations of this rule are collisions
of application-provided IDs.
In ORM 2.16.0, the check was added by throwing an exception. In ORM 2.16.1, this will be
changed to a deprecation notice. ORM 3.0 will make it an exception again. Use
`\Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()` if you want to opt-in
to the new mode.
## Potential changes to the order in which `INSERT`s are executed
In https://github.com/doctrine/orm/pull/10547, the commit order computation was improved
to fix a series of bugs where a correct (working) commit order was previously not found.
Also, the new computation may get away with fewer queries being executed: By inserting
referred-to entities first and using their ID values for foreign key fields in subsequent
`INSERT` statements, additional `UPDATE` statements that were previously necessary can be
avoided.
When using database-provided, auto-incrementing IDs, this may lead to IDs being assigned
to entities in a different order than it was previously the case.
## Deprecated `\Doctrine\ORM\Internal\CommitOrderCalculator` and related classes
With changes made to the commit order computation, the internal classes
`\Doctrine\ORM\Internal\CommitOrderCalculator`, `\Doctrine\ORM\Internal\CommitOrder\Edge`,
`\Doctrine\ORM\Internal\CommitOrder\Vertex` and `\Doctrine\ORM\Internal\CommitOrder\VertexState`
have been deprecated and will be removed in ORM 3.0.
## Deprecated returning post insert IDs from `EntityPersister::executeInserts()`
Persisters implementing `\Doctrine\ORM\Persisters\Entity\EntityPersister` should no longer
return an array of post insert IDs from their `::executeInserts()` method. Make the
persister call `Doctrine\ORM\UnitOfWork::assignPostInsertId()` instead.
## Changing the way how reflection-based mapping drivers report fields, deprecated the "old" mode
In ORM 3.0, a change will be made regarding how the `AttributeDriver` reports field mappings.
This change is necessary to be able to detect (and reject) some invalid mapping configurations.
To avoid surprises during 2.x upgrades, the new mode is opt-in. It can be activated on the
`AttributeDriver` and `AnnotationDriver` by setting the `$reportFieldsWhereDeclared`
constructor parameter to `true`. It will cause `MappingException`s to be thrown when invalid
configurations are detected.
Not enabling the new mode will cause a deprecation notice to be raised. In ORM 3.0,
only the new mode will be available.
# Upgrade to 2.15
## Deprecated configuring `JoinColumn` on the inverse side of one-to-one associations
For one-to-one associations, the side using the `mappedBy` attribute is the inverse side.
The owning side is the entity with the table containing the foreign key. Using `JoinColumn`
configuration on the _inverse_ side now triggers a deprecation notice and will be an error
in 3.0.
## Deprecated overriding fields or associations not declared in mapped superclasses
As stated in the documentation, fields and associations may only be overridden when being inherited
from mapped superclasses. Overriding them for parent entity classes now triggers a deprecation notice
and will be an error in 3.0.
## Deprecated undeclared entity inheritance
As soon as an entity class inherits from another entity class, inheritance has to
be declared by adding the appropriate configuration for the root entity.
## Deprecated stubs for "concrete table inheritance"
This third way of mapping class inheritance was never implemented. Code stubs are
now deprecated and will be removed in 3.0.
* `\Doctrine\ORM\Mapping\ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS` constant
* `\Doctrine\ORM\Mapping\ClassMetadataInfo::isInheritanceTypeTablePerClass()` method
* Using `TABLE_PER_CLASS` as the value for the `InheritanceType` attribute or annotation
or in XML configuration files.
# Upgrade to 2.14
## Deprecated `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byName($field)` method.

View File

@@ -24,14 +24,14 @@
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/cache": "^1.12.1 || ^2.1.1",
"doctrine/collections": "^1.5 || ^2.0",
"doctrine/collections": "^1.5 || ^2.1",
"doctrine/common": "^3.0.3",
"doctrine/dbal": "^2.13.1 || ^3.2",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3",
"doctrine/lexer": "^1.2.3 || ^2",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^2",
"doctrine/persistence": "^2.4 || ^3",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^4.2 || ^5.0 || ^6.0",
@@ -40,16 +40,16 @@
},
"require-dev": {
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^11.0",
"doctrine/coding-standard": "^9.0.2 || ^12.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.10.6",
"phpstan/phpstan": "~1.4.10 || 1.10.35",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
"vimeo/psalm": "4.30.0 || 5.9.0"
"vimeo/psalm": "4.30.0 || 5.15.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"

View File

@@ -211,7 +211,7 @@ Now look at the following test-code for our entities:
{
public function testAddEntry()
{
$account = new Account("123456", $maxCredit = 200);
$account = new Account("123456", maxCredit: 200);
$this->assertEquals(0, $account->getBalance());
$account->addEntry(500);
@@ -223,7 +223,7 @@ Now look at the following test-code for our entities:
public function testExceedMaxLimit()
{
$account = new Account("123456", $maxCredit = 200);
$account = new Account("123456", maxCredit: 200);
$this->expectException(Exception::class);
$account->addEntry(-1000);

View File

@@ -13,7 +13,7 @@ for all our domain objects.
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
(\ `Details <https://github.com/doctrine/orm/issues/8383>`_)
Implementing NotifyPropertyChanged
----------------------------------

View File

@@ -58,6 +58,10 @@ First Attributes:
.. code-block:: php
<?php
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Doctrine\ORM\Mapping\PrePersist;
use Doctrine\ORM\Mapping\PreUpdate;
#[Entity]
#[HasLifecycleCallbacks]

View File

@@ -18,8 +18,8 @@ Doctrine ORM don't panic. You can get help from different sources:
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
If you need more structure over the different topics you can browse the :doc:`table
of contents <toc>`.
If you need more structure over the different topics you can browse the table
of contents.
Getting Started
---------------
@@ -34,32 +34,32 @@ Mapping Objects onto a Database
-------------------------------
* **Mapping**:
:doc:`Objects <reference/basic-mapping>` |
:doc:`Associations <reference/association-mapping>` |
:doc:`Objects <reference/basic-mapping>` \|
:doc:`Associations <reference/association-mapping>` \|
:doc:`Inheritance <reference/inheritance-mapping>`
* **Drivers**:
:doc:`Docblock Annotations <reference/annotations-reference>` |
:doc:`Attributes <reference/attributes-reference>` |
:doc:`XML <reference/xml-mapping>` |
:doc:`YAML <reference/yaml-mapping>` |
:doc:`Docblock Annotations <reference/annotations-reference>` \|
:doc:`Attributes <reference/attributes-reference>` \|
:doc:`XML <reference/xml-mapping>` \|
:doc:`YAML <reference/yaml-mapping>` \|
:doc:`PHP <reference/php-mapping>`
Working with Objects
--------------------
* **Basic Reference**:
:doc:`Entities <reference/working-with-objects>` |
:doc:`Associations <reference/working-with-associations>` |
:doc:`Entities <reference/working-with-objects>` \|
:doc:`Associations <reference/working-with-associations>` \|
:doc:`Events <reference/events>`
* **Query Reference**:
:doc:`DQL <reference/dql-doctrine-query-language>` |
:doc:`QueryBuilder <reference/query-builder>` |
:doc:`DQL <reference/dql-doctrine-query-language>` \|
:doc:`QueryBuilder <reference/query-builder>` \|
:doc:`Native SQL <reference/native-sql>`
* **Internals**:
:doc:`Internals explained <reference/unitofwork>` |
:doc:`Internals explained <reference/unitofwork>` \|
:doc:`Associations <reference/unitofwork-associations>`
Advanced Topics
@@ -102,20 +102,20 @@ Cookbook
--------
* **Patterns**:
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
* **DQL Extension Points**:
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` \|
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
* **Implementation**:
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
:doc:`Validation <cookbook/validation-of-entities>` |
:doc:`Entities in the Session <cookbook/entities-in-session>` |
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` \|
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` \|
:doc:`Working with DateTime <cookbook/working-with-datetime>` \|
:doc:`Validation <cookbook/validation-of-entities>` \|
:doc:`Entities in the Session <cookbook/entities-in-session>` \|
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
* **Hidden Gems**
@@ -124,5 +124,3 @@ Cookbook
* **Custom Datatypes**
:doc:`MySQL Enums <cookbook/mysql-enums>`
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
.. include:: toc.rst

View File

@@ -29,7 +29,7 @@ steps of configuration.
$config = new Configuration;
$config->setMetadataCache($metadataCache);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$config->setMetadataDriverImpl($driverImpl);
$config->setQueryCache($queryCache);
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
@@ -134,7 +134,7 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
<?php
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
$config->setMetadataDriverImpl($driverImpl);
The path information to the entities is required for the attribute
@@ -311,10 +311,12 @@ Reference Proxies
The method ``EntityManager#getReference($entityName, $identifier)``
lets you obtain a reference to an entity for which the identifier
is known, without loading that entity from the database. This is
useful, for example, as a performance enhancement, when you want to
establish an association to an entity for which you have the
identifier. You could simply do this:
is known, without necessarily loading that entity from the database.
This is useful, for example, as a performance enhancement, when you
want to establish an association to an entity for which you have the
identifier.
Consider the following example:
.. code-block:: php
@@ -324,15 +326,33 @@ identifier. You could simply do this:
$item = $em->getReference('MyProject\Model\Item', $itemId);
$cart->addItem($item);
Here, we added an Item to a Cart without loading the Item from the
database. If you access any state that isn't yet available in the
Item instance, the proxying mechanism would fully initialize the
object's state transparently from the database. Here
$item is actually an instance of the proxy class that was generated
for the Item class but your code does not need to care. In fact it
**should not care**. Proxy objects should be transparent to your
Whether the object being returned from ``EntityManager#getReference()``
is a proxy or a direct instance of the entity class may depend on different
factors, including whether the entity has already been loaded into memory
or entity inheritance being used. But your code does not need to care
and in fact it **should not care**. Proxy objects should be transparent to your
code.
When using the ``EntityManager#getReference()`` method, you need to be aware
of a few peculiarities.
At the best case, the ORM can avoid querying the database at all. But, that
also means that this method will not throw an exception when an invalid value
for the ``$identifier`` parameter is passed. ``$identifier`` values are
not checked and there is no guarantee that the requested entity instance even
exists the method will still return a proxy object.
Its only when the proxy has to be fully initialized or associations cannot
be written to the database that invalid ``$identifier`` values may lead to
exceptions.
The ``EntityManager#getReference()`` is mostly useful when you only
need a reference to some entity to make an association, like in the example
above. In that case, it can save you from loading data from the database
that you don't need. But remember as soon as you read any property values
besides those making up the ID, a database request will be made to initialize
all fields.
Association proxies
~~~~~~~~~~~~~~~~~~~

View File

@@ -545,12 +545,12 @@ has meaning in the SchemaTool schema generation context.
Required attributes:
- **name**: Name of the Index
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
Optional attributes:
- **name**: Name of the Index. If not provided, a generated name will be assigned.
- **options**: Array of platform specific options:
- ``where``: SQL WHERE condition to be used for partial indexes. It will
@@ -1316,12 +1316,12 @@ context.
Required attributes:
- **name**: Name of the Index
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
Optional attributes:
- **name**: Name of the Index. If not provided, a generated name will be assigned.
- **options**: Array of platform specific options:
- ``where``: SQL WHERE condition to be used for partial indexes. It will

View File

@@ -24,28 +24,34 @@ performance it is also recommended that you use APC with PHP.
Doctrine ORM Packages
-------------------
Doctrine ORM is divided into three main packages.
Doctrine ORM is divided into four main packages.
- Common
- DBAL (includes Common)
- ORM (includes DBAL+Common)
- `Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html>`_
- `Event Manager <https://www.doctrine-project.org/projects/doctrine-event-manager/en/stable/index.html>`_
- `Persistence <https://www.doctrine-project.org/projects/doctrine-persistence/en/stable/index.html>`_
- `DBAL <https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/index.html>`_
- ORM (depends on DBAL+Persistence+Collections)
This manual mainly covers the ORM package, sometimes touching parts
of the underlying DBAL and Common packages. The Doctrine code base
of the underlying DBAL and Persistence packages. The Doctrine code base
is split in to these packages for a few reasons and they are to...
- ...make things more maintainable and decoupled
- ...allow you to use the code in Doctrine Common without the ORM
or DBAL
- ...allow you to use the code in Doctrine Persistence and Collections
without the ORM or DBAL
- ...allow you to use the DBAL without the ORM
The Common Package
~~~~~~~~~~~~~~~~~~
Collection, Event Manager and Persistence
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Common package contains highly reusable components that have no
dependencies beyond the package itself (and PHP, of course). The
root namespace of the Common package is ``Doctrine\Common``.
The Collection, Event Manager and Persistence packages contain highly
reusable components that have no dependencies beyond the packages
themselves (and PHP, of course). The root namespace of the Persistence
package is ``Doctrine\Persistence``. The root namespace of the
Collection package is ``Doctrine\Common\Collections``, for historical
reasons. The root namespace of the Event Manager package is just
``Doctrine\Common``, also for historical reasons.
The DBAL Package
~~~~~~~~~~~~~~~~
@@ -102,7 +108,7 @@ persistent entity state and mapping information for its subclasses,
but which is not itself an entity.
Mapped superclasses are explained in greater detail in the chapter
on :doc:`inheritance mapping <reference/inheritance-mapping>`.
on :doc:`inheritance mapping </reference/inheritance-mapping>`.
Transient Classes
~~~~~~~~~~~~~~~~~
@@ -199,5 +205,3 @@ typical implementation of the
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.

View File

@@ -881,6 +881,15 @@ Generated MySQL Schema:
replaced by one-to-many/many-to-one associations between the 3
participating classes.
.. note::
For many-to-many associations, the ORM takes care of managing rows
in the join table connecting both sides. Due to the way it deals
with entity removals, database-level constraints may not work the
way one might intuitively assume. Thus, be sure not to miss the section
on :ref:`join table management <remove_object_many_to_many_join_tables>`.
Many-To-Many, Bidirectional
---------------------------
@@ -1019,6 +1028,15 @@ one is bidirectional.
The MySQL schema is exactly the same as for the Many-To-Many
uni-directional case above.
.. note::
For many-to-many associations, the ORM takes care of managing rows
in the join table connecting both sides. Due to the way it deals
with entity removals, database-level constraints may not work the
way one might intuitively assume. Thus, be sure not to miss the section
on :ref:`join table management <remove_object_many_to_many_join_tables>`.
Owning and Inverse Side on a ManyToMany Association
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -368,6 +368,7 @@ Optional parameters:
- **length**: By default this is 255.
- **columnDefinition**: By default this is null the definition according to the type will be used. This option allows to override it.
- **enumType**: By default this is `null`. Allows to map discriminatorColumn value to PHP enum
- **options**: See "options" attribute on :ref:`#[Column] <attrref_column>`.
.. _attrref_discriminatormap:
@@ -539,13 +540,13 @@ has meaning in the ``SchemaTool`` schema generation context.
Required parameters:
- **name**: Name of the Index
- **fields**: Array of fields. Exactly one of **fields, columns** is required.
- **columns**: Array of columns. Exactly one of **fields, columns** is required.
Optional parameters:
- **name**: Name of the Index. If not provided, a generated name will be assigned.
- **options**: Array of platform specific options:
- ``where``: SQL WHERE condition to be used for partial indexes. It will
@@ -575,7 +576,7 @@ Example with partial indexes:
#[Index(name: "search_idx", columns: ["category"],
options: [
"where": "((category IS NOT NULL))"
"where" => "((category IS NOT NULL))"
]
)]
class ECommerceProduct
@@ -1103,11 +1104,12 @@ context.
Required parameters:
- **name**: Name of the Index
- **columns**: Array of columns.
- **fields**: Array of fields (the names of the properties, used in the entity class).
- **columns**: Array of columns (the names of the columns, used in the schema).
Optional parameters:
- **name**: Name of the Index. If not provided, a generated name will be assigned.
- **options**: Array of platform specific options:
- ``where``: SQL WHERE condition to be used for partial indexes. It will

View File

@@ -47,8 +47,7 @@ mapping metadata:
- :doc:`Attributes <attributes-reference>`
- :doc:`XML <xml-mapping>`
- :doc:`PHP code <php-mapping>`
- :doc:`Docblock Annotations <annotations-reference>` (deprecated and
will be removed in ``doctrine/orm`` 3.0)
- :doc:`Docblock Annotations <annotations-reference>` (deprecated and will be removed in ``doctrine/orm`` 3.0)
- :doc:`YAML <yaml-mapping>` (deprecated and will be removed in ``doctrine/orm`` 3.0.)
This manual will usually show mapping metadata via attributes, though
@@ -460,7 +459,7 @@ Here is the list of possible generation strategies:
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the ``#[GeneratedValue]`` entirely.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
It will allow you to pass a :ref:`class of your own to generate the identifiers.<annref_customidgenerator>`
Sequence Generator
^^^^^^^^^^^^^^^^^^

View File

@@ -86,7 +86,7 @@ with the batching strategy that was already used for bulk inserts:
<?php
$batchSize = 20;
$i = 1;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
foreach ($q->toIterable() as $user) {
$user->increaseCredit();
@@ -145,7 +145,7 @@ The following example shows how to do this:
<?php
$batchSize = 20;
$i = 1;
$i = 0;
$q = $em->createQuery('select u from MyProject\Model\User u');
foreach($q->toIterable() as $row) {
$em->remove($row);

View File

@@ -63,7 +63,7 @@ Notify
.. note::
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
(\ `Details <https://github.com/doctrine/orm/issues/8383>`_)
This policy is based on the assumption that the entities notify
interested listeners of changes to their properties. For that

View File

@@ -104,7 +104,7 @@ Inside the ``ORMSetup`` methods several assumptions are made:
In order to have ``ORMSetup`` configure the cache automatically, the library ``symfony/cache``
has to be installed as a dependency.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration </reference/advanced-configuration>` section.
.. note::

View File

@@ -1304,14 +1304,13 @@ creating a class which extends ``AbstractHydrator``:
<?php
namespace MyProject\Hydrators;
use Doctrine\DBAL\FetchMode;
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
class CustomHydrator extends AbstractHydrator
{
protected function _hydrateAll()
{
return $this->_stmt->fetchAll(FetchMode::FETCH_ASSOC);
return $this->_stmt->fetchAllAssociative();
}
}
@@ -1337,8 +1336,8 @@ There are situations when a query you want to execute returns a
very large result-set that needs to be processed. All the
previously described hydration modes completely load a result-set
into memory which might not be feasible with large result sets. See
the `Batch Processing <batch-processing.html>`_ section on details how
to iterate large result sets.
the :doc:`Batch Processing </reference/batch-processing>` section on
details how to iterate large result sets.
Functions
~~~~~~~~~
@@ -1382,7 +1381,7 @@ Result Cache API:
$query->setResultCacheDriver(new ApcCache());
$query->useResultCache(true)
->setResultCacheLifeTime($seconds = 3600);
->setResultCacheLifeTime(3600);
$result = $query->getResult(); // cache miss
@@ -1393,7 +1392,7 @@ Result Cache API:
$result = $query->getResult(); // saved in given result cache id.
// or call useResultCache() with all parameters:
$query->useResultCache(true, $seconds = 3600, 'my_query_result');
$query->useResultCache(true, 3600, 'my_query_result');
$result = $query->getResult(); // cache hit!
// Introspection
@@ -1426,7 +1425,7 @@ userland:
reloading this data. Partially loaded objects have to be passed to
``EntityManager::refresh()`` if they are to be reloaded fully from
the database. This query hint is deprecated and will be removed
in the future (`Details <https://github.com/doctrine/orm/issues/8471>`_)
in the future (\ `Details <https://github.com/doctrine/orm/issues/8471>`_)
- ``Query::HINT_REFRESH`` - This query is used internally by
``EntityManager::refresh()`` and can be used in userland as well.
If you specify this hint and a query returns the data for an entity
@@ -1458,7 +1457,7 @@ several methods to interact with it:
- ``Query::setQueryCacheDriver($driver)`` - Allows to set a Cache
instance
- ``Query::setQueryCacheLifeTime($seconds = 3600)`` - Set lifetime
- ``Query::setQueryCacheLifeTime($seconds)`` - Set lifetime
of the query caching.
- ``Query::expireQueryCache($bool)`` - Enforce the expiring of the
query cache if set to true.

View File

@@ -215,6 +215,10 @@ specific to a particular entity class's lifecycle.
<?php
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Event\PrePersistEventArgs;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
use Doctrine\ORM\Mapping\PrePersist;
use Doctrine\ORM\Mapping\PreUpdate;
#[Entity]
#[HasLifecycleCallbacks]
@@ -281,10 +285,10 @@ specific to a particular entity class's lifecycle.
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="User">
<!-- ... -->
<lifecycle-callbacks>
@@ -453,13 +457,12 @@ 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 :ref:`cascaded associations<transitive-persistence>`.
- The other is inside the
``flush()`` method when changes to associations are computed and
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 :ref:`persistence by reachability<persistence-by-reachability>`.
- One is 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 :ref:`cascade: persist<transitive-persistence>`. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
In both cases you get passed a ``PrePersistEventArgs`` instance
which has access to the entity and the entity manager.
@@ -705,13 +708,21 @@ 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 ``postPersist`` event occurs for an entity after the entity has
been made persistent. It will be invoked after all database insert
operations for new entities have been performed. Generated primary
key values will be available for all entities at the time this
event is triggered.
- 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.
entity has been deleted. It will be invoked after all database
delete operations for entity rows have been executed. This event is
not called for a DQL ``DELETE`` statement.
.. note::
At the time ``postPersist`` is called, there may still be collection and/or
"extra" updates pending. The database may not yet be completely in
sync with the entity states in memory, not even for the new entities.
.. warning::
@@ -720,6 +731,19 @@ not directly mapped by Doctrine.
cascade remove relations. In this case, you should load yourself the proxy in
the associated ``pre*`` event.
.. warning::
Making changes to entities and calling ``EntityManager::flush()`` from within
``post*`` event handlers is strongly discouraged, and might be deprecated and
eventually prevented in the future.
The reason is that it causes re-entrance into ``UnitOfWork::commit()`` while a commit
is currently being processed. The ``UnitOfWork`` was never designed to support this,
and its behavior in this situation is not covered by any tests.
This may lead to entity or collection updates being missed, applied only in parts and
changes being lost at the end of the commit phase.
.. _reference-events-post-load:
postLoad

View File

@@ -13,11 +13,10 @@ Database Schema
How do I set the charset and collation for MySQL tables?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can't set these values with attributes, annotations or inside yml or
xml mapping files. To make a database work with the default charset and
collation you should configure MySQL to use it as default charset, or
create the database with charset and collation details. This way they
get inherited to all newly created database tables and columns.
In your mapping configuration, the column definition (for example, the
``#[Column]`` attribute) has an ``options`` parameter where you can specify
the ``charset`` and ``collation``. The default values are ``utf8`` and
``utf8_unicode_ci``, respectively.
Entity Classes
--------------

View File

@@ -93,3 +93,34 @@ object.
want to refresh or reload an object after having modified a filter or the
FilterCollection, then you should clear the EntityManager and re-fetch your
entities, having the new rules for filtering applied.
Suspending/Restoring Filters
----------------------------
When a filter is disabled, the instance is fully deleted and all the filter
parameters previously set are lost. Then, if you enable it again, a new filter
is created without the previous filter parameters. If you want to keep a filter
(in order to use it later) but temporary disable it, you'll need to use the
``FilterCollection#suspend($name)`` and ``FilterCollection#restore($name)``
methods instead.
.. code-block:: php
<?php
$filter = $em->getFilters()->enable("locale");
$filter->setParameter('locale', 'en');
// Temporary suspend the filter
$filter = $em->getFilters()->suspend("locale");
// Do things
// Then restore it, the locale parameter will still be set
$filter = $em->getFilters()->restore("locale");
.. warning::
If you enable a previously disabled filter, doctrine will create a new
one without keeping any of the previously parameter set with
``SQLFilter#setParameter()`` or ``SQLFilter#getParameterList()``. If you
want to restore the previously disabled filter instead, you must use the
``FilterCollection#restore($name)`` method.

View File

@@ -91,7 +91,7 @@ Apply Best Practices
A lot of the points mentioned in the Best Practices chapter will
also positively affect the performance of Doctrine.
See :doc:`Best Practices <reference/best-practices>`
See :doc:`Best Practices </reference/best-practices>`
Change Tracking policies
------------------------

View File

@@ -15,27 +15,37 @@ is common to multiple entity classes.
Mapped superclasses, just as regular, non-mapped classes, can
appear in the middle of an otherwise mapped inheritance hierarchy
(through Single Table Inheritance or Class Table Inheritance).
(through Single Table Inheritance or Class Table Inheritance). They
are not query-able, and need not have an ``#[Id]`` property.
No database table will be created for a mapped superclass itself,
only for entity classes inheriting from it. Also, a mapped superclass
need not have an ``#[Id]`` property.
only for entity classes inheriting from it. That implies that a
mapped superclass cannot be the ``targetEntity`` in associations.
In other words, a mapped superclass can use unidirectional One-To-One
and Many-To-One associations where it is the owning side.
Many-To-Many associations are only possible if the mapped
superclass is only used in exactly one entity at the moment. For further
support of inheritance, the single or joined table inheritance features
have to be used.
.. note::
A mapped superclass cannot be an entity, it is not query-able and
persistent relationships defined by a mapped superclass must be
unidirectional (with an owning side only). This means that One-To-Many
associations are not possible on a mapped superclass at all.
Furthermore Many-To-Many associations are only possible if the
mapped superclass is only used in exactly one entity at the moment.
For further support of inheritance, the single or
joined table inheritance features have to be used.
One-To-Many associations are not generally possible on a mapped
superclass, since they require the "many" side to hold the foreign
key.
It is, however, possible to use the :doc:`ResolveTargetEntityListener </cookbook/resolve-target-entity-listener>`
to replace references to a mapped superclass with an entity class at runtime.
As long as there is only one entity subclass inheriting from the mapped
superclass and all references to the mapped superclass are resolved to that
entity class at runtime, the mapped superclass *can* use One-To-Many associations
and be named as the ``targetEntity`` on the owning sides.
.. warning::
At least when using attributes or annotations to specify your mapping,
it _seems_ as if you could inherit from a base class that is neither
it *seems* as if you could inherit from a base class that is neither
an entity nor a mapped superclass, but has properties with mapping configuration
on them that would also be used in the inheriting class.
@@ -50,7 +60,7 @@ need not have an ``#[Id]`` property.
You may be tempted to use traits to mix mapped fields or relationships
into your entity classes to circumvent some of the limitations of
mapped superclasses. Before doing that, please read the section on traits
in the :doc:`Limitations and Known Issues <reference/limitations-and-known-issues>` chapter.
in the :doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
Example:
@@ -370,7 +380,7 @@ It is not supported to use overrides in entity inheritance scenarios.
.. note::
When using traits, make sure not to miss the warnings given in the
:doc:`Limitations and Known Issues<reference/limitations-and-known-issues>` chapter.
:doc:`Limitations and Known Issues</reference/limitations-and-known-issues>` chapter.
Association Override

View File

@@ -1,4 +1,4 @@
Installation
============
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
The installation chapter has moved to :doc:`Installation and Configuration </reference/configuration>`.

View File

@@ -145,7 +145,7 @@ more than two years after the initial Doctrine 2 release and the time where
core components were designed.
In fact, this documentation mentions traits only in the context of
:doc:`overriding field association mappings in subclasses <tutorials/override-field-association-mappings-in-subclasses>`.
:doc:`overriding field association mappings in subclasses </tutorials/override-field-association-mappings-in-subclasses>`.
Coverage of traits in test cases is practically nonexistent.
Thus, you should at least be aware that when using traits in your entity and
@@ -162,14 +162,14 @@ that, some precedence and conflict resolution rules apply.
When it comes to loading mapping configuration, the annotation and attribute drivers
rely on PHP reflection to inspect class properties including their docblocks.
As long as the results are consistent with what a solution _without_ traits would
As long as the results are consistent with what a solution *without* traits would
have produced, this is probably fine.
However, to mention known limitations, it is currently not possible to use "class"
level `annotations <https://github.com/doctrine/orm/pull/1517>` or
`attributes <https://github.com/doctrine/orm/issues/8868>` on traits, and attempts to
improve parser support for traits as `here <https://github.com/doctrine/annotations/pull/102>`
or `there <https://github.com/doctrine/annotations/pull/63>` have been abandoned
level `annotations <https://github.com/doctrine/orm/pull/1517>`_ or
`attributes <https://github.com/doctrine/orm/issues/8868>`_ on traits, and attempts to
improve parser support for traits as `here <https://github.com/doctrine/annotations/pull/102>`_
or `there <https://github.com/doctrine/annotations/pull/63>`_ have been abandoned
due to complexity.
XML mapping configuration probably needs to completely re-configure or otherwise

View File

@@ -123,12 +123,12 @@ the ``FileDriver`` implementation for you to extend from:
class MyMetadataDriver extends FileDriver
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected $_fileExtension = '.dcm.ext';
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadMetadataForClass($className, ClassMetadata $metadata)
{
@@ -138,7 +138,7 @@ the ``FileDriver`` implementation for you to extend from:
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function _loadMappingFile($file)
{

View File

@@ -250,6 +250,40 @@ The first parameter is the name of the column in the SQL result set
and the second parameter is the result alias under which the value
of the column will be placed in the transformed Doctrine result.
Special case: DTOs
...................
You can also use ``ResultSetMapping`` to map the results of a native SQL
query to a DTO (Data Transfer Object). This is done by adding scalar
results for each argument of the DTO's constructor, then filling the
``newObjectMappings`` property of the ``ResultSetMapping`` with
information about where to map each scalar result:
.. code-block:: php
<?php
$rsm = new ResultSetMapping();
$rsm->addScalarResult('name', 1, 'string');
$rsm->addScalarResult('email', 2, 'string');
$rsm->addScalarResult('city', 3, 'string');
$rsm->newObjectMappings['name'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0, // a result can contain many DTOs, this is the index of the DTO to map to
'argIndex' => 0, // each scalar result can be mapped to a different argument of the DTO constructor
];
$rsm->newObjectMappings['email'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 1,
];
$rsm->newObjectMappings['city'] = [
'className' => CmsUserDTO::class,
'objIndex' => 0,
'argIndex' => 2,
];
Meta results
~~~~~~~~~~~~

View File

@@ -6,7 +6,7 @@ Partial Objects
Creating Partial Objects through DQL is deprecated and
will be removed in the future, use data transfer object
support in DQL instead. (`Details
support in DQL instead. (\ `Details
<https://github.com/doctrine/orm/issues/8471>`_)
A partial object is an object whose state is not fully initialized

View File

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

View File

@@ -253,7 +253,7 @@ Calling ``setParameter()`` automatically infers which type you are setting as
value. This works for integers, arrays of strings/integers, DateTime instances
and for managed entities. If you want to set a type explicitly you can call
the third argument to ``setParameter()`` explicitly. It accepts either a DBAL
Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
``Doctrine\DBAL\ParameterType::*`` or a DBAL Type name for conversion.
.. note::
@@ -578,8 +578,6 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
not (no effect on the ``where`` and ``having`` DQL query parts,
which always override all previously defined items)
-
.. code-block:: php
<?php

View File

@@ -322,7 +322,10 @@ level cache region.
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Country">
<cache usage="READ_ONLY" region="my_entity_region" />
<id name="id" type="integer" column="id">
@@ -427,7 +430,10 @@ It caches the primary keys of association and cache each element will be cached
.. code-block:: xml
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="State">
<cache usage="NONSTRICT_READ_WRITE" />

View File

@@ -153,7 +153,7 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMap
final class CustomEnumTypedFieldMapper implements TypedFieldMapper
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{

View File

@@ -37,8 +37,8 @@ will still end up with the same reference:
public function testIdentityMapReference(): void
{
$objectA = $this->entityManager->getReference('EntityName', 1);
// check for proxyinterface
$this->assertInstanceOf('Doctrine\Persistence\Proxy', $objectA);
// check entity is not initialized
$this->assertTrue($this->entityManager->isUninitializedObject($objectA));
$objectB = $this->entityManager->find('EntityName', 1);
@@ -137,7 +137,7 @@ optimize the performance of the Flush Operation:
.. note::
Flush only a single entity with ``$entityManager->flush($entity)`` is deprecated and will be removed in ORM 3.0.
(`Details <https://github.com/doctrine/orm/issues/8459>`_)
(\ `Details <https://github.com/doctrine/orm/issues/8459>`_)
Query Internals
---------------

View File

@@ -718,6 +718,7 @@ methods:
* ``andX($arg1, $arg2, ...)``
* ``orX($arg1, $arg2, ...)``
* ``not($expression)``
* ``eq($field, $value)``
* ``gt($field, $value)``
* ``lt($field, $value)``

View File

@@ -192,6 +192,11 @@ be properly synchronized with the database when
database in the most efficient way and a single, short transaction,
taking care of maintaining referential integrity.
.. note::
Do not make any assumptions in your code about the number of queries
it takes to flush changes, about the ordering of ``INSERT``, ``UPDATE``
and ``DELETE`` queries or the order in which entities will be processed.
Example:
@@ -286,17 +291,53 @@ as follows:
After an entity has been removed, its in-memory state is the same as
before the removal, except for generated identifiers.
Removing an entity will also automatically delete any existing
records in many-to-many join tables that link this entity. The
action taken depends on the value of the ``@joinColumn`` mapping
attribute "onDelete". Either Doctrine issues a dedicated ``DELETE``
statement for records of each join table or it depends on the
foreign key semantics of onDelete="CASCADE".
During the ``EntityManager#flush()`` operation, the removed entity
will also be removed from all collections in entities currently
loaded into memory.
.. _remove_object_many_to_many_join_tables:
Join-table management when removing from many-to-many collections
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Regarding existing rows in many-to-many join tables that refer to
an entity being removed, the following applies.
When the entity being removed does not declare the many-to-many association
itself (that is, the many-to-many association is unidirectional and
the entity is on the inverse side), the ORM has no reasonable way to
detect associations targeting the entity's class. Thus, no ORM-level handling
of join-table rows is attempted and database-level constraints apply.
In case of database-level ``ON DELETE RESTRICT`` constraints, the
``EntityManager#flush()`` operation may abort and a ``ConstraintViolationException``
may be thrown. No in-memory collections will be modified in this case.
With ``ON DELETE CASCADE``, the RDBMS will take care of removing rows
from join tables.
When the entity being removed is part of bi-directional many-to-many
association, either as the owning or inverse side, the ORM will
delete rows from join tables before removing the entity itself. That means
database-level ``ON DELETE RESTRICT`` constraints on join tables are not
effective, since the join table rows are removed first. Removal of join table
rows happens through specialized methods in entity and collection persister
classes and take one query per entity and join table. In case the association
uses a ``@JoinColumn`` configuration with ``onDelete="CASCADE"``, instead
of using a dedicated ``DELETE`` query the database-level operation will be
relied upon.
.. note::
In case you rely on database-level ``ON DELETE RESTRICT`` constraints,
be aware that by making many-to-many associations bidirectional the
assumed protection may be lost.
Performance of different deletion strategies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Deleting an object with all its associated objects can be achieved
in multiple ways with very different performance impacts.
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM
will fetch this association. If its a Single association it will
pass this entity to

View File

@@ -16,9 +16,9 @@ setup for the latest code in trunk.
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
...
@@ -102,9 +102,9 @@ of several common elements:
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
@@ -769,9 +769,9 @@ entity relationship. You can define this in XML with the "association-key" attri
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Application\Model\ArticleAttribute">

View File

@@ -1,86 +0,0 @@
Welcome to Doctrine 2 ORM's documentation!
==========================================
Tutorials
---------
.. toctree::
:maxdepth: 1
tutorials/getting-started
tutorials/getting-started-database
tutorials/getting-started-models
tutorials/working-with-indexed-associations
tutorials/extra-lazy-associations
tutorials/composite-primary-keys
tutorials/ordered-associations
tutorials/override-field-association-mappings-in-subclasses
tutorials/pagination.rst
tutorials/embeddables.rst
Reference Guide
---------------
.. toctree::
:maxdepth: 1
:numbered:
reference/architecture
reference/configuration.rst
reference/faq
reference/basic-mapping
reference/association-mapping
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/events
reference/unitofwork
reference/unitofwork-associations
reference/transactions-and-concurrency
reference/batch-processing
reference/dql-doctrine-query-language
reference/query-builder
reference/native-sql
reference/change-tracking-policies
reference/partial-objects
reference/annotations-reference
reference/attributes-reference
reference/xml-mapping
reference/yaml-mapping
reference/php-mapping
reference/caching
reference/improving-performance
reference/tools
reference/metadata-drivers
reference/best-practices
reference/limitations-and-known-issues
tutorials/pagination
reference/filters
reference/namingstrategy
reference/advanced-configuration
reference/second-level-cache
reference/security
Cookbook
--------
.. toctree::
:maxdepth: 1
cookbook/aggregate-fields
cookbook/custom-mapping-types
cookbook/decorator-pattern
cookbook/dql-custom-walkers
cookbook/dql-user-defined-functions
cookbook/implementing-arrayaccess-for-domain-objects
cookbook/implementing-the-notify-changetracking-policy
cookbook/resolve-target-entity-listener
cookbook/sql-table-prefixes
cookbook/strategy-cookbook-introduction
cookbook/validation-of-entities
cookbook/working-with-datetime
cookbook/mysql-enums
cookbook/advanced-field-value-conversion-using-custom-mapping-types
cookbook/entities-in-session

View File

@@ -85,9 +85,9 @@ and year of production as primary keys:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="VehicleCatalogue\Model\Car">
@@ -267,9 +267,9 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Application\Model\ArticleAttribute">

View File

@@ -85,9 +85,9 @@ switch to extra lazy as shown in these examples:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">

View File

@@ -22,5 +22,5 @@ In this workflow you would modify the database schema first and then
regenerate the PHP code to use with this schema. You need a flexible
code-generator for this task.
We spinned off a subproject, Doctrine CodeGenerator, that will fill this gap and
We spun off a subproject, Doctrine CodeGenerator, that will fill this gap and
allow you to do *Database First* development.

View File

@@ -18,7 +18,7 @@ before. There are some prerequisites for the tutorial that have to be
installed:
- PHP (latest stable version)
- Composer Package Manager (`Install Composer
- Composer Package Manager (\ `Install Composer
<https://getcomposer.org/doc/00-intro.md>`_)
The code of this tutorial is `available on Github <https://github.com/doctrine/doctrine2-orm-tutorial>`_.
@@ -102,8 +102,7 @@ Install Doctrine using the Composer Dependency Management tool, by calling:
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
into the ``vendor`` directory.
Add the following directories:
::
Add the following directories::
doctrine2-tutorial
|-- config
@@ -322,7 +321,7 @@ data in your storage, and later in your application when the data is loaded agai
.. note::
This method, although very common, is inappropriate for Domain Driven
Design (`DDD <https://en.wikipedia.org/wiki/Domain-driven_design>`)
Design (\ `DDD <https://en.wikipedia.org/wiki/Domain-driven_design>`_)
where methods should represent real business operations and not simple
property change, And business invariants should be maintained both in the
application state (entities in this case) and in the database, with no
@@ -449,7 +448,7 @@ entity.
.. note::
A `DTO <https://en.wikipedia.org/wiki/Data_transfer_object>` is an object
A `DTO <https://en.wikipedia.org/wiki/Data_transfer_object>`_ is an object
that only carries data without any logic. Its only goal is to be transferred
from one service to another.
A ``DTO`` often represents data sent by a client and that has to be validated,
@@ -558,10 +557,10 @@ methods, but you only need to choose one.
.. code-block:: xml
<!-- config/xml/Product.dcm.xml -->
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Product" table="products">
<id name="id" type="integer">
@@ -736,7 +735,7 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
#[ORM\Id]
#[ORM\Column(type: 'integer')]
#[ORM\GeneratedValue]
private int $id;
private int|null $id;
#[ORM\Column(type: 'string')]
private string $description;
@@ -1139,10 +1138,10 @@ the ``Product`` before:
.. code-block:: xml
<!-- config/xml/Bug.dcm.xml -->
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Bug" table="bugs">
<id name="id" type="integer">
@@ -1200,21 +1199,21 @@ which translates the YYYY-mm-dd HH:mm:ss database format
into a PHP DateTime instance and back.
After the field definitions, the two qualified references to the
user entity are defined. They are created by the ``many-to-one``
tag. The class name of the related entity has to be specified with
the ``target-entity`` attribute, which is enough information for
the database mapper to access the foreign-table. Since
user entity are defined. They are created by the ``ManyToOne``
attribute. The class name of the related entity has to be specified with
the ``targetEntity`` parameter, which is enough information for
the database mapper to access the foreign table. Since
``reporter`` and ``engineer`` are on the owning side of a
bi-directional relation, we also have to specify the ``inversed-by``
attribute. They have to point to the field names on the inverse
side of the relationship. We will see in the next example that the ``inversed-by``
attribute has a counterpart ``mapped-by`` which makes that
bi-directional relation, we also have to specify the ``inversedBy``
parameter. They have to point to the field names on the inverse
side of the relationship. We will see in the next example that the ``inversedBy``
parameter has a counterpart ``mappedBy`` which makes that
the inverse side.
The last definition is for the ``Bug#products`` collection. It
holds all products where the specific bug occurs. Again
you have to define the ``target-entity`` and ``field`` attributes
on the ``many-to-many`` tag.
you have to define the ``targetEntity`` and ``field`` parameters
on the ``ManyToMany`` attribute.
Finally, we'll add metadata mappings for the ``User`` entity.
@@ -1294,10 +1293,10 @@ Finally, we'll add metadata mappings for the ``User`` entity.
.. code-block:: xml
<!-- config/xml/User.dcm.xml -->
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="User" table="users">
<id name="id" type="integer">
@@ -1337,15 +1336,14 @@ Finally, we'll add metadata mappings for the ``User`` entity.
targetEntity: Bug
mappedBy: engineer
Here are some new things to mention about the ``one-to-many`` tags.
Here are some new things to mention about the ``OneToMany`` attribute.
Remember that we discussed about the inverse and owning side. Now
both reportedBugs and assignedBugs are inverse relations, which
means the join details have already been defined on the owning
side. Therefore we only have to specify the property on the Bug
class that holds the owning sides.
Update your database schema by running:
::
Update your database schema by running::
$ php bin/doctrine orm:schema-tool:update --force
@@ -1819,9 +1817,9 @@ we have to adjust the metadata slightly.
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Bug" table="bugs" repository-class="BugRepository">

View File

@@ -15,7 +15,7 @@ has a very simple API and implements the SPL interfaces ``Countable`` and
->setFirstResult(0)
->setMaxResults(100);
$paginator = new Paginator($query, $fetchJoinCollection = true);
$paginator = new Paginator($query, fetchJoinCollection: true);
$c = count($paginator);
foreach ($paginator as $post) {
@@ -36,10 +36,10 @@ correct result:
This behavior is only necessary if you actually fetch join a to-many
collection. You can disable this behavior by setting the
``$fetchJoinCollection`` flag to ``false``; in that case only 2 instead of the 3 queries
``fetchJoinCollection`` argument to ``false``; in that case only 2 instead of the 3 queries
described are executed. We hope to automate the detection for this in
the future.
.. note::
``$fetchJoinCollection`` flag set to ``true`` might affect results if you use aggregations in your query.
``fetchJoinCollection`` argument set to ``true`` might affect results if you use aggregations in your query.

View File

@@ -161,9 +161,9 @@ The code and mappings for the Market entity looks like this:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Market">
@@ -278,9 +278,9 @@ here are the code and mappings for it:
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\StockExchange\Stock">

View File

@@ -302,7 +302,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
<xs:attribute name="type" type="orm:type" default="string" />
<xs:attribute name="column" type="orm:columntoken" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />
@@ -330,6 +330,7 @@
<xs:complexType name="discriminator-column">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
@@ -414,7 +415,7 @@
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
<xs:attribute name="type" type="xs:NMTOKEN" />
<xs:attribute name="type" type="orm:type" />
<xs:attribute name="column" type="orm:columntoken" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="association-key" type="xs:boolean" default="false" />
@@ -446,6 +447,13 @@
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="type" id="type">
<xs:restriction base="xs:token">
<xs:pattern value="([a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+)|(\c+)" id="type.class.pattern">
</xs:pattern>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="inverse-join-columns">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
@@ -630,7 +638,7 @@
<xs:element name="options" type="orm:options" minOccurs="0" />
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
</xs:choice>
<xs:attribute name="type" type="xs:NMTOKEN" default="string" />
<xs:attribute name="type" type="orm:type" default="string" />
<xs:attribute name="column" type="orm:columntoken" />
<xs:attribute name="length" type="xs:NMTOKEN" />
<xs:attribute name="unique" type="xs:boolean" default="false" />

View File

@@ -1010,7 +1010,7 @@ abstract class AbstractQuery
*
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
*
* @return mixed The scalar result.
* @return bool|float|int|string|null The scalar result.
*
* @throws NoResultException If the query returned no result.
* @throws NonUniqueResultException If the query result is not unique.

View File

@@ -14,6 +14,8 @@ use Doctrine\ORM\Persisters\Entity\EntityPersister;
/**
* Contract for building second level cache regions components.
*
* @psalm-import-type AssociationMapping from ClassMetadata
*/
interface CacheFactory
{
@@ -31,7 +33,7 @@ interface CacheFactory
/**
* Build a collection persister for the given relation mapping.
*
* @param mixed[] $mapping The association mapping.
* @param AssociationMapping $mapping The association mapping.
*
* @return CachedCollectionPersister
*/

View File

@@ -48,7 +48,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getEntityCacheRegion($className)
{
@@ -63,7 +63,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getCollectionCacheRegion($className, $association)
{
@@ -78,7 +78,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function containsEntity($className, $identifier)
{
@@ -93,7 +93,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictEntity($className, $identifier)
{
@@ -108,7 +108,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictEntityRegion($className)
{
@@ -123,7 +123,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictEntityRegions()
{
@@ -141,7 +141,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function containsCollection($className, $association, $ownerIdentifier)
{
@@ -156,7 +156,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictCollection($className, $association, $ownerIdentifier)
{
@@ -171,7 +171,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictCollectionRegion($className, $association)
{
@@ -186,7 +186,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictCollectionRegions()
{
@@ -210,7 +210,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function containsQuery($regionName)
{
@@ -218,7 +218,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictQueryRegion($regionName = null)
{
@@ -234,7 +234,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictQueryRegions()
{
@@ -246,7 +246,7 @@ class DefaultCache implements Cache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getQueryCache($regionName = null)
{

View File

@@ -106,7 +106,7 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function buildCachedEntityPersister(EntityManagerInterface $em, EntityPersister $persister, ClassMetadata $metadata)
{
@@ -134,10 +134,11 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function buildCachedCollectionPersister(EntityManagerInterface $em, CollectionPersister $persister, array $mapping)
{
assert(isset($mapping['cache']));
$usage = $mapping['cache']['usage'];
$region = $this->getRegion($mapping['cache']);
@@ -161,7 +162,7 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function buildQueryCache(EntityManagerInterface $em, $regionName = null)
{
@@ -177,7 +178,7 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function buildCollectionHydrator(EntityManagerInterface $em, array $mapping)
{
@@ -185,7 +186,7 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata)
{
@@ -193,7 +194,7 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getRegion(array $cache)
{
@@ -224,7 +225,7 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getTimestampRegion()
{
@@ -239,7 +240,7 @@ class DefaultCacheFactory implements CacheFactory
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function createCache(EntityManagerInterface $entityManager)
{

View File

@@ -35,7 +35,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection)
{
@@ -49,7 +49,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection)
{

View File

@@ -47,7 +47,7 @@ class DefaultEntityHydrator implements EntityHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function buildCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, $entity)
{
@@ -140,7 +140,7 @@ class DefaultEntityHydrator implements EntityHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadCacheEntry(ClassMetadata $metadata, EntityCacheKey $key, EntityCacheEntry $entry, $entity = null)
{

View File

@@ -16,7 +16,6 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Proxy;
use function array_map;
use function array_shift;
@@ -29,6 +28,8 @@ use function reset;
/**
* Default query cache implementation.
*
* @psalm-import-type AssociationMapping from ClassMetadata
*/
class DefaultQueryCache implements QueryCache
{
@@ -66,7 +67,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
{
@@ -225,7 +226,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
{
@@ -326,8 +327,8 @@ class DefaultQueryCache implements QueryCache
}
/**
* @param array<string,mixed> $assoc
* @param mixed $assocValue
* @param AssociationMapping $assoc
* @param mixed $assocValue
*
* @return mixed[]|null
* @psalm-return array{targetEntity: class-string, type: mixed, list?: array[], identifier?: array}|null
@@ -343,7 +344,7 @@ class DefaultQueryCache implements QueryCache
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
if (! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
if (! $this->uow->isUninitializedObject($assocValue) && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
// Entity put fail
if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
return null;
@@ -448,7 +449,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function clear()
{
@@ -456,7 +457,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getRegion()
{

View File

@@ -40,7 +40,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key)
{
@@ -50,7 +50,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
{
@@ -60,7 +60,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function collectionCachePut($regionName, CollectionCacheKey $key)
{
@@ -70,7 +70,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function entityCacheHit($regionName, EntityCacheKey $key)
{
@@ -80,7 +80,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function entityCacheMiss($regionName, EntityCacheKey $key)
{
@@ -90,7 +90,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function entityCachePut($regionName, EntityCacheKey $key)
{
@@ -100,7 +100,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function queryCacheHit($regionName, QueryCacheKey $key)
{
@@ -110,7 +110,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function queryCacheMiss($regionName, QueryCacheKey $key)
{
@@ -120,7 +120,7 @@ class CacheLoggerChain implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function queryCachePut($regionName, QueryCacheKey $key)
{

View File

@@ -25,7 +25,7 @@ class StatisticsCacheLogger implements CacheLogger
private $cachePutCountMap = [];
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
{
@@ -34,7 +34,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function collectionCacheHit($regionName, CollectionCacheKey $key)
{
@@ -43,7 +43,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function collectionCachePut($regionName, CollectionCacheKey $key)
{
@@ -52,7 +52,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function entityCacheMiss($regionName, EntityCacheKey $key)
{
@@ -61,7 +61,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function entityCacheHit($regionName, EntityCacheKey $key)
{
@@ -70,7 +70,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function entityCachePut($regionName, EntityCacheKey $key)
{
@@ -79,7 +79,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function queryCacheHit($regionName, QueryCacheKey $key)
{
@@ -88,7 +88,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function queryCacheMiss($regionName, QueryCacheKey $key)
{
@@ -97,7 +97,7 @@ class StatisticsCacheLogger implements CacheLogger
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function queryCachePut($regionName, QueryCacheKey $key)
{

View File

@@ -25,6 +25,7 @@ use function array_values;
use function assert;
use function count;
/** @psalm-import-type AssociationMapping from ClassMetadata */
abstract class AbstractCollectionPersister implements CachedCollectionPersister
{
/** @var UnitOfWork */
@@ -64,7 +65,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
* @param CollectionPersister $persister The collection persister that will be cached.
* @param Region $region The collection region.
* @param EntityManagerInterface $em The entity manager.
* @param mixed[] $association The association mapping.
* @param AssociationMapping $association The association mapping.
*/
public function __construct(CollectionPersister $persister, Region $region, EntityManagerInterface $em, array $association)
{
@@ -85,7 +86,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getCacheRegion()
{
@@ -93,7 +94,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getSourceEntityMetadata()
{
@@ -101,7 +102,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getTargetEntityMetadata()
{
@@ -109,7 +110,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key)
{
@@ -123,7 +124,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function storeCollectionCache(CollectionCacheKey $key, $elements)
{
@@ -167,7 +168,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function contains(PersistentCollection $collection, $element)
{
@@ -175,7 +176,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function containsKey(PersistentCollection $collection, $key)
{
@@ -183,7 +184,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function count(PersistentCollection $collection)
{
@@ -199,7 +200,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get(PersistentCollection $collection, $index)
{
@@ -207,7 +208,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function slice(PersistentCollection $collection, $offset, $length = null)
{

View File

@@ -12,7 +12,7 @@ use function spl_object_id;
class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
@@ -32,7 +32,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
@@ -40,7 +40,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function delete(PersistentCollection $collection)
{
@@ -53,7 +53,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{

View File

@@ -11,7 +11,7 @@ use Doctrine\ORM\PersistentCollection;
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{

View File

@@ -7,21 +7,23 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
use Doctrine\ORM\Cache\CollectionCacheKey;
use Doctrine\ORM\Cache\ConcurrentRegion;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Collection\CollectionPersister;
use function spl_object_id;
/** @psalm-import-type AssociationMapping from ClassMetadata */
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
{
/** @param mixed[] $association The association mapping. */
/** @param AssociationMapping $association The association mapping. */
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
{
parent::__construct($persister, $region, $em, $association);
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
@@ -41,7 +43,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
@@ -61,7 +63,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function delete(PersistentCollection $collection)
{
@@ -82,7 +84,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{

View File

@@ -23,6 +23,7 @@ use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\UnitOfWork;
use function array_merge;
use function assert;
use function serialize;
use function sha1;
@@ -98,7 +99,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function addInsert($entity)
{
@@ -106,7 +107,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getInserts()
{
@@ -114,7 +115,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getSelectSQL($criteria, $assoc = null, $lockMode = null, $limit = null, $offset = null, ?array $orderBy = null)
{
@@ -130,7 +131,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getInsertSQL()
{
@@ -138,7 +139,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getResultSetMapping()
{
@@ -146,7 +147,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
@@ -154,7 +155,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function exists($entity, ?Criteria $extraConditions = null)
{
@@ -170,7 +171,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getCacheRegion()
{
@@ -184,7 +185,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function storeEntityCache($entity, EntityCacheKey $key)
{
@@ -262,7 +263,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function expandParameters($criteria)
{
@@ -270,7 +271,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function expandCriteriaParameters(Criteria $criteria)
{
@@ -278,7 +279,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getClassMetadata()
{
@@ -286,7 +287,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
@@ -294,7 +295,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
@@ -302,7 +303,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getOwningTable($fieldName)
{
@@ -310,17 +311,23 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function executeInserts()
{
$this->queuedCache['insert'] = $this->persister->getInserts();
// The commit order/foreign key relationships may make it necessary that multiple calls to executeInsert()
// are performed, so collect all the new entities.
$newInserts = $this->persister->getInserts();
if ($newInserts) {
$this->queuedCache['insert'] = array_merge($this->queuedCache['insert'] ?? [], $newInserts);
}
return $this->persister->executeInserts();
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = null)
{
@@ -364,7 +371,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null)
{
@@ -400,7 +407,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadById(array $identifier, $entity = null)
{
@@ -464,7 +471,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadCriteria(Criteria $criteria)
{
@@ -503,7 +510,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection)
{
@@ -538,7 +545,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection)
{
@@ -573,7 +580,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
{
@@ -581,7 +588,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function lock(array $criteria, $lockMode)
{
@@ -589,7 +596,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function refresh(array $id, $entity, $lockMode = null)
{

View File

@@ -14,7 +14,7 @@ use function get_class;
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
@@ -48,7 +48,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
@@ -56,7 +56,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function delete($entity)
{
@@ -73,7 +73,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update($entity)
{

View File

@@ -13,7 +13,7 @@ use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity;
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update($entity)
{

View File

@@ -21,7 +21,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionComplete()
{
@@ -51,7 +51,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function afterTransactionRolledBack()
{
@@ -71,7 +71,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function delete($entity)
{
@@ -96,7 +96,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update($entity)
{

View File

@@ -91,7 +91,7 @@ class DefaultRegion implements Region
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getName()
{
@@ -109,7 +109,7 @@ class DefaultRegion implements Region
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function contains(CacheKey $key)
{
@@ -117,7 +117,7 @@ class DefaultRegion implements Region
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get(CacheKey $key)
{
@@ -132,7 +132,7 @@ class DefaultRegion implements Region
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getMultiple(CollectionCacheEntry $collection)
{
@@ -164,7 +164,7 @@ class DefaultRegion implements Region
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @return bool
*/
@@ -182,7 +182,7 @@ class DefaultRegion implements Region
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @return bool
*/
@@ -192,7 +192,7 @@ class DefaultRegion implements Region
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @return bool
*/

View File

@@ -116,7 +116,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getName()
{
@@ -124,7 +124,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function contains(CacheKey $key)
{
@@ -136,7 +136,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get(CacheKey $key)
{
@@ -148,7 +148,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getMultiple(CollectionCacheEntry $collection)
{
@@ -160,7 +160,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function put(CacheKey $key, CacheEntry $entry, ?Lock $lock = null)
{
@@ -172,7 +172,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evict(CacheKey $key)
{
@@ -184,7 +184,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function evictAll()
{
@@ -202,7 +202,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function lock(CacheKey $key)
{
@@ -223,7 +223,7 @@ class FileLockRegion implements ConcurrentRegion
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function unlock(CacheKey $key, Lock $lock)
{

View File

@@ -14,7 +14,7 @@ use Doctrine\ORM\Cache\TimestampRegion;
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(CacheKey $key)
{

View File

@@ -17,7 +17,7 @@ class TimestampQueryCacheValidator implements QueryCacheValidator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry)
{

View File

@@ -62,8 +62,6 @@ use function trim;
* It combines all configuration options from DBAL & ORM.
*
* Internal note: When adding a new configuration option just write a getter/setter pair.
*
* @psalm-import-type AutogenerateMode from ProxyFactory
*/
class Configuration extends \Doctrine\DBAL\Configuration
{
@@ -95,8 +93,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Gets the strategy for automatically generating proxy classes.
*
* @return int Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
* @psalm-return AutogenerateMode
* @return ProxyFactory::AUTOGENERATE_*
*/
public function getAutoGenerateProxyClasses()
{
@@ -106,9 +103,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
/**
* Sets the strategy for automatically generating proxy classes.
*
* @param bool|int $autoGenerate Possible values are constants of Doctrine\ORM\Proxy\ProxyFactory.
* @psalm-param bool|AutogenerateMode $autoGenerate
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
* @param bool|ProxyFactory::AUTOGENERATE_* $autoGenerate True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
*
* @return void
*/
@@ -164,7 +159,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
*
* @return AnnotationDriver
*/
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true, bool $reportFieldsWhereDeclared = false)
{
Deprecation::trigger(
'doctrine/orm',
@@ -203,15 +198,16 @@ class Configuration extends \Doctrine\DBAL\Configuration
return new AnnotationDriver(
$reader,
(array) $paths
(array) $paths,
$reportFieldsWhereDeclared
);
}
/**
* @deprecated No replacement planned.
*
* Adds a namespace under a certain alias.
*
* @deprecated No replacement planned.
*
* @param string $alias
* @param string $namespace
*
@@ -1121,4 +1117,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
$this->_attributes['isLazyGhostObjectEnabled'] = $flag;
}
public function setRejectIdCollisionInIdentityMap(bool $flag): void
{
$this->_attributes['rejectIdCollisionInIdentityMap'] = $flag;
}
public function isRejectIdCollisionInIdentityMapEnabled(): bool
{
return $this->_attributes['rejectIdCollisionInIdentityMap'] ?? false;
}
}

View File

@@ -31,7 +31,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getConnection()
{
@@ -39,7 +39,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getExpressionBuilder()
{
@@ -47,7 +47,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @psalm-param class-string<T> $className
*
@@ -61,7 +61,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getClassMetadata($className)
{
@@ -69,7 +69,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function beginTransaction()
{
@@ -77,7 +77,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function transactional($func)
{
@@ -85,7 +85,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function wrapInTransaction(callable $func)
{
@@ -102,7 +102,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function commit()
{
@@ -110,7 +110,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function rollback()
{
@@ -118,7 +118,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function createQuery($dql = '')
{
@@ -126,7 +126,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function createNamedQuery($name)
{
@@ -134,7 +134,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function createNativeQuery($sql, ResultSetMapping $rsm)
{
@@ -142,7 +142,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function createNamedNativeQuery($name)
{
@@ -150,7 +150,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function createQueryBuilder()
{
@@ -158,7 +158,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getReference($entityName, $id)
{
@@ -166,7 +166,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getPartialReference($entityName, $identifier)
{
@@ -174,7 +174,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function close()
{
@@ -182,7 +182,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function copy($entity, $deep = false)
{
@@ -190,7 +190,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function lock($entity, $lockMode, $lockVersion = null)
{
@@ -198,7 +198,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function find($className, $id, $lockMode = null, $lockVersion = null)
{
@@ -206,7 +206,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function flush($entity = null)
{
@@ -214,7 +214,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function refresh($object)
{
@@ -228,7 +228,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getEventManager()
{
@@ -236,7 +236,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getConfiguration()
{
@@ -244,7 +244,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function isOpen()
{
@@ -252,7 +252,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getUnitOfWork()
{
@@ -260,7 +260,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getHydrator($hydrationMode)
{
@@ -268,7 +268,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function newHydrator($hydrationMode)
{
@@ -276,7 +276,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getProxyFactory()
{
@@ -284,7 +284,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getFilters()
{
@@ -292,7 +292,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function isFiltersStateClean()
{
@@ -300,7 +300,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function hasFilters()
{
@@ -308,7 +308,7 @@ abstract class EntityManagerDecorator extends ObjectManagerDecorator implements
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getCache()
{

View File

@@ -953,6 +953,14 @@ class EntityManager implements EntityManagerInterface
$this->unitOfWork->initializeObject($obj);
}
/**
* {@inheritDoc}
*/
public function isUninitializedObject($obj): bool
{
return $this->unitOfWork->isUninitializedObject($obj);
}
/**
* Factory method to create EntityManager instances.
*

View File

@@ -27,7 +27,7 @@ use Doctrine\Persistence\ObjectManager;
interface EntityManagerInterface extends ObjectManager
{
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @psalm-param class-string<T> $className
*

View File

@@ -297,7 +297,7 @@ class EntityRepository implements ObjectRepository, Selectable
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getClassName()
{

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Exception;
use function get_class;
use function sprintf;
final class EntityIdentityCollisionException extends ORMException
{
/**
* @param object $existingEntity
* @param object $newEntity
*/
public static function create($existingEntity, $newEntity, string $idHash): self
{
return new self(
sprintf(
<<<'EXCEPTION'
While adding an entity of class %s with an ID hash of "%s" to the identity map,
another object of class %s was already present for the same ID. This exception
is a safeguard against an internal inconsistency - IDs should uniquely map to
entity object instances. This problem may occur if:
- you use application-provided IDs and reuse ID values;
- database-provided IDs are reassigned after truncating the database without
clearing the EntityManager;
- you might have been using EntityManager#getReference() to create a reference
for a nonexistent ID that was subsequently (by the RDBMS) assigned to another
entity.
Otherwise, it might be an ORM-internal inconsistency, please report it.
EXCEPTION
,
get_class($newEntity),
$idHash,
get_class($existingEntity)
)
);
}
}

View File

@@ -17,7 +17,7 @@ class AssignedGenerator extends AbstractIdGenerator
/**
* Returns the identifier assigned to the given entity.
*
* {@inheritdoc}
* {@inheritDoc}
*
* @throws EntityMissingAssignedId
*/

View File

@@ -49,7 +49,7 @@ class IdentityGenerator extends AbstractIdGenerator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function isPostInsertGenerator()
{

View File

@@ -21,20 +21,20 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*
* @var int
*/
private $_allocationSize;
private $allocationSize;
/**
* The name of the sequence.
*
* @var string
*/
private $_sequenceName;
private $sequenceName;
/** @var int */
private $_nextValue = 0;
private $nextValue = 0;
/** @var int|null */
private $_maxValue = null;
private $maxValue = null;
/**
* Initializes a new sequence generator.
@@ -44,8 +44,8 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*/
public function __construct($sequenceName, $allocationSize)
{
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
$this->sequenceName = $sequenceName;
$this->allocationSize = $allocationSize;
}
/**
@@ -53,20 +53,20 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*/
public function generateId(EntityManagerInterface $em, $entity)
{
if ($this->_maxValue === null || $this->_nextValue === $this->_maxValue) {
if ($this->maxValue === null || $this->nextValue === $this->maxValue) {
// Allocate new values
$connection = $em->getConnection();
$sql = $connection->getDatabasePlatform()->getSequenceNextValSQL($this->_sequenceName);
$sql = $connection->getDatabasePlatform()->getSequenceNextValSQL($this->sequenceName);
if ($connection instanceof PrimaryReadReplicaConnection) {
$connection->ensureConnectedToPrimary();
}
$this->_nextValue = (int) $connection->fetchOne($sql);
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
$this->nextValue = (int) $connection->fetchOne($sql);
$this->maxValue = $this->nextValue + $this->allocationSize;
}
return $this->_nextValue++;
return $this->nextValue++;
}
/**
@@ -76,7 +76,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*/
public function getCurrentMaxValue()
{
return $this->_maxValue;
return $this->maxValue;
}
/**
@@ -86,7 +86,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
*/
public function getNextValue()
{
return $this->_nextValue;
return $this->nextValue;
}
/**
@@ -103,8 +103,8 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
public function __serialize(): array
{
return [
'allocationSize' => $this->_allocationSize,
'sequenceName' => $this->_sequenceName,
'allocationSize' => $this->allocationSize,
'sequenceName' => $this->sequenceName,
];
}
@@ -123,7 +123,7 @@ class SequenceGenerator extends AbstractIdGenerator implements Serializable
/** @param array<string, mixed> $data */
public function __unserialize(array $data): void
{
$this->_sequenceName = $data['sequenceName'];
$this->_allocationSize = $data['allocationSize'];
$this->sequenceName = $data['sequenceName'];
$this->allocationSize = $data['allocationSize'];
}
}

View File

@@ -14,19 +14,19 @@ use Doctrine\ORM\EntityManagerInterface;
class TableGenerator extends AbstractIdGenerator
{
/** @var string */
private $_tableName;
private $tableName;
/** @var string */
private $_sequenceName;
private $sequenceName;
/** @var int */
private $_allocationSize;
private $allocationSize;
/** @var int|null */
private $_nextValue;
private $nextValue;
/** @var int|null */
private $_maxValue;
private $maxValue;
/**
* @param string $tableName
@@ -35,9 +35,9 @@ class TableGenerator extends AbstractIdGenerator
*/
public function __construct($tableName, $sequenceName = 'default', $allocationSize = 10)
{
$this->_tableName = $tableName;
$this->_sequenceName = $sequenceName;
$this->_allocationSize = $allocationSize;
$this->tableName = $tableName;
$this->sequenceName = $sequenceName;
$this->allocationSize = $allocationSize;
}
/**
@@ -47,23 +47,23 @@ class TableGenerator extends AbstractIdGenerator
EntityManagerInterface $em,
$entity
) {
if ($this->_maxValue === null || $this->_nextValue === $this->_maxValue) {
if ($this->maxValue === null || $this->nextValue === $this->maxValue) {
// Allocate new values
$conn = $em->getConnection();
if ($conn->getTransactionNestingLevel() === 0) {
// use select for update
$sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->_tableName, $this->_sequenceName);
$sql = $conn->getDatabasePlatform()->getTableHiLoCurrentValSql($this->tableName, $this->sequenceName);
$currentLevel = $conn->fetchOne($sql);
if ($currentLevel !== null) {
$this->_nextValue = $currentLevel;
$this->_maxValue = $this->_nextValue + $this->_allocationSize;
$this->nextValue = $currentLevel;
$this->maxValue = $this->nextValue + $this->allocationSize;
$updateSql = $conn->getDatabasePlatform()->getTableHiLoUpdateNextValSql(
$this->_tableName,
$this->_sequenceName,
$this->_allocationSize
$this->tableName,
$this->sequenceName,
$this->allocationSize
);
if ($conn->executeStatement($updateSql, [1 => $currentLevel, 2 => $currentLevel + 1]) !== 1) {
@@ -78,6 +78,6 @@ class TableGenerator extends AbstractIdGenerator
}
}
return $this->_nextValue++;
return $this->nextValue++;
}
}

View File

@@ -4,7 +4,12 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\CommitOrder;
/** @internal */
use Doctrine\Deprecations\Deprecation;
/**
* @internal
* @deprecated
*/
final class Edge
{
/**
@@ -27,6 +32,13 @@ final class Edge
public function __construct(string $from, string $to, int $weight)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10547',
'The %s class is deprecated and will be removed in ORM 3.0',
self::class
);
$this->from = $from;
$this->to = $to;
$this->weight = $weight;

View File

@@ -4,9 +4,13 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\CommitOrder;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Mapping\ClassMetadata;
/** @internal */
/**
* @internal
* @deprecated
*/
final class Vertex
{
/**
@@ -32,6 +36,13 @@ final class Vertex
public function __construct(string $hash, ClassMetadata $value)
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10547',
'The %s class is deprecated and will be removed in ORM 3.0',
self::class
);
$this->hash = $hash;
$this->value = $value;
}

View File

@@ -4,7 +4,12 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal\CommitOrder;
/** @internal */
use Doctrine\Deprecations\Deprecation;
/**
* @internal
* @deprecated
*/
final class VertexState
{
public const NOT_VISITED = 0;
@@ -13,5 +18,11 @@ final class VertexState
private function __construct()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10547',
'The %s class is deprecated and will be removed in ORM 3.0',
self::class
);
}
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Internal;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Internal\CommitOrder\Edge;
use Doctrine\ORM\Internal\CommitOrder\Vertex;
use Doctrine\ORM\Internal\CommitOrder\VertexState;
@@ -17,6 +18,8 @@ use function array_reverse;
* using a depth-first searching (DFS) to traverse the graph built in memory.
* This algorithm have a linear running time based on nodes (V) and dependency
* between the nodes (E), resulting in a computational complexity of O(V + E).
*
* @deprecated
*/
class CommitOrderCalculator
{
@@ -45,6 +48,16 @@ class CommitOrderCalculator
*/
private $sortedNodeList = [];
public function __construct()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10547',
'The %s class is deprecated and will be removed in ORM 3.0',
self::class
);
}
/**
* Checks for node (vertex) existence in graph.
*

View File

@@ -19,39 +19,39 @@ use function reset;
class ArrayHydrator extends AbstractHydrator
{
/** @var array<string,bool> */
private $_rootAliases = [];
private $rootAliases = [];
/** @var bool */
private $_isSimpleQuery = false;
private $isSimpleQuery = false;
/** @var mixed[] */
private $_identifierMap = [];
private $identifierMap = [];
/** @var mixed[] */
private $_resultPointers = [];
private $resultPointers = [];
/** @var array<string,string> */
private $_idTemplate = [];
private $idTemplate = [];
/** @var int */
private $_resultCounter = 0;
private $resultCounter = 0;
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function prepare()
{
$this->_isSimpleQuery = count($this->resultSetMapping()->aliasMap) <= 1;
$this->isSimpleQuery = count($this->resultSetMapping()->aliasMap) <= 1;
foreach ($this->resultSetMapping()->aliasMap as $dqlAlias => $className) {
$this->_identifierMap[$dqlAlias] = [];
$this->_resultPointers[$dqlAlias] = [];
$this->_idTemplate[$dqlAlias] = '';
$this->identifierMap[$dqlAlias] = [];
$this->resultPointers[$dqlAlias] = [];
$this->idTemplate[$dqlAlias] = '';
}
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{
@@ -65,12 +65,12 @@ class ArrayHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateRowData(array $row, array &$result)
{
// 1) Initialize
$id = $this->_idTemplate; // initialize the id-memory
$id = $this->idTemplate; // initialize the id-memory
$nonemptyComponents = [];
$rowData = $this->gatherRowData($row, $id, $nonemptyComponents);
@@ -91,14 +91,14 @@ class ArrayHydrator extends AbstractHydrator
// Get a reference to the right element in the result tree.
// This element will get the associated element attached.
if ($this->resultSetMapping()->isMixed && isset($this->_rootAliases[$parent])) {
$first = reset($this->_resultPointers);
if ($this->resultSetMapping()->isMixed && isset($this->rootAliases[$parent])) {
$first = reset($this->resultPointers);
// TODO: Exception if $key === null ?
$baseElement =& $this->_resultPointers[$parent][key($first)];
} elseif (isset($this->_resultPointers[$parent])) {
$baseElement =& $this->_resultPointers[$parent];
$baseElement =& $this->resultPointers[$parent][key($first)];
} elseif (isset($this->resultPointers[$parent])) {
$baseElement =& $this->resultPointers[$parent];
} else {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
unset($this->resultPointers[$dqlAlias]); // Ticket #1228
continue;
}
@@ -116,8 +116,8 @@ class ArrayHydrator extends AbstractHydrator
}
if (isset($nonemptyComponents[$dqlAlias])) {
$indexExists = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexExists = isset($this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
$index = $indexExists ? $this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
$indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
if (! $indexExists || ! $indexIsValid) {
@@ -131,7 +131,7 @@ class ArrayHydrator extends AbstractHydrator
end($baseElement[$relationAlias]);
$this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
$this->identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
}
}
} else {
@@ -155,8 +155,8 @@ class ArrayHydrator extends AbstractHydrator
} else {
// It's a root result element
$this->_rootAliases[$dqlAlias] = true; // Mark as root
$entityKey = $this->resultSetMapping()->entityMappings[$dqlAlias] ?: 0;
$this->rootAliases[$dqlAlias] = true; // Mark as root
$entityKey = $this->resultSetMapping()->entityMappings[$dqlAlias] ?: 0;
// if this row has a NULL value for the root result id then make it a null result.
if (! isset($nonemptyComponents[$dqlAlias])) {
@@ -164,14 +164,14 @@ class ArrayHydrator extends AbstractHydrator
? [$entityKey => null]
: null;
$resultKey = $this->_resultCounter;
++$this->_resultCounter;
$resultKey = $this->resultCounter;
++$this->resultCounter;
continue;
}
// Check for an existing element
if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
if ($this->isSimpleQuery || ! isset($this->identifierMap[$dqlAlias][$id[$dqlAlias]])) {
$element = $this->resultSetMapping()->isMixed
? [$entityKey => $data]
: $data;
@@ -180,15 +180,15 @@ class ArrayHydrator extends AbstractHydrator
$resultKey = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]];
$result[$resultKey] = $element;
} else {
$resultKey = $this->_resultCounter;
$resultKey = $this->resultCounter;
$result[] = $element;
++$this->_resultCounter;
++$this->resultCounter;
}
$this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
$this->identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
} else {
$index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
$index = $this->identifierMap[$dqlAlias][$id[$dqlAlias]];
$resultKey = $index;
}
@@ -197,7 +197,7 @@ class ArrayHydrator extends AbstractHydrator
}
if (! isset($resultKey)) {
$this->_resultCounter++;
$this->resultCounter++;
}
// Append scalar values to mixed result sets
@@ -206,7 +206,7 @@ class ArrayHydrator extends AbstractHydrator
// this only ever happens when no object is fetched (scalar result only)
$resultKey = isset($this->resultSetMapping()->indexByMap['scalars'])
? $row[$this->resultSetMapping()->indexByMap['scalars']]
: $this->_resultCounter - 1;
: $this->resultCounter - 1;
}
foreach ($rowData['scalars'] as $name => $value) {
@@ -217,7 +217,7 @@ class ArrayHydrator extends AbstractHydrator
// Append new object to mixed result sets
if (isset($rowData['newObjects'])) {
if (! isset($resultKey)) {
$resultKey = $this->_resultCounter - 1;
$resultKey = $this->resultCounter - 1;
}
$scalarCount = (isset($rowData['scalars']) ? count($rowData['scalars']) : 0);
@@ -253,19 +253,19 @@ class ArrayHydrator extends AbstractHydrator
bool $oneToOne
): void {
if ($coll === null) {
unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
unset($this->resultPointers[$dqlAlias]); // Ticket #1228
return;
}
if ($oneToOne) {
$this->_resultPointers[$dqlAlias] =& $coll;
$this->resultPointers[$dqlAlias] =& $coll;
return;
}
if ($index !== false) {
$this->_resultPointers[$dqlAlias] =& $coll[$index];
$this->resultPointers[$dqlAlias] =& $coll[$index];
return;
}
@@ -275,6 +275,6 @@ class ArrayHydrator extends AbstractHydrator
}
end($coll);
$this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
$this->resultPointers[$dqlAlias] =& $coll[key($coll)];
}
}

View File

@@ -16,21 +16,21 @@ use ReturnTypeWillChange;
class IterableResult implements Iterator
{
/** @var AbstractHydrator */
private $_hydrator;
private $hydrator;
/** @var bool */
private $_rewinded = false;
private $rewinded = false;
/** @var int */
private $_key = -1;
private $key = -1;
/** @var mixed[]|null */
private $_current = null;
private $current = null;
/** @param AbstractHydrator $hydrator */
public function __construct($hydrator)
{
$this->_hydrator = $hydrator;
$this->hydrator = $hydrator;
}
/**
@@ -41,12 +41,12 @@ class IterableResult implements Iterator
#[ReturnTypeWillChange]
public function rewind()
{
if ($this->_rewinded === true) {
if ($this->rewinded === true) {
throw new HydrationException('Can only iterate a Result once.');
}
$this->_current = $this->next();
$this->_rewinded = true;
$this->current = $this->next();
$this->rewinded = true;
}
/**
@@ -57,30 +57,30 @@ class IterableResult implements Iterator
#[ReturnTypeWillChange]
public function next()
{
$this->_current = $this->_hydrator->hydrateRow();
$this->_key++;
$this->current = $this->hydrator->hydrateRow();
$this->key++;
return $this->_current;
return $this->current;
}
/** @return mixed */
#[ReturnTypeWillChange]
public function current()
{
return $this->_current;
return $this->current;
}
/** @return int */
#[ReturnTypeWillChange]
public function key()
{
return $this->_key;
return $this->key;
}
/** @return bool */
#[ReturnTypeWillChange]
public function valid()
{
return $this->_current !== false;
return $this->current !== false;
}
}

View File

@@ -10,10 +10,10 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Query;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Persistence\Proxy;
use function array_fill_keys;
use function array_keys;
use function array_map;
use function count;
use function is_array;
use function key;
@@ -52,7 +52,7 @@ class ObjectHydrator extends AbstractHydrator
private $existingCollections = [];
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function prepare()
{
@@ -108,7 +108,7 @@ class ObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function cleanup()
{
@@ -139,7 +139,7 @@ class ObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{
@@ -284,13 +284,17 @@ class ObjectHydrator extends AbstractHydrator
$class = $this->_metadataCache[$className];
if ($class->isIdentifierComposite) {
$idHash = '';
foreach ($class->identifier as $fieldName) {
$idHash .= ' ' . (isset($class->associationMappings[$fieldName])
? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
: $data[$fieldName]);
}
$idHash = UnitOfWork::getIdHashByIdentifier(
array_map(
/** @return mixed */
static function (string $fieldName) use ($data, $class) {
return isset($class->associationMappings[$fieldName])
? $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]
: $data[$fieldName];
},
$class->identifier
)
);
return $this->_uow->tryGetByIdHash(ltrim($idHash), $class->rootEntityName);
} elseif (isset($class->associationMappings[$class->identifier[0]])) {
@@ -308,7 +312,6 @@ class ObjectHydrator extends AbstractHydrator
* that belongs to a particular component/class. Afterwards, all these chunks
* are processed, one after the other. For each chunk of class data only one of the
* following code paths is executed:
*
* Path A: The data chunk belongs to a joined/associated object and the association
* is collection-valued.
* Path B: The data chunk belongs to a joined/associated object and the association
@@ -435,7 +438,7 @@ class ObjectHydrator extends AbstractHydrator
// PATH B: Single-valued association
$reflFieldValue = $reflField->getValue($parentObject);
if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || ($reflFieldValue instanceof Proxy && ! $reflFieldValue->__isInitialized())) {
if (! $reflFieldValue || isset($this->_hints[Query::HINT_REFRESH]) || $this->_uow->isUninitializedObject($reflFieldValue)) {
// we only need to take action if this value is null,
// we refresh the entity or its an uninitialized proxy.
if (isset($nonemptyComponents[$dqlAlias])) {
@@ -453,9 +456,6 @@ class ObjectHydrator extends AbstractHydrator
$targetClass->reflFields[$inverseAssoc['fieldName']]->setValue($element, $parentObject);
$this->_uow->setOriginalEntityProperty(spl_object_id($element), $inverseAssoc['fieldName'], $parentObject);
}
} elseif ($parentClass === $targetClass && $relation['mappedBy']) {
// Special case: bi-directional self-referencing one-one on the same class
$targetClass->reflFields[$relationField]->setValue($element, $parentObject);
}
} else {
// For sure bidirectional, as there is no inverse side in unidirectional mappings

View File

@@ -16,7 +16,7 @@ use function count;
final class ScalarColumnHydrator extends AbstractHydrator
{
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @throws MultipleSelectorsFoundException
* @throws Exception

View File

@@ -12,7 +12,7 @@ namespace Doctrine\ORM\Internal\Hydration;
class ScalarHydrator extends AbstractHydrator
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{
@@ -26,7 +26,7 @@ class ScalarHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateRowData(array $row, array &$result)
{

View File

@@ -28,7 +28,7 @@ class SimpleObjectHydrator extends AbstractHydrator
private $class;
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function prepare()
{
@@ -44,7 +44,7 @@ class SimpleObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function cleanup()
{
@@ -55,7 +55,7 @@ class SimpleObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{
@@ -71,7 +71,7 @@ class SimpleObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateRowData(array $row, array &$result)
{

View File

@@ -17,7 +17,7 @@ use function key;
class SingleScalarHydrator extends AbstractHydrator
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{

View File

@@ -51,7 +51,7 @@ final class HydrationCompleteHandler
}
/**
* This method should me called after any hydration cycle completed.
* This method should be called after any hydration cycle completed.
*
* Method fires all deferred invocations of postLoad events
*/

View File

@@ -9,7 +9,9 @@ use Doctrine\DBAL\Platforms\DB2Platform;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use function get_class;
use function method_exists;
use function strpos;
use function strtolower;
use function strtoupper;
@@ -26,7 +28,7 @@ trait SQLResultCasing
return strtolower($column);
}
if (method_exists(AbstractPlatform::class, 'getSQLResultCasing')) {
if (strpos(get_class($platform), 'Doctrine\\DBAL\\Platforms\\') !== 0 && method_exists(AbstractPlatform::class, 'getSQLResultCasing')) {
return $platform->getSQLResultCasing($column);
}

View File

@@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal;
use Doctrine\ORM\Internal\TopologicalSort\CycleDetectedException;
use function array_keys;
use function array_reverse;
use function array_unshift;
use function spl_object_id;
/**
* TopologicalSort implements topological sorting, which is an ordering
* algorithm for directed graphs (DG) using a depth-first searching (DFS)
* to traverse the graph built in memory.
* This algorithm has a linear running time based on nodes (V) and edges
* between the nodes (E), resulting in a computational complexity of O(V + E).
*
* @internal
*/
final class TopologicalSort
{
private const NOT_VISITED = 1;
private const IN_PROGRESS = 2;
private const VISITED = 3;
/**
* Array of all nodes, indexed by object ids.
*
* @var array<int, object>
*/
private $nodes = [];
/**
* DFS state for the different nodes, indexed by node object id and using one of
* this class' constants as value.
*
* @var array<int, self::*>
*/
private $states = [];
/**
* Edges between the nodes. The first-level key is the object id of the outgoing
* node; the second array maps the destination node by object id as key. The final
* boolean value indicates whether the edge is optional or not.
*
* @var array<int, array<int, bool>>
*/
private $edges = [];
/**
* Builds up the result during the DFS.
*
* @var list<object>
*/
private $sortResult = [];
/** @param object $node */
public function addNode($node): void
{
$id = spl_object_id($node);
$this->nodes[$id] = $node;
$this->states[$id] = self::NOT_VISITED;
$this->edges[$id] = [];
}
/** @param object $node */
public function hasNode($node): bool
{
return isset($this->nodes[spl_object_id($node)]);
}
/**
* Adds a new edge between two nodes to the graph
*
* @param object $from
* @param object $to
* @param bool $optional This indicates whether the edge may be ignored during the topological sort if it is necessary to break cycles.
*/
public function addEdge($from, $to, bool $optional): void
{
$fromId = spl_object_id($from);
$toId = spl_object_id($to);
if (isset($this->edges[$fromId][$toId]) && $this->edges[$fromId][$toId] === false) {
return; // we already know about this dependency, and it is not optional
}
$this->edges[$fromId][$toId] = $optional;
}
/**
* Returns a topological sort of all nodes. When we have an edge A->B between two nodes
* A and B, then A will be listed before B in the result.
*
* @return list<object>
*/
public function sort(): array
{
/*
* When possible, keep objects in the result in the same order in which they were added as nodes.
* Since nodes are unshifted into $this->>sortResult (see the visit() method), that means we
* need to work them in array_reverse order here.
*/
foreach (array_reverse(array_keys($this->nodes)) as $oid) {
if ($this->states[$oid] === self::NOT_VISITED) {
$this->visit($oid);
}
}
return $this->sortResult;
}
private function visit(int $oid): void
{
if ($this->states[$oid] === self::IN_PROGRESS) {
// This node is already on the current DFS stack. We've found a cycle!
throw new CycleDetectedException($this->nodes[$oid]);
}
if ($this->states[$oid] === self::VISITED) {
// We've reached a node that we've already seen, including all
// other nodes that are reachable from here. We're done here, return.
return;
}
$this->states[$oid] = self::IN_PROGRESS;
// Continue the DFS downwards the edge list
foreach ($this->edges[$oid] as $adjacentId => $optional) {
try {
$this->visit($adjacentId);
} catch (CycleDetectedException $exception) {
if ($exception->isCycleCollected()) {
// There is a complete cycle downstream of the current node. We cannot
// do anything about that anymore.
throw $exception;
}
if ($optional) {
// The current edge is part of a cycle, but it is optional and the closest
// such edge while backtracking. Break the cycle here by skipping the edge
// and continuing with the next one.
continue;
}
// We have found a cycle and cannot break it at $edge. Best we can do
// is to retreat from the current vertex, hoping that somewhere up the
// stack this can be salvaged.
$this->states[$oid] = self::NOT_VISITED;
$exception->addToCycle($this->nodes[$oid]);
throw $exception;
}
}
// We have traversed all edges and visited all other nodes reachable from here.
// So we're done with this vertex as well.
$this->states[$oid] = self::VISITED;
array_unshift($this->sortResult, $this->nodes[$oid]);
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal\TopologicalSort;
use RuntimeException;
use function array_unshift;
class CycleDetectedException extends RuntimeException
{
/** @var list<object> */
private $cycle;
/** @var object */
private $startNode;
/**
* Do we have the complete cycle collected?
*
* @var bool
*/
private $cycleCollected = false;
/** @param object $startNode */
public function __construct($startNode)
{
parent::__construct('A cycle has been detected, so a topological sort is not possible. The getCycle() method provides the list of nodes that form the cycle.');
$this->startNode = $startNode;
$this->cycle = [$startNode];
}
/** @return list<object> */
public function getCycle(): array
{
return $this->cycle;
}
/** @param object $node */
public function addToCycle($node): void
{
array_unshift($this->cycle, $node);
if ($node === $this->startNode) {
$this->cycleCollected = true;
}
}
public function isCycleCollected(): bool
{
return $this->cycleCollected;
}
}

View File

@@ -16,7 +16,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
use SQLResultCasing;
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -24,7 +24,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getTableName(ClassMetadata $class, AbstractPlatform $platform)
{
@@ -32,7 +32,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -40,7 +40,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -48,7 +48,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -56,7 +56,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -64,7 +64,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform)
{
@@ -72,7 +72,7 @@ class AnsiQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null)
{

View File

@@ -220,10 +220,11 @@ class ClassMetadataBuilder
* @param string $type
* @param int $length
* @psalm-param class-string<BackedEnum>|null $enumType
* @psalm-param array<string, mixed> $options
*
* @return $this
*/
public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null)
public function setDiscriminatorColumn($name, $type = 'string', $length = 255, ?string $columnDefinition = null, ?string $enumType = null, array $options = [])
{
$this->cm->setDiscriminatorColumn(
[
@@ -232,6 +233,7 @@ class ClassMetadataBuilder
'length' => $length,
'columnDefinition' => $columnDefinition,
'enumType' => $enumType,
'options' => $options,
]
);

View File

@@ -20,7 +20,7 @@ final class ChainTypedFieldMapper implements TypedFieldMapper
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{

View File

@@ -4,12 +4,102 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use BackedEnum;
/**
* {@inheritDoc}
*
* @todo remove or rename ClassMetadataInfo to ClassMetadata
* @template-covariant T of object
* @template-extends ClassMetadataInfo<T>
* @psalm-type FieldMapping = array{
* type: string,
* fieldName: string,
* columnName: string,
* length?: int,
* id?: bool,
* nullable?: bool,
* notInsertable?: bool,
* notUpdatable?: bool,
* generated?: int,
* enumType?: class-string<BackedEnum>,
* columnDefinition?: string,
* precision?: int,
* scale?: int,
* unique?: bool,
* inherited?: class-string,
* originalClass?: class-string,
* originalField?: string,
* quoted?: bool,
* requireSQLConversion?: bool,
* declared?: class-string,
* declaredField?: string,
* options?: array<string, mixed>,
* version?: string,
* default?: string|int,
* }
* @psalm-type JoinColumnData = array{
* name: string,
* referencedColumnName: string,
* unique?: bool,
* quoted?: bool,
* fieldName?: string,
* onDelete?: string,
* columnDefinition?: string,
* nullable?: bool,
* }
* @psalm-type AssociationMapping = array{
* cache?: array,
* cascade: array<string>,
* declared?: class-string,
* fetch: mixed,
* fieldName: string,
* id?: bool,
* inherited?: class-string,
* indexBy?: string,
* inversedBy: string|null,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* isOnDeleteCascade?: bool,
* isOwningSide: bool,
* joinColumns?: array<JoinColumnData>,
* joinColumnFieldNames?: array<string, string>,
* joinTable?: array,
* joinTableColumns?: list<mixed>,
* mappedBy: string|null,
* orderBy?: array,
* originalClass?: class-string,
* originalField?: string,
* orphanRemoval?: bool,
* relationToSourceKeyColumns?: array,
* relationToTargetKeyColumns?: array,
* sourceEntity: class-string,
* sourceToTargetKeyColumns?: array<string, string>,
* targetEntity: class-string,
* targetToSourceKeyColumns?: array<string, string>,
* type: int,
* unique?: bool,
* }
* @psalm-type DiscriminatorColumnMapping = array{
* name: string,
* fieldName: string,
* type: string,
* length?: int,
* columnDefinition?: string|null,
* enumType?: class-string<BackedEnum>|null,
* options?: array<string, mixed>,
* }
* @psalm-type EmbeddedClassMapping = array{
* class: class-string,
* columnPrefix: string|null,
* declaredField: string|null,
* originalField: string|null,
* inherited?: class-string,
* declared?: class-string,
* }
*/
class ClassMetadata extends ClassMetadataInfo
{

View File

@@ -7,6 +7,9 @@ namespace Doctrine\ORM\Mapping;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Platforms;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
use Doctrine\DBAL\Platforms\SQLServerPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
@@ -47,6 +50,9 @@ use function substr;
* to a relational database.
*
* @extends AbstractClassMetadataFactory<ClassMetadata>
* @psalm-import-type AssociationMapping from ClassMetadata
* @psalm-import-type EmbeddedClassMapping from ClassMetadata
* @psalm-import-type FieldMapping from ClassMetadata
*/
class ClassMetadataFactory extends AbstractClassMetadataFactory
{
@@ -107,7 +113,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
if ($parent) {
$class->setInheritanceType($parent->inheritanceType);
$class->setDiscriminatorColumn($parent->discriminatorColumn);
$class->setIdGeneratorType($parent->generatorType);
$this->inheritIdGeneratorMapping($class, $parent);
$this->addInheritedFields($class, $parent);
$this->addInheritedRelations($class, $parent);
$this->addInheritedEmbeddedClasses($class, $parent);
@@ -135,25 +141,27 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
throw MappingException::reflectionFailure($class->getName(), $e);
}
// If this class has a parent the id generator strategy is inherited.
// However this is only true if the hierarchy of parents contains the root entity,
// if it consists of mapped superclasses these don't necessarily include the id field.
if ($parent && $rootEntityFound) {
$this->inheritIdGeneratorMapping($class, $parent);
} else {
// Complete id generator mapping when the generator was declared/added in this class
if ($class->identifier && (! $parent || ! $parent->identifier)) {
$this->completeIdGeneratorMapping($class);
}
if (! $class->isMappedSuperclass) {
if ($rootEntityFound && $class->isInheritanceTypeNone()) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10431',
"Entity class '%s' is a subclass of the root entity class '%s', but no inheritance mapping type was declared. This is a misconfiguration and will be an error in Doctrine ORM 3.0.",
$class->name,
end($nonSuperclassParents)
);
}
foreach ($class->embeddedClasses as $property => $embeddableClass) {
if (isset($embeddableClass['inherited'])) {
continue;
}
if (! (isset($embeddableClass['class']) && $embeddableClass['class'])) {
throw MappingException::missingEmbeddedClass($property);
}
if (isset($this->embeddablesActiveNesting[$embeddableClass['class']])) {
throw MappingException::infiniteEmbeddableNesting($class->name, $property);
}
@@ -411,20 +419,30 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
return strtolower(end($parts));
}
/**
* Puts the `inherited` and `declared` values into mapping information for fields, associations
* and embedded classes.
*
* @param AssociationMapping|EmbeddedClassMapping|FieldMapping $mapping
*/
private function addMappingInheritanceInformation(array &$mapping, ClassMetadata $parentClass): void
{
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
}
/**
* Adds inherited fields to the subclass mapping.
*/
private function addInheritedFields(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->fieldMappings as $mapping) {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$this->addMappingInheritanceInformation($mapping, $parentClass);
$subClass->addInheritedFieldMapping($mapping);
}
@@ -441,14 +459,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->associationMappings as $field => $mapping) {
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$this->addMappingInheritanceInformation($mapping, $parentClass);
// When the class inheriting the relation ($subClass) is the first entity class since the
// relation has been defined in a mapped superclass (or in a chain
// of mapped superclasses) above, then declare this current entity class as the source of
@@ -456,10 +467,6 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
// According to the definitions given in https://github.com/doctrine/orm/pull/10396/,
// this is the case <=> ! isset($mapping['inherited']).
if (! isset($mapping['inherited'])) {
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
}
$mapping['sourceEntity'] = $subClass->name;
}
@@ -470,14 +477,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private function addInheritedEmbeddedClasses(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->embeddedClasses as $field => $embeddedClass) {
if (! isset($embeddedClass['inherited']) && ! $parentClass->isMappedSuperclass) {
$embeddedClass['inherited'] = $parentClass->name;
}
if (! isset($embeddedClass['declared'])) {
$embeddedClass['declared'] = $parentClass->name;
}
$this->addMappingInheritanceInformation($embeddedClass, $parentClass);
$subClass->embeddedClasses[$field] = $embeddedClass;
}
}
@@ -620,9 +620,11 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
case ClassMetadata::GENERATOR_TYPE_IDENTITY:
$sequenceName = null;
$fieldName = $class->identifier ? $class->getSingleIdentifierFieldName() : null;
$platform = $this->getTargetPlatform();
// Platforms that do not have native IDENTITY support need a sequence to emulate this behaviour.
if ($this->getTargetPlatform()->usesSequenceEmulatedIdentityColumns()) {
/** @psalm-suppress UndefinedClass, InvalidClass */
if (! $platform instanceof MySQLPlatform && ! $platform instanceof SqlitePlatform && ! $platform instanceof SQLServerPlatform && $platform->usesSequenceEmulatedIdentityColumns()) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/issues/8850',

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