Compare commits

...

233 Commits

Author SHA1 Message Date
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
Grégoire Paris da0998c401 Add missing underscore to RST links (#10731) 2023-05-30 23:00:28 +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
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 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
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
Alexander M. Turek 9bc6f5b4ac Support unserializing 2.14 ParserResult instances (#10684) 2023-05-07 20:56:25 +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
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
Javier Spagnoletti 1ea8424e07 Apply SlevomatCodingStandard.Commenting.AnnotationName CS rule (#10653) 2023-04-25 08:56:58 +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 e5fb1a4a8f Merge pull request #10604 from greg0ire/psalm-5.9.0 2023-03-30 17:18:54 +02:00
Grégoire Paris 8cb7a5e212 Upgrade to Psalm 5.9.0
It looks like some issues are now represented by different classes.
2023-03-30 17:05:25 +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 da9b9de590 Merge pull request #10589 from e2palmes/patch-1 2023-03-22 12:12:27 +01:00
Emmanuel DE PALMÈS 9ec697db7d Added missing ';'
missing ';' on Obtaining the EntityManager section, bootstrap code line 32
2023-03-22 11:52:36 +01:00
Gabriel Ostrolucký 25bd41a504 Merge branch '2.14.x' into 2.15.x 2023-03-19 22:46:07 +01:00
Mikael Peigney a50a611bee docs: Remove incorrect @SequenceGenerator info (#10583)
The default allocationSize for @SequenceGenerator has actually been set to 1 ever since 434325e (back in 2010!)
This was done to remove confusion, but the docs were never updated to reflect the change
2023-03-16 10:32:42 +01:00
David Maicher abb30093ed add $isXsdValidationEnabled to SimplifiedXmlDriver constructor 2023-03-14 09:38:23 +01:00
Alexander M. Turek df559d8ac0 PHPStan 1.10.6, Psalm 5.8.0 (#10575) 2023-03-14 07:03:59 +01:00
Cyril Beslay a6de4b9663 docs: Update Logger removal in Batch Processing documentation (#10572)
Current documented way is using Doctrine DBAL2.
Since DBAL3, logging is done with Middlewares, so I updated the recommendation in documentation.
2023-03-12 00:09:30 +01:00
Grégoire Paris 6db296cd5c Merge pull request #10539 from mpdude/performance-to-one-inheritance
More precisely document the performance impact of to-one associations towards inheritance hierarchies
2023-03-08 23:00:08 +01:00
Matthias Pigulla 7c2adde6f2 More precisely document the performance impact of to-one associations towards inheritance hierarchies
This puts the remarks that apply to both JTI/STI in a common section at the beginning, and tries to explain a bit more in detail why it is that way.

It was sparked off by the discussion in #10538.
2023-03-08 17:27:44 +00: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
Grégoire Paris 69543ba1bf Skip test instead of commenting it out (#10563)
Skipping makes the test more discoverable.
2023-03-06 09:27:35 +01:00
Alexander M. Turek 9ff0440aac Merge 2.14.x into 2.15.x (#10561) 2023-03-05 22:35:31 +01:00
Alexander M. Turek a33885575f Skip test instead of commenting it out (#10560) 2023-03-05 22:24:44 +01:00
Gabriel Ostrolucký e28dee0742 Add missing return statements to Command:configure methods 2023-03-05 21:31:46 +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 a28e2d8277 Ignore the cache dir of PHPUnit 10 (#10546) 2023-02-28 13:35:35 +01:00
Alexander M. Turek fa4b945b94 Make data providers static (#10545) 2023-02-28 08:29:47 +01:00
Alexander M. Turek 4759a1bf75 Make data providers static (#10544) 2023-02-28 08:29:28 +01:00
Alexander M. Turek e0ad7ac506 Bump dev tools (#10541)
* phpstan/phpstan (1.9.14 => 1.10.3)
* squizlabs/php_codesniffer (3.7.1 => 3.7.2)
* vimeo/psalm (5.6.0 => 5.7.7)
2023-02-28 08:28:45 +01:00
Christophe Coevoet 9485d4d835 Mark SqlWalker methods as not deprecated (#10540)
phpstan treats implementations of deprecated methods of an interface as being deprecated themselves by default.
However, SqlWalker does not intend to deprecate all those methods that are deprecated in TreeWalker, as they are
moved down. Marking them as not deprecated will avoid reporting usages of deprecated APIs when implementing
custom DQL functions for instance.
2023-02-26 15:21:47 +01:00
Matthias Pigulla 7814cbf4ec Fix a Markdown/RST formatting glitch 2023-02-24 17:59:02 +01:00
Matthieu Lempereur 9a6e1b3505 docs: consistency order for docblock in association mapping (#10534) 2023-02-22 14:00:30 +01:00
Grégoire Paris c286742814 Merge pull request #10529 from joshpme/patch-1
Correct use of PHP attribute
2023-02-20 08:00:20 +01:00
Josh P 052887765b Correct use of PHP attribute
Incorrect syntax used in php 8 attribute. This should be key: value, rather than key=value
2023-02-20 15:37:30 +11:00
Al Zee 5464e21022 fix typo in faq.rst (#10526) 2023-02-17 08:25:10 +01:00
Grégoire Paris c26c55926f Merge pull request #8797 from mpdude/query_count_hint 2023-02-15 17:15:45 +01:00
Simon Podlipsky 5369e4f425 fix: use executeStatement in SchemaTool (#10516) 2023-02-14 19:14:28 +01:00
Matthias Pigulla 29bc6cc955 Write a test in a more specific way
... so we can be sure that in fact the second result has a different size.

Co-authored-by: Luís Cobucci <lcobucci@gmail.com>
2023-02-14 08:04:56 +00: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 31ff969628 Put up a warning sign that mapping may not be inherited from transient classes (#10392)
This _seems_ to work, but...

To my understanding, that is only because `ReflectionClass::getProperties` will report `protected` properties also for subclasses, including DocBlocks etc. The mapping drivers are unable to tell where a field comes from, so they pick up the mapping and treat it as originating from the inheriting class.

If that is indeed outside of what the ORM was designed for (sombody please confirm?), then we should explicitly discourage it.

Yes, I have seen examples of this in the wild.
2023-02-09 00:19:38 +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:

https://github.com/doctrine/orm/blob/d6c0031d44f04e04bbc0cd57a3ed7e05c7ea8b40/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 01f139d76c Run tests with ext-pgsql (#10480) 2023-02-08 09:33:05 +01:00
Matthias Pigulla 660197ea71 Avoid unnecessary information in query hints to improve query cache hit ratio
I've noticed that over time my query caches fill up with redundant queries, i. e. different cache entries for the DQL -> SQL translation that are exactly the same. For me, it's an issue because the cache entries fill up precious OPcache memory.

Further investigation revealed that the queries themselves do not differ, but only the query hints – that are part of the computed cache key – do.

In particular, only the value for the `WhereInWalker::HINT_PAGINATOR_ID_COUNT` query hint are different. Since `WhereInWalker` only needs to know _if_ there are matching IDs but not _how many_, we could avoid such cache misses by using just a boolean value as cache hint.
2023-02-08 08:19:25 +01:00
Alexander M. Turek ab06e07b53 Baseline Psalm errors for DBAL 3.6 (#10507) 2023-02-08 07:53:21 +01:00
Grégoire Paris 022b945ed5 Merge pull request #10444 from mpdude/paginator-dql-cacheable
Make Paginator-internal query cacheable in the query cache
2023-02-08 07:53:12 +01:00
Sebastian Busch 7203d05539 Clarify difference between transactional() methods of Connection and EntityManager (#10133)
One could interpret the old description as if `Connection#transactional()` would not rollback the transaction. Also, the fact that the `EntityManager` gets closed in case of an exception was not mentioned.
2023-02-07 23:38:04 +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
Alexander M. Turek 0bd5fbf215 Remove calls to assertObjectHasAttribute() (#10502) 2023-02-07 00:13:13 +01:00
Alexander M. Turek 6a713dd39e Remove calls to withConsecutive() (#10501) 2023-02-06 23:23:05 +01:00
Grégoire Paris 5f169d9325 Merge pull request #10420 from mpdude/fix-9095
Fix #9095 by re-applying #9096
2023-02-06 08:23:15 +01:00
Grégoire Paris cf4680d0e6 Merge pull request #10498 from greg0ire/fix-invalid-test
Use recognized array key
2023-02-06 08:22:26 +01:00
Matthias Pigulla cc5775c3f4 Make class final (as suggested in GH review) 2023-02-05 21:38:03 +00:00
Grégoire Paris c5cf6a046b Use recognized array key
"joinColumn" has no meaning for the static PHP driver. That's because it
performs no translation, unlike other drivers: we're using the
ClassMetadata API directly.

Before this change, changing the value of the "name" key did not break
the test suite. After this change, it does.
2023-02-05 14:35:06 +01:00
Matthias Pigulla 77df5db3b9 Make Paginator-internal query cacheable in the query cache
Make the `Paginator`-internal query (`... WHERE ... IN (id, id2,
id3...)`) cacheable in the query cache again.

When the Paginator creates the internal subquery that does the actual
result limiting, it has to take DBAL type conversions for the identifier
column of the paginated root entity into account (#7820, fixed in
 #7821).

In order to perform this type conversion, we need to know the DBAL type
class for the root entity's `#[Id]`, and we have to figure it out based
on a given (arbitrary) DQL query. This requires DQL parsing and
inspecting the AST, so #7821 placed the conversion code in the
`WhereInWalker` where all the necessary information is available.

The problem is that type conversion has to happen every time the
paginator is run, but the query that results from running
`WhereInWalker` would be kept in the query cache. This was reported in
 #7837 and fixed by #7865, by making this particular query expire every
time. The query must not be cached, since the necessary ID type
conversion happens as a side-effect of running the `WhereInWalker`.

The Paginator internal query that uses `WhereInWalker` has its DQL
re-parsed and transformed in every request.

This PR moves the code that determines the DBAL type out of
`WhereInWalker` into a dedicated SQL walker class, `RootTypeWalker`.

`RootTypeWalker` uses a ~hack~  clever trick to report the type back: It
sets the type as the resulting "SQL" string. The benefit is that
`RootTypeWalker` results can be cached in the query cache themselves.
Only the first time a given DQL query has to be paginated, we need to
run this walker to find out the root entity's ID type. After that, the
type will be returned from the query cache.

With the type information being provided, `Paginator` can take care of
the necessary conversions by itself. This happens every time the
Paginator is used.

The internal query that uses `WhereInWalker` can be cached again since
it no longer has side effects.
2023-02-05 11:17:38 +01:00
Matthias Pigulla ee8269ea55 Fix #9095 by re-applying #9096
Since #10411 has been merged, no more need to specify all intermediate
abstract entity classes.

Thus, we can relax the schema validator check as requested in #9095. The
reasons given in #9142 no longer apply.

Fixes #9095
2023-02-05 11:13:21 +01:00
Grégoire Paris d038f23570 Use linebreaks 2023-02-05 11:11:21 +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
Alexander M. Turek 3843d7e0cc Make all data providers static (#10493) 2023-02-04 08:35:56 +00:00
Jan Nedbal d50ba2e248 Fix invalid phpdocs missing null (#10490) 2023-02-03 22:36:32 +00:00
Jan Nedbal 8debb92d78 Add forgotten exception throws (#10489) 2023-02-03 21:32:13 +00:00
Grégoire Paris 8ff7938e31 Hunt down invalid docblocks (#10476)
It is OK to ignore some of the errors we get, but not this one.
2023-01-31 21:12:00 +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
Grégoire Paris d6c0031d44 Merge pull request #10453 from mpdude/mapped-superclass-association
Add regression test for a to-many relationship on a base class & mapped superclass in the hierarchy
2023-01-28 11:12:16 +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
Alexander M. Turek c78f933e57 Psalm 5.6.0, PHPStan 1.9.14 (#10468) 2023-01-26 19:05:45 +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
Matthias Pigulla 80eb85beaa Add regression test for a to-many relationship on a base class & mapped superclass in the hierarchy
This picks the test case from #9517 and rebases it onto 2.14.x.

The problem has been covered in #8415, so this PR closes #9517 and fixes #9516.

Co-authored-by: Robert D'Ercole <bobdercole@gmail.com>
2023-01-24 20:47:41 +00: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
Alexander M. Turek ed56f42cd5 Psalm 5.5.0 (#10445) 2023-01-23 17:33:19 +01:00
Matthias Pigulla 8b28543939 Avoid wasting Opcache memory with Paginator queries (#10434)
This PR prevents the Paginator from causing OpCache "wasted memory" to increase _on every request_ when used with Symfony's `PhpFilesAdapter` as the cache implementation for the query cache.

Depending on configured thresholds, wasted memory this will either cause periodic opcache restarts or running out of memory and not being able to cache additional scripts ([Details](https://tideways.com/profiler/blog/fine-tune-your-opcache-configuration-to-avoid-caching-suprises)).

Fixes #9917, closes #10095.

There is a long story (#7820, #7821, #7837, #7865) behind how the Paginator can take care of DBAL type conversions when creating the pagination query. This conversion has to transform identifier values before they will be used as a query parameter, so it has to happen every time the Paginator is used.

For reasons, this conversion happens inside `WhereInWalker`. Tree walkers like this are used only during the DQL parsing/AST processing steps. Having a DQL query in the query cache short-cuts this step by fetching the parsing/processing result from the cache.

So, to make sure the conversion happens also with the query cache being enabled, this line

https://github.com/doctrine/orm/blob/1753d035005c1125c9fb4855c3fa629341e5734d/lib/Doctrine/ORM/Tools/Pagination/Paginator.php#L165

was added in #7837. It causes `\Doctrine\ORM\Query::parse()` to re-parse the query every time, but will also put the result into the query cache afterwards.

At this point, the setup described in #9917 – which, to my knowledge, is the default in Symfony + DoctrineBundle projects – will ultimately bring us to this code:

https://github.com/symfony/symfony/blob/4b3391725f2fc4a072e776974f00a992cbc70515/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php#L248-L249

When writing a cache item with an already existing key, the driver has to make sure the opcache will honor the changed PHP file. This is what causes _wasted memory_ to increase.

Instead of using `\Doctrine\ORM\Query::expireQueryCache()`, which will force `\Doctrine\ORM\Query::parse()` to parse the query again before putting it into the cache, use `\Doctrine\ORM\Query::useQueryCache(false)`. The subtle difference is the latter will not place the processed query in the cache in the first place.

A test case is added to check that repeated use of the paginator does not call the cache to update existing keys. That should suffice to make sure we're not running into the issue, while at the same time not complicating tests by using the `PhpFilesAdapter` directly.

Note that in order to observe the described issue in tests, you will need to use the `PhpFilesDriver` and also make sure that OpCache is enabled and also activated for `php-cli` (which is running the unit tests).

This particular subquery generated/used by the Paginator is not put into the query cache. The DQL parsing/to-SQL conversion has to happen _every time_ the Paginator is used.

This, however, was already the case before this PR. In other words, this PR only changes that we do not store/update the cached result every time, but instead completely omit caching the query.
2023-01-23 13:14:46 +01:00
Javier Spagnoletti eec3c42494 Replace hardcoded name with Command::getName() in output message from UpdateCommand (#10443) 2023-01-23 12:51:16 +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 bc394877bc Use the right property (#10441) 2023-01-22 14:04:12 +07:00
Grégoire Paris 1753d03500 Merge pull request #10433 from mpdude/re-enable-tests-7820
Make sure tests from #7837 are actually run
2023-01-21 10:02:34 +01: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 7ce6d8d427 Merge pull request #10436 from greg0ire/update-baseline
Update Psalm baseline
2023-01-20 11:43:52 +01:00
Grégoire Paris a48d95c71d Update Psalm baseline 2023-01-20 11:42:11 +01:00
Matthias Pigulla aee1d33042 Review the documentation regarding entity inheritance (#10429)
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-19 19:59:28 +01:00
Matthias Pigulla 8da741ad75 Make sure tests from 7820 are actually run 2023-01-19 12:05:32 +00: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
Matthias Pigulla ba7387fd8c Fixup GH8127 test case (#10424)
* Fixup GH8127 test case

This removes the unnecessary "middle2" class and puts back in the effect of the data provider.

Both changes were accidentally committed when I was working on #10411 and just meant as experiments during debugging.

* Fix CS
2023-01-18 14:42:26 +01:00
Grégoire Paris a83e4f7978 Merge pull request #10418 from greg0ire/unique-bool
Use correct type for FieldMapping#unique
2023-01-18 07:37:15 +01:00
Grégoire Paris 4f335ab565 Merge pull request #10415 from greg0ire/maintain-psalm-xml
Remove ignore rules for fixed issues
2023-01-18 07:36:27 +01:00
Grégoire Paris f88b0032ad Use correct type for FieldMapping#unique
Looking at usages in the codebase, it is supposed to be at least
bool|string, but not string.
When dumping the value inside `getFieldMapping`, it is always a boolean.
2023-01-17 16:29:07 +01:00
Grégoire Paris 69c7791ba2 Merge pull request #8415 from mpdude/mapped-superclass-association-inheritance
Fix association handling when there is a MappedSuperclass in the middle of an inheritance hierarchy
2023-01-17 16:25:20 +01:00
Grégoire Paris 1090dbe9be Merge pull request #10411 from mpdude/discover-missing-subclasses
Fill in missing subclasses when loading ClassMetadata
2023-01-17 16:13:53 +01:00
Matthias Pigulla c46b604ed7 Fix CS 2023-01-17 14:40:55 +00:00
Matthias Pigulla 8d9ebeded8 Fix association handling when there is a MappedSuperclass in the middle of an inheritance hierarchy
This fixes two closely related bugs.

1. When inheriting a to-one association from a mapped superclass, update the `sourceEntity` class name to the current class only when the association is actually _declared_ in the mapped superclass.
2. Reject association types that are not allowed on mapped superclasses only when they are actually _declared_ in a mapped superclass, not when inherited from parent classes.

Currently, when a many-to-one association is inherited from a `MappedSuperclass`, mapping information will be updated so that the association has the current (inheriting) class as the source entity.

https://github.com/doctrine/orm/blob/2138cc93834cfae9cd3f86c991fa051a3129b693/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php#L384-L393

This was added in 7dc8ef1db9 for [DDC-671](https://github.com/doctrine/orm/issues/5181).

The reason for this is that a mapped superclass is not an entity itself and has no table.

So, in the database, associations can only be from the inheriting entities' tables towards the referred-to target. This is also the reason for the limitation that only to-one associations may be added in mapped superclasses, since for those the database foreign key can be placed on the table(s) of the inheriting entities (and there may be more than one child class).

Neither the decision to update the `sourceEntity` nor the validation check should be based on `$parent->isMappedSuperclass`.

This only works in the simple case where the class hierarchy is `Mapped Superclass → Entity`.

The check is wrong when we have an inheritance hierarchy set up and the class hierarchy is `Base Entity → Mapped Superclass → Child Entity`.

Bug 1: The association should keep the root entity as the source. After all, in a JTI, the root table will contain the foreign key, and we need to base joins on that table when traversing `FROM LeafClass l JOIN l.target`.

Bug 2: Do not reject the to-many association declared in the base class. It is ok to have the reverse (owning) side point back to the base entity, as it would be if there were no mapped superclasses at all. The mapped superclass does not declare, add or otherwise interfere with the to-many association at all.

Base the decision to change the `sourceEntity` on `$mapping['inherited']` being set. This field points to the topmost _parent entity_ class in the ancestry tree where the relationship mapping appears for the first time.

When it is not set, the current class is the first _entity_ class in the hierarchy with that association. Since we are inheriting the relation, it must have been added in a mapped superclass above, but was not yet present in the nearest parent entity class.

In that case, it may only be a to-one association and the source entity needs to be updated.

(See #10396 for a clarification of the semantics of `inherited`.)

Here is a simplified example of the class hierarchy.

See the two tests added for more details – one is for checking the correct usage of a to-one association against/with the base class in JTI. The other is to test that a to-many association on the base class is not rejected.

I am sure that there are other tests that (still) cover the update of `sourceEntity` is happening.

```php
/**
 * @Entity
 */
class AssociationTarget
{
    /**
     * @Column(type="integer")
     * @Id
     * @GeneratedValue
     */
    public $id;
}

/**
 * @Entity
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name="discriminator", type="string")
 * @DiscriminatorMap({"1" = "BaseClass", "2" = "LeafClass"})
 */
class BaseClass
{
    /**
     * @Column(type="integer")
     * @Id
     * @GeneratedValue
     */
    public $id;

    /**
     * @ManyToOne(targetEntity="AssociationTarget")
     */
    public $target;
}

/**
 * @MappedSuperclass
 */
class MediumSuperclass extends BaseClass
{
}

/**
 * @Entity
 */
class LeafClass extends MediumSuperclass
{
}
```

When querying `FROM LeafClass l`, it should be possible to `JOIN l.target`. This currently leads to an SQL error because the SQL join will be made via `LeafClass.target_id` instead of `BaseClass.target_id`. `LeafClass` is considered the `sourceEntity` for the association – which is wrong–, and so the foreign key field is expected to be in the `LeafClass` table (using JTI here).

Fixes #5998, fixes #7825.

I have removed the abstract entity class, since it is not relevant for the issue and took the discussion off course. Also, the discriminator map now contains all classes.

Added the second variant of the bug, namely that a to-many association would wrongly be rejected in the same situation.
2023-01-17 14:37:40 +00:00
Grégoire Paris 55f9178e84 Remove ignore rules for fixed issues
The ArgumentTypeCoercion one is hiding issues we could address.
Addressing them will require changes that should go in the next minor,
so I'm moving them to the baseline instead. That way, we cannot
introduce more issues of this type in this file.
2023-01-17 15:34:10 +01:00
Grégoire Paris 37572802cc Reuse association mapping array shape (#10403) 2023-01-17 11:27:42 +01:00
Matthias Pigulla 60955755e0 Remove outdated todo 2023-01-17 08:49:36 +00: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
Matthias Pigulla a8e979819a Include a test for DDC-6558 2023-01-16 21:13:30 +00:00
Matthias Pigulla 4e8e3ef30b Discover entity subclasses that need not be declared in the discriminator map 2023-01-16 20:33:25 +00: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
Grégoire Paris de7eee5ed7 Merge pull request #10385 from nicolas-grekas/uninitialized-prop-reproducer
Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors
2023-01-16 19:36:59 +01:00
Grégoire Paris 4051937fda Merge pull request #10412 from ThomasLandauer/patch-8
Adding link to Attributes reference
2023-01-16 19:35:20 +01:00
Thomas Landauer b2e42dc92d Adding link to Attributes reference
In fact, I moved it upwards, and updated it to new target :-)
2023-01-16 17:19:29 +01:00
Alexander M. Turek f0616626e0 Test with a stable PHPUnit (#10406) 2023-01-16 15:53:19 +07:00
Grégoire Paris f219b87870 Merge pull request #10393 from mpdude/traits-warning
Place a warning about the uses of traits in the documentation
2023-01-16 08:56:11 +01: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
Grégoire Paris 87fefbac34 Merge pull request #10396 from mpdude/document-inherited-declared-meaning
Document the meanings of 'inherited' and 'declared' in field mapping information
2023-01-15 15:40:38 +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
Matthias Pigulla 853e80ca98 Reword text 2023-01-14 22:56:23 +00:00
Grégoire Paris d68baef880 Merge pull request #10404 from greg0ire/stable-phpbench
Stop allowing phpbench's master branch
2023-01-14 20:44:08 +01:00
Grégoire Paris fdccfbd120 Stop allowing phpbench's master branch
A stable version has been published, it allows doctrine/annotations 2
2023-01-14 16:41:18 +01: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
Matthias Pigulla 180afa8c8f Write down what "transient" means (#10394)
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-14 10:33:58 +01:00
Grégoire Paris dbbf5c7279 Merge pull request #10390 from greg0ire/wrong-phpdoc-exception
Use more accurate phpdoc for OptimisticLockException
2023-01-14 10:29:13 +01:00
Grégoire Paris 5d9b8f0ea8 Merge pull request #10399 from mpdude/fix-toothbrush-example
Fix DDL example for Mapped Superclasses
2023-01-14 10:28:24 +01:00
Matthias Pigulla 174947155d Fix DDL example for Mapped Superclasses
This was not updated to reflect the changes made when the example was improved in b3ee7141eb.
2023-01-14 08:14:04 +00:00
Grégoire Paris 2138cc9383 Sync variable name with class name (#10395) 2023-01-14 00:16:41 +01:00
Matthias Pigulla 39a434914d Be more vague about the Entity Generator 2023-01-13 23:04:40 +00:00
Matthias Pigulla 227f60c832 Update lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2023-01-13 23:51:39 +01:00
Matthias Pigulla f72f6b199b Document the meanings of 'inherited' and 'declared' in field mapping information 2023-01-13 22:35:36 +00:00
Matthias Pigulla 92e63ca4f9 Place a warning about the uses of traits in the documentation 2023-01-13 13:33:53 +00:00
Grégoire Paris 3c98973ab3 Use more accurate phpdoc for OptimisticLockException
While working on migrating this part of the codebase to PHP 8, I found
phpdoc that is wrong.
2023-01-12 18:01:17 +01:00
Kevin Bond 3b8692fa4a add reproducer 2023-01-09 16:09:03 +01:00
Nicolas Grekas c9efc1cdee Fix initializing lazy objects and get rid of "Typed property must not be accessed before initialization" errors 2023-01-09 15:58: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
Alexander M. Turek c5e4e41e05 PHPStan 1.9.8, Psalm 5.4.0 (#10382) 2023-01-09 11:16:44 +07:00
fauVictor 9431b2ea45 fix typo for missing a comma (#10377) 2023-01-06 16:31:05 +01: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
Thomas Landauer 036ea713a5 Docs: Removing type: 'integer' from mappings (#10368)
Yet another micro-PR ;-) - as requested at https://github.com/doctrine/orm/pull/10364#issuecomment-1370155521

I also changed `$currentPrice` from `float` to `int`, since IMO it's better to store prices as `int` (=cents).

Question: Is there a reason why most typehints are `int|null`, instead of `?int`? Should I change them?
2023-01-04 00:16:22 +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
Thomas Landauer 0852847659 Docs: Moving *attributes* mapping to first position (#10364) 2023-01-03 22:42:12 +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
Thomas Landauer 1e2625a82f Docs: Deleting duplicate mapping example (#10363)
I'm guessing this was forgotten to delete some time ago...
2023-01-03 20:43:26 +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
Alexander M. Turek 3010fd1680 PHPStan 1.9.5 (#10359) 2023-01-02 23:12:40 +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
Alexander M. Turek 85ac2769a9 Shorter deprecation message (#10357) 2022-12-31 17:43:12 +01:00
Axel Venet 28e98b3475 Add Fully-Qualified class name in UnrecognizedField exception to ease debugging (#10342) 2022-12-31 15:20:29 +01:00
Alexander M. Turek 99a37d864e Include parameter types in hydration cache key generation (#10355) 2022-12-31 00:44:23 +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
Alexander M. Turek 27df173971 Fix Psalm errors with Collection 2.1.2 (#10343) 2022-12-28 17:19:21 +01:00
Antonio Norman 0aa45dd607 Added warning about query cache in relation to parameters (#10276)
* Added warning about query cache in relation to parameters

* Updated warning about query cache in relation to parameters

* Update docs/en/reference/filters.rst

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

* Update docs/en/reference/filters.rst

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

Co-authored-by: Claudio Zizza <859964+SenseException@users.noreply.github.com>
2022-12-24 23:01:28 +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
Grégoire Paris f8bf84d1aa Merge pull request #10088 from HypeMC/enums-in-simpleobjecthydrator 2022-12-20 16:50:24 +01:00
michnovka c825e34f8d Improve and fix TypedFieldMapper docs (#10327) 2022-12-20 13:09:59 +01:00
Grégoire Paris ff6bad486b Require dev version of phpbench (#10328)
It is important to have the same version of all dependencies in dev and
in the CI, otherwise it makes it hard to have the right static analysis
baseline for every environment.
2022-12-20 09:18:23 +01:00
Grégoire Paris 30a2680bfd Merge pull request #10325 from greg0ire/update-branch-metadata
Update branch metadata
2022-12-19 23:48:49 +01:00
Grégoire Paris a460a4d054 Update branch metadata 2022-12-19 23:35:07 +01:00
HypeMC 9d5ab4ce76 Ensure consistent original data with enums
Previously different hydrators would store the original data for enum
fields in different ways, the SimpleObjectHydrator would keep them as
strings while other hydrators would convert then to native php enums.

This would make the data in the internal UnitOfWork::$originalEntityData
array inconsistent which could've caused problems in the long run.

Now, all hydrators convert enum fields to native php enums ensuring the
original data is always consistent regardless of the hydrator used.
2022-12-07 04:54:22 +01:00
373 changed files with 6598 additions and 3841 deletions
+18 -6
View File
@@ -12,21 +12,33 @@
"upcoming": true
},
{
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"name": "2.16",
"branchName": "2.16.x",
"slug": "2.16",
"upcoming": true
},
{
"name": "2.13",
"branchName": "2.13.x",
"slug": "2.13",
"name": "2.15",
"branchName": "2.15.x",
"slug": "2.15",
"current": true,
"aliases": [
"current",
"stable"
]
},
{
"name": "2.14",
"branchName": "2.14.x",
"slug": "2.14",
"maintained": false
},
{
"name": "2.13",
"branchName": "2.13.x",
"slug": "2.13",
"maintained": false
},
{
"name": "2.12",
"branchName": "2.12.x",
+6 -12
View File
@@ -78,9 +78,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -119,13 +116,18 @@ jobs:
- "3@dev"
postgres-version:
- "15"
extension:
- pdo_pgsql
- pgsql
include:
- php-version: "8.0"
dbal-version: "2.13"
postgres-version: "14"
extension: pdo_pgsql
- php-version: "8.2"
dbal-version: "default"
postgres-version: "9.6"
extension: pdo_pgsql
services:
postgres:
@@ -149,6 +151,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "pgsql pdo_pgsql"
coverage: "pcov"
ini-values: "zend.assertions=1, apc.enable_cli=1"
@@ -156,9 +159,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -220,9 +220,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
with:
@@ -300,9 +297,6 @@ jobs:
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
-6
View File
@@ -57,9 +57,6 @@ jobs:
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
@@ -97,9 +94,6 @@ jobs:
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^3.1 --no-update"
- name: "Uninstall PHPBench"
run: "composer remove --dev --no-update phpbench/phpbench"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v2"
with:
+1
View File
@@ -15,5 +15,6 @@ vendor/
/tests/Doctrine/Performance/history.db
/.phpcs-cache
composer.lock
.phpunit.cache
.phpunit.result.cache
/*.phpunit.xml
+5
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:
+11 -11
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
+34
View File
@@ -1,5 +1,39 @@
# 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.
Use `Doctrine\ORM\Persisters\Exception\UnrecognizedField::byFullyQualifiedName($className, $field)` instead.
## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`
The following public constants have been deprecated:
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
colors="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
failOnRisky="true"
convertDeprecationsToExceptions="true"
>
<php>
<ini name="error_reporting" value="-1" />
<var name="db_driver" value="pgsql"/>
<var name="db_host" value="localhost" />
<var name="db_user" value="postgres" />
<var name="db_password" value="postgres" />
<var name="db_dbname" value="doctrine_tests" />
<!-- necessary change for some CLI/console output test assertions -->
<env name="COLUMNS" value="120"/>
</php>
<testsuites>
<testsuite name="Doctrine DBAL Test Suite">
<directory>../../../tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">../../../lib/Doctrine</directory>
</whitelist>
</filter>
<groups>
<exclude>
<group>performance</group>
<group>locking_functional</group>
</exclude>
</groups>
</phpunit>
+8 -8
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.9.4",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"phpstan/phpstan": "~1.4.10 || 1.10.14",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.1",
"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.3.0"
"vimeo/psalm": "4.30.0 || 5.11.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"
+1 -1
View File
@@ -21,7 +21,7 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
#[Entity]
class Article
{
#[Column(type='datetime')]
#[Column(type: 'datetime')]
private DateTime $updated;
public function setUpdated(): void
+19
View File
@@ -94,6 +94,25 @@ classes, and non-entity classes may extend entity classes.
never calls entity constructors, thus you are free to use them as
you wish and even have it require arguments of any type.
Mapped Superclasses
~~~~~~~~~~~~~~~~~~~
A mapped superclass is an abstract or concrete class that provides
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>`.
Transient Classes
~~~~~~~~~~~~~~~~~
The term "transient class" appears in some places in the mapping
drivers as well as the code dealing with metadata handling.
A transient class is a class that is neither an entity nor a mapped
superclass. From the ORM's point of view, these classes can be
completely ignored, and no class metadata is loaded for them at all.
Entity states
~~~~~~~~~~~~~
+8 -8
View File
@@ -1386,6 +1386,14 @@ Is essentially the same as following:
.. configuration-block::
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
private Shipment $shipment;
.. code-block:: annotation
<?php
@@ -1396,14 +1404,6 @@ Is essentially the same as following:
*/
private Shipment $shipment;
.. code-block:: attribute
<?php
/** One Product has One Shipment. */
#[OneToOne(targetEntity: Shipment::class)]
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
private Shipment $shipment;
.. code-block:: xml
<doctrine-mapping>
+4 -2
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:
@@ -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
@@ -1104,7 +1105,8 @@ 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:
-2
View File
@@ -534,8 +534,6 @@ the above example with ``allocationSize=100`` Doctrine ORM would only
need to access the sequence once to generate the identifiers for
100 new entities.
*The default allocationSize for a @SequenceGenerator is currently 10.*
.. caution::
The allocationSize is detected by SchemaTool and
+6 -4
View File
@@ -19,11 +19,13 @@ especially what the strategies presented here provide help with.
.. note::
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
To avoid that you should disable it in the DBAL configuration:
To avoid that you should remove the corresponding middleware.
To remove all middlewares, you can use this line:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setSQLLogger(null);
$em->getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3
$em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2
Bulk Inserts
------------
@@ -84,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();
@@ -143,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);
@@ -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();
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ upon insert:
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self:STATUS_DISABLED;
private int $status = self::STATUS_DISABLED;
}
.
+5 -4
View File
@@ -28,10 +28,11 @@ table alias of the SQL table of the entity.
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
Parameters for the query should be set on the filter object by
``SQLFilter#setParameter()``. Only parameters set via this function can be used
in filters. The ``SQLFilter#getParameter()`` function takes care of the
proper quoting of parameters.
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
2. The filter must be deterministic. Don't change the values base on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
.. code-block:: php
+1 -4
View File
@@ -45,8 +45,7 @@ in scenarios where data is loaded for read-only purposes.
Read-Only Entities
------------------
You can mark entities as read only (See metadata mapping
references for details).
You can mark entities as read only. For details, see :ref:`attrref_entity`
This means that the entity marked as read only is never considered for updates.
During flush on the EntityManager these entities are skipped even if properties
@@ -55,8 +54,6 @@ changed.
Read-Only allows to persist new entities of a kind and remove existing ones,
they are just not considered for updates.
See :ref:`annref_entity`
You can also explicitly mark individual entities read only directly on the
UnitOfWork via a call to ``markReadOnly()``:
+137 -89
View File
@@ -1,6 +1,9 @@
Inheritance Mapping
===================
This chapter explains the available options for mapping class
hierarchies.
Mapped Superclasses
-------------------
@@ -12,19 +15,52 @@ 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. 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
an entity nor a mapped superclass, but has properties with mapping configuration
on them that would also be used in the inheriting class.
This, however, is due to how the corresponding mapping
drivers work and what the PHP reflection API reports for inherited fields.
Such a configuration is explicitly not supported. To give just one example,
it will break for ``private`` properties.
.. note::
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.
Example:
@@ -77,21 +113,76 @@ like this (this is for SQLite):
.. code-block:: sql
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
CREATE TABLE Employee (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, toothbrush_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
As you can see from this DDL snippet, there is only a single table
for the entity subclass. All the mappings from the mapped
superclass were inherited to the subclass as if they had been
defined on that class directly.
Entity Inheritance
------------------
As soon as one entity class inherits from another entity class, either
directly, with a mapped superclass or other unmapped (also called
"transient") classes in between, these entities form an inheritance
hierarchy. The topmost entity class in this hierarchy is called the
root entity, and the hierarchy includes all entities that are
descendants of this root entity.
On the root entity class, ``#[InheritanceType]``,
``#[DiscriminatorColumn]`` and ``#[DiscriminatorMap]`` must be specified.
``#[InheritanceType]`` specifies one of the two available inheritance
mapping strategies that are explained in the following sections.
``#[DiscriminatorColumn]`` designates the so-called discriminator column.
This is an extra column in the table that keeps information about which
type from the hierarchy applies for a particular database row.
``#[DiscriminatorMap]`` declares the possible values for the discriminator
column and maps them to class names in the hierarchy. This discriminator map
has to declare all non-abstract entity classes that exist in that particular
inheritance hierarchy. That includes the root as well as any intermediate
entity classes, given they are not abstract.
The names of the classes in the discriminator map do not need to be fully
qualified if the classes are contained in the same namespace as the entity
class on which the discriminator map is applied.
If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map contains the
lowercase short name of each class as key.
.. note::
Automatically generating the discriminator map is very expensive
computation-wise. The mapping driver has to provide all classes
for which mapping configuration exists, and those have to be
loaded and checked against the current inheritance hierarchy
to see if they are part of it. The resulting map, however, can be kept
in the metadata cache.
Performance impact on to-one associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There is a general performance consideration when using entity inheritance:
If the target-entity of a many-to-one or one-to-one association is part of
an inheritance hierarchy, it is preferable for performance reasons that it
be a leaf entity (ie. have no subclasses).
Otherwise, the ORM is currently unable to tell beforehand which entity class
will have to be used, and so no appropriate proxy instance can be created.
That means the referred-to entities will *always* be loaded eagerly, which
might even propagate to relationships of these entities (in the case of
self-referencing associations).
Single Table Inheritance
------------------------
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
is an inheritance mapping strategy where all classes of a hierarchy
are mapped to a single database table. In order to distinguish
which row represents which type in the hierarchy a so-called
discriminator column is used.
is an inheritance mapping strategy where all classes of a hierarchy are
mapped to a single database table.
Example:
@@ -156,27 +247,9 @@ Example:
MyProject\Model\Employee:
type: entity
Things to note:
- The ``#[InheritanceType]`` and ``#[DiscriminatorColumn]`` must be
specified on the topmost class that is part of the mapped entity
hierarchy.
- The ``#[DiscriminatorMap]`` specifies which values of the
discriminator column identify a row as being of a certain type. In
the case above a value of "person" identifies a row as being of
type ``Person`` and "employee" identifies a row as being of type
``Employee``.
- All entity classes that is part of the mapped entity hierarchy
(including the topmost class) should be specified in the
``#[DiscriminatorMap]``. In the case above Person class included.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
In this example, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and employee" identifies a row as being of type ``Employee``.
Design-time considerations
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -192,17 +265,10 @@ Performance impact
This strategy is very efficient for querying across all types in
the hierarchy or for specific types. No table joins are required,
only a WHERE clause listing the type identifiers. In particular,
only a ``WHERE`` clause listing the type identifiers. In particular,
relationships involving types that employ this mapping strategy are
very performing.
There is a general performance consideration with Single Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is an STI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -210,7 +276,7 @@ For Single-Table-Inheritance to work in scenarios where you are
using either a legacy database schema or a self-written database
schema you have to make sure that all columns that are not in the
root entity but in any of the different sub-entities has to allow
null values. Columns that have NOT NULL constraints have to be on
null values. Columns that have ``NOT NULL`` constraints have to be on
the root entity of the single-table inheritance hierarchy.
Class Table Inheritance
@@ -220,10 +286,11 @@ Class Table Inheritance
is an inheritance mapping strategy where each class in a hierarchy
is mapped to several tables: its own table and the tables of all
parent classes. The table of a child class is linked to the table
of a parent class through a foreign key constraint. Doctrine ORM
implements this strategy through the use of a discriminator column
in the topmost table of the hierarchy because this is the easiest
way to achieve polymorphic queries with Class Table Inheritance.
of a parent class through a foreign key constraint.
The discriminator column is placed in the topmost table of the hierarchy,
because this is the easiest way to achieve polymorphic queries with Class
Table Inheritance.
Example:
@@ -247,24 +314,9 @@ Example:
// ...
}
Things to note:
- The ``#[InheritanceType]``, ``#[DiscriminatorColumn]`` and
``#[DiscriminatorMap]`` must be specified on the topmost class that is
part of the mapped entity hierarchy.
- The ``#[DiscriminatorMap]`` specifies which values of the
discriminator column identify a row as being of which type. In the
case above a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type
``Employee``.
- The names of the classes in the discriminator map do not need to
be fully qualified if the classes are contained in the same
namespace as the entity class on which the discriminator map is
applied.
- If no discriminator map is provided, then the map is generated
automatically. The automatically generated discriminator map
contains the lowercase short name of each class as key.
As before, the ``#[DiscriminatorMap]`` specifies that in the
discriminator column, a value of "person" identifies a row as being of type
``Person`` and "employee" identifies a row as being of type ``Employee``.
.. note::
@@ -295,20 +347,13 @@ perform just about any query which can have a negative impact on
performance, especially with large tables and/or large hierarchies.
When partial objects are allowed, either globally or on the
specific query, then querying for any type will not cause the
tables of subtypes to be OUTER JOINed which can increase
tables of subtypes to be ``OUTER JOIN``ed which can increase
performance but the resulting partial objects will not fully load
themselves on access of any subtype fields, so accessing fields of
subtypes after such a query is not safe.
There is a general performance consideration with Class Table
Inheritance: If the target-entity of a many-to-one or one-to-one
association is a CTI entity, it is preferable for performance reasons that it
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
Otherwise Doctrine *CANNOT* create proxy instances
of this entity and will *ALWAYS* load the entity eagerly.
There is also another important performance consideration that it is *NOT POSSIBLE*
to query for the base entity without any LEFT JOINs to the sub-types.
There is also another important performance consideration that it is *not possible*
to query for the base entity without any ``LEFT JOIN``s to the sub-types.
SQL Schema considerations
~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -326,14 +371,16 @@ column and cascading on delete.
Overrides
---------
Used to override a mapping for an entity field or relationship. Can only be
applied to an entity that extends a mapped superclass or uses a trait to
override a relationship or field mapping defined by the mapped superclass or
trait.
Overrides can only be applied to entities that extend a mapped superclass or
use traits. They are used to override a mapping for an entity field or
relationship defined in that mapped superclass or trait.
It is not possible to override attributes or associations in entity to entity
inheritance scenarios, because this can cause unforseen edge case behavior and
increases complexity in ORM internal classes.
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.
Association Override
@@ -538,10 +585,11 @@ Example:
Things to note:
- The "association override" specifies the overrides base on the property name.
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
- The association type *CANNOT* be changed.
- The override could redefine the joinTables or joinColumns depending on the association type.
- The "association override" specifies the overrides based on the property
name.
- This feature is available for all kind of associations (OneToOne, OneToMany, ManyToOne, ManyToMany).
- The association type *cannot* be changed.
- The override could redefine the ``joinTables`` or ``joinColumns`` depending on the association type.
- The override could redefine ``inversedBy`` to reference more than one extended entity.
- The override could redefine fetch to modify the fetch strategy of the extended entity.
@@ -714,8 +762,8 @@ Could be used by an entity that extends a mapped superclass to override a field
Things to note:
- The "attribute override" specifies the overrides base on the property name.
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
- The "attribute override" specifies the overrides based on the property name.
- The column type *cannot* be changed. If the column type is not equal, you get a ``MappingException``.
- The override can redefine all the attributes except the type.
Query the Type
@@ -130,10 +130,51 @@ included in the core of Doctrine ORM. However there are already two
extensions out there that offer support for Nested Set with
ORM:
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
Using Traits in Entity Classes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The use of traits in entity or mapped superclasses, at least when they
include mapping configuration or mapped fields, is currently not
endorsed by the Doctrine project. The reasons for this are as follows.
Traits were added in PHP 5.4 more than 10 years ago, but at the same time
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>`.
Coverage of traits in test cases is practically nonexistent.
Thus, you should at least be aware that when using traits in your entity and
mapped superclasses, you will be in uncharted terrain.
.. warning::
There be dragons.
From a more technical point of view, traits basically work at the language level
as if the code contained in them had been copied into the class where the trait
is used, and even private fields are accessible by the using class. In addition to
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
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
due to complexity.
XML mapping configuration probably needs to completely re-configure or otherwise
copy-and-paste configuration for fields used from traits.
Known Issues
------------
+3 -3
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)
{
+1 -1
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()``
@@ -114,8 +114,8 @@ functionally equivalent to the previously shown code looks as follows:
The difference between ``Connection#transactional($func)`` and
``EntityManager#transactional($func)`` is that the latter
abstraction flushes the ``EntityManager`` prior to transaction
commit and rolls back the transaction when an
exception occurs.
commit and in case of an exception the ``EntityManager`` gets closed
in addition to the transaction rollback.
.. _transactions-and-concurrency_exception-handling:
+8 -5
View File
@@ -3,13 +3,12 @@ Implementing a TypedFieldMapper
.. versionadded:: 2.14
You can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
You can specify custom typed field mapping between PHP type and DBAL type using ``Doctrine\ORM\Configuration``
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
.. code-block:: php
<?php
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
@@ -24,6 +23,7 @@ PHP type => DBAL type mappings into its constructor to override the default beha
<?php
use App\CustomIds\CustomIdObject;
use App\DBAL\Type\CustomIdObjectType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
CustomIdObject::class => CustomIdObjectType::class,
@@ -84,6 +84,7 @@ It is perfectly valid to override even the "automatic" mapping rules mentioned a
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
'int' => CustomIntType::class,
@@ -126,10 +127,12 @@ the instances were supplied to the ``ChainTypedFieldMapper`` constructor.
<?php
use App\DBAL\Type\CustomIntType;
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
$configuration->setTypedFieldMapper(
new ChainTypedFieldMapper(
DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
new CustomTypedFieldMapper()
)
);
@@ -150,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
{
@@ -173,4 +176,4 @@ You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMap
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
that behaves like ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
that behaves like the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
@@ -718,6 +718,7 @@ methods:
* ``andX($arg1, $arg2, ...)``
* ``orX($arg1, $arg2, ...)``
* ``not($expression)``
* ``eq($field, $value)``
* ``gt($field, $value)``
* ``lt($field, $value)``
+62 -83
View File
@@ -32,9 +32,9 @@ and year of production as primary keys:
class Car
{
public function __construct(
#[Id, Column(type: 'string')]
#[Id, Column]
private string $name,
#[Id, Column(type: 'integer')]
#[Id, Column]
private int $year,
) {
}
@@ -82,27 +82,6 @@ and year of production as primary keys:
}
}
.. code-block:: annotation
<?php
/**
* @Entity
*/
class User
{
/** @Id @Column(type="integer") @GeneratedValue */
private int|null $id = null;
}
/**
* @Entity
*/
class Address
{
/** @Id @OneToOne(targetEntity="User") */
private User|null $user = null;
}
.. code-block:: xml
<?xml version="1.0" encoding="UTF-8"?>
@@ -152,7 +131,7 @@ And for querying you can use arrays to both DQL and EntityRepositories:
$dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1";
$audi = $em->createQuery($dql)
->setParameter(1, array("name" => "Audi A8", "year" => 2010))
->setParameter(1, ["name" => "Audi A8", "year" => 2010])
->getSingleResult();
You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name``
@@ -190,7 +169,52 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
.. configuration-block::
.. code-block:: php
.. code-block:: attribute
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Article
{
#[Id, Column, GeneratedValue]
private int|null $id = null;
#[Column]
private string $title;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column]
private string $attribute;
#[Column]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: annotation
<?php
namespace Application\Model;
@@ -241,51 +265,6 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
}
}
.. code-block:: attribute
<?php
namespace Application\Model;
use Doctrine\Common\Collections\ArrayCollection;
#[Entity]
class Article
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $title;
/** @var ArrayCollection<string, ArticleAttribute> */
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
private Collection $attributes;
public function addAttribute(string $name, ArticleAttribute $value): void
{
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
}
}
#[Entity]
class ArticleAttribute
{
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
private Article $article;
#[Id, Column(type: 'string')]
private string $attribute;
#[Column(type: 'string')]
private string $value;
public function __construct(string $name, string $value, Article $article)
{
$this->attribute = $name;
$this->value = $value;
$this->article = $article;
}
}
.. code-block:: xml
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
@@ -338,7 +317,7 @@ One good example for this is a user-address relationship:
#[Entity]
class User
{
#[Id, Column(type: 'integer'), GeneratedValue]
#[Id, Column, GeneratedValue]
private int|null $id = null;
}
@@ -386,18 +365,18 @@ of products purchased and maybe even the current price.
#[Entity]
class Order
{
#[Id, Column(type: 'integer'), GeneratedValue]
#[Id, Column, GeneratedValue]
private int|null $id = null;
/** @var ArrayCollection<int, OrderItem> */
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
private Collection $items;
#[Column(type: 'boolean')]
#[Column]
private bool $paid = false;
#[Column(type: 'boolean')]
#[Column]
private bool $shipped = false;
#[Column(type: 'datetime')]
#[Column]
private DateTime $created;
public function __construct(
@@ -412,16 +391,16 @@ of products purchased and maybe even the current price.
#[Entity]
class Product
{
#[Id, Column(type: 'integer'), GeneratedValue]
#[Id, Column, GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
#[Column]
private string $name;
#[Column(type: 'decimal')]
private float $currentPrice;
#[Column]
private int $currentPrice;
public function getCurrentPrice(): float
public function getCurrentPrice(): int
{
return $this->currentPrice;
}
@@ -436,11 +415,11 @@ of products purchased and maybe even the current price.
#[Id, ManyToOne(targetEntity: Product::class)]
private Product|null $product = null;
#[Column(type: 'integer')]
#[Column]
private int $amount = 1;
#[Column(type: 'decimal')]
private float $offeredPrice;
#[Column]
private int $offeredPrice;
public function __construct(Order $order, Product $product, int $amount = 1)
{
@@ -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.
+3 -3
View File
@@ -166,7 +166,7 @@ step:
$connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'path' => __DIR__ . '/db.sqlite',
], $config)
], $config);
// obtaining the entity manager
$entityManager = new EntityManager($connection, $config);
@@ -322,7 +322,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 +449,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,
+11 -3
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" />
+3 -1
View File
@@ -1321,9 +1321,11 @@ abstract class AbstractQuery
protected function getHydrationCacheId()
{
$parameters = [];
$types = [];
foreach ($this->getParameters() as $parameter) {
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
$types[$parameter->getName()] = $parameter->getType();
}
$sql = $this->getSQL();
@@ -1335,7 +1337,7 @@ abstract class AbstractQuery
ksort($hints);
assert($queryCacheProfile !== null);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints);
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints);
}
/**
+3 -1
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
*/
+14 -14
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)
{
@@ -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)
{
@@ -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)
{
@@ -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)
{
+8 -6
View File
@@ -29,6 +29,8 @@ use function reset;
/**
* Default query cache implementation.
*
* @psalm-import-type AssociationMapping from ClassMetadata
*/
class DefaultQueryCache implements QueryCache
{
@@ -66,7 +68,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
{
@@ -225,7 +227,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
{
@@ -326,8 +328,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
@@ -448,7 +450,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function clear()
{
@@ -456,7 +458,7 @@ class DefaultQueryCache implements QueryCache
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getRegion()
{
@@ -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)
{
@@ -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)
{
@@ -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)
{
@@ -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)
{
@@ -11,7 +11,7 @@ use Doctrine\ORM\PersistentCollection;
class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollectionPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{
@@ -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)
{
@@ -98,7 +98,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function addInsert($entity)
{
@@ -106,7 +106,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getInserts()
{
@@ -114,7 +114,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 +130,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getInsertSQL()
{
@@ -138,7 +138,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getResultSetMapping()
{
@@ -146,7 +146,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getSelectConditionStatementSQL($field, $value, $assoc = null, $comparison = null)
{
@@ -154,7 +154,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function exists($entity, ?Criteria $extraConditions = null)
{
@@ -170,7 +170,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getCacheRegion()
{
@@ -184,7 +184,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function storeEntityCache($entity, EntityCacheKey $key)
{
@@ -246,7 +246,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*
* @param string $query
* @param string[]|Criteria $criteria
* @param string[] $orderBy
* @param string[]|null $orderBy
* @param int|null $limit
* @param int|null $offset
*
@@ -262,7 +262,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function expandParameters($criteria)
{
@@ -270,7 +270,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function expandCriteriaParameters(Criteria $criteria)
{
@@ -278,7 +278,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getClassMetadata()
{
@@ -286,7 +286,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getManyToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
@@ -294,7 +294,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getOneToManyCollection(array $assoc, $sourceEntity, $offset = null, $limit = null)
{
@@ -302,7 +302,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getOwningTable($fieldName)
{
@@ -310,7 +310,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function executeInserts()
{
@@ -320,7 +320,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, ?array $orderBy = null)
{
@@ -364,7 +364,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadAll(array $criteria = [], ?array $orderBy = null, $limit = null, $offset = null)
{
@@ -400,7 +400,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadById(array $identifier, $entity = null)
{
@@ -464,7 +464,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadCriteria(Criteria $criteria)
{
@@ -503,7 +503,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadManyToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection)
{
@@ -538,7 +538,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadOneToManyCollection(array $assoc, $sourceEntity, PersistentCollection $collection)
{
@@ -573,7 +573,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
{
@@ -581,7 +581,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function lock(array $criteria, $lockMode)
{
@@ -589,7 +589,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function refresh(array $id, $entity, $lockMode = null)
{
@@ -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)
{
@@ -13,7 +13,7 @@ use Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity;
class ReadOnlyCachedEntityPersister extends NonStrictReadWriteCachedEntityPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update($entity)
{
@@ -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)
{
@@ -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
*/
@@ -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)
{
@@ -14,7 +14,7 @@ use Doctrine\ORM\Cache\TimestampRegion;
class UpdateTimestampCache extends DefaultRegion implements TimestampRegion
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(CacheKey $key)
{
@@ -17,7 +17,7 @@ class TimestampQueryCacheValidator implements QueryCacheValidator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry)
{
+1 -1
View File
@@ -227,7 +227,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
$alias
);
} else {
NotSupported::createForPersistence3(sprintf(
throw NotSupported::createForPersistence3(sprintf(
'Using short namespace alias "%s" by calling %s',
$alias,
__METHOD__
@@ -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()
{
+1 -1
View File
@@ -814,7 +814,7 @@ class EntityManager implements EntityManagerInterface
$entityName
);
} else {
NotSupported::createForPersistence3(sprintf(
throw NotSupported::createForPersistence3(sprintf(
'Using short namespace alias "%s" when calling %s',
$entityName,
__METHOD__
+1 -1
View File
@@ -27,7 +27,7 @@ use Doctrine\Persistence\ObjectManager;
interface EntityManagerInterface extends ObjectManager
{
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @psalm-param class-string<T> $className
*
+1 -1
View File
@@ -297,7 +297,7 @@ class EntityRepository implements ObjectRepository, Selectable
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getClassName()
{
+1 -1
View File
@@ -73,7 +73,7 @@ final class Events
* has been applied to it.
*
* Note that the postLoad event occurs for an entity before any associations have been
* initialized. Therefore it is not safe to access associations in a postLoad callback
* initialized. Therefore, it is not safe to access associations in a postLoad callback
* or event handler.
*
* This is an entity lifecycle event.
+1 -1
View File
@@ -17,7 +17,7 @@ class AssignedGenerator extends AbstractIdGenerator
/**
* Returns the identifier assigned to the given entity.
*
* {@inheritdoc}
* {@inheritDoc}
*
* @throws EntityMissingAssignedId
*/
+1 -1
View File
@@ -49,7 +49,7 @@ class IdentityGenerator extends AbstractIdGenerator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function isPostInsertGenerator()
{
+17 -17
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'];
}
}
+16 -16
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++;
}
}
@@ -698,7 +698,7 @@ abstract class AbstractHydrator
*
* @return BackedEnum|array<BackedEnum>
*/
private function buildEnum($value, string $enumType)
final protected function buildEnum($value, string $enumType)
{
if (is_array($value)) {
return array_map(static function ($value) use ($enumType): BackedEnum {
@@ -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)];
}
}
@@ -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;
}
}
@@ -14,6 +14,7 @@ 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 +53,7 @@ class ObjectHydrator extends AbstractHydrator
private $existingCollections = [];
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function prepare()
{
@@ -108,7 +109,7 @@ class ObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function cleanup()
{
@@ -139,7 +140,7 @@ class ObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{
@@ -284,13 +285,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]])) {
@@ -16,7 +16,7 @@ use function count;
final class ScalarColumnHydrator extends AbstractHydrator
{
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @throws MultipleSelectorsFoundException
* @throws Exception
@@ -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)
{
@@ -6,9 +6,11 @@ namespace Doctrine\ORM\Internal\Hydration;
use Doctrine\ORM\Internal\SQLResultCasing;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Query;
use Exception;
use RuntimeException;
use ValueError;
use function array_keys;
use function array_search;
@@ -26,7 +28,7 @@ class SimpleObjectHydrator extends AbstractHydrator
private $class;
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function prepare()
{
@@ -42,7 +44,7 @@ class SimpleObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function cleanup()
{
@@ -53,7 +55,7 @@ class SimpleObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{
@@ -69,7 +71,7 @@ class SimpleObjectHydrator extends AbstractHydrator
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateRowData(array $row, array &$result)
{
@@ -140,6 +142,21 @@ class SimpleObjectHydrator extends AbstractHydrator
$value = $type->convertToPHPValue($value, $this->_platform);
}
if ($value !== null && isset($cacheKeyInfo['enumType'])) {
$originalValue = $value;
try {
$value = $this->buildEnum($originalValue, $cacheKeyInfo['enumType']);
} catch (ValueError $e) {
throw MappingException::invalidEnumValue(
$entityName,
$cacheKeyInfo['fieldName'],
(string) $originalValue,
$cacheKeyInfo['enumType'],
$e
);
}
}
$fieldName = $cacheKeyInfo['fieldName'];
// Prevent overwrite in case of inherit classes using same property name (See AbstractHydrator)
@@ -148,6 +165,10 @@ class SimpleObjectHydrator extends AbstractHydrator
}
}
if (isset($this->_hints[Query::HINT_REFRESH_ENTITY])) {
$this->registerManaged($this->class, $this->_hints[Query::HINT_REFRESH_ENTITY], $data);
}
$uow = $this->_em->getUnitOfWork();
$entity = $uow->createEntity($entityName, $data, $this->_hints);
@@ -17,7 +17,7 @@ use function key;
class SingleScalarHydrator extends AbstractHydrator
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function hydrateAllData()
{
@@ -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
*/
@@ -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)
{
@@ -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,
]
);
@@ -20,7 +20,7 @@ final class ChainTypedFieldMapper implements TypedFieldMapper
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
@@ -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
{
@@ -47,6 +47,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
{
@@ -115,6 +118,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$class->setVersioned($parent->isVersioned);
$class->setVersionField($parent->versionField);
$class->setDiscriminatorMap($parent->discriminatorMap);
$class->addSubClasses($parent->subClasses);
$class->setLifecycleCallbacks($parent->lifecycleCallbacks);
$class->setChangeTrackingPolicy($parent->changeTrackingPolicy);
@@ -144,15 +148,21 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
}
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);
}
@@ -219,11 +229,16 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$this->addDefaultDiscriminatorMap($class);
}
// During the following event, there may also be updates to the discriminator map as per GH-1257/GH-8402.
// So, we must not discover the missing subclasses before that.
if ($this->evm->hasListeners(Events::loadClassMetadata)) {
$eventArgs = new LoadClassMetadataEventArgs($class, $this->em);
$this->evm->dispatchEvent(Events::loadClassMetadata, $eventArgs);
}
$this->findAbstractEntityClassesNotListedInDiscriminatorMap($class);
if ($class->changeTrackingPolicy === ClassMetadata::CHANGETRACKING_NOTIFY) {
Deprecation::trigger(
'doctrine/orm',
@@ -338,6 +353,57 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$class->setDiscriminatorMap($map);
}
private function findAbstractEntityClassesNotListedInDiscriminatorMap(ClassMetadata $rootEntityClass): void
{
// Only root classes in inheritance hierarchies need contain a discriminator map,
// so skip for other classes.
if (! $rootEntityClass->isRootEntity() || $rootEntityClass->isInheritanceTypeNone()) {
return;
}
$processedClasses = [$rootEntityClass->name => true];
foreach ($rootEntityClass->subClasses as $knownSubClass) {
$processedClasses[$knownSubClass] = true;
}
foreach ($rootEntityClass->discriminatorMap as $declaredClassName) {
// This fetches non-transient parent classes only
$parentClasses = $this->getParentClasses($declaredClassName);
foreach ($parentClasses as $parentClass) {
if (isset($processedClasses[$parentClass])) {
continue;
}
$processedClasses[$parentClass] = true;
// All non-abstract entity classes must be listed in the discriminator map, and
// this will be validated/enforced at runtime (possibly at a later time, when the
// subclass is loaded, but anyways). Also, subclasses is about entity classes only.
// That means we can ignore non-abstract classes here. The (expensive) driver
// check for mapped superclasses need only be run for abstract candidate classes.
if (! (new ReflectionClass($parentClass))->isAbstract() || $this->peekIfIsMappedSuperclass($parentClass)) {
continue;
}
// We have found a non-transient, non-mapped-superclass = an entity class (possibly abstract, but that does not matter)
$rootEntityClass->addSubClass($parentClass);
}
}
}
/** @param class-string $className */
private function peekIfIsMappedSuperclass(string $className): bool
{
$reflService = $this->getReflectionService();
$class = $this->newClassMetadataInstance($className);
$this->initializeReflection($class, $reflService);
$this->driver->loadMetadataForClass($className, $class);
return $class->isMappedSuperclass;
}
/**
* Gets the lower-case short name of a class.
*
@@ -354,20 +420,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);
}
@@ -384,23 +460,17 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
private function addInheritedRelations(ClassMetadata $subClass, ClassMetadata $parentClass): void
{
foreach ($parentClass->associationMappings as $field => $mapping) {
if ($parentClass->isMappedSuperclass) {
if ($mapping['type'] & ClassMetadata::TO_MANY && ! $mapping['isOwningSide']) {
throw MappingException::illegalToManyAssociationOnMappedSuperclass($parentClass->name, $field);
}
$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
// the relationship.
// According to the definitions given in https://github.com/doctrine/orm/pull/10396/,
// this is the case <=> ! isset($mapping['inherited']).
if (! isset($mapping['inherited'])) {
$mapping['sourceEntity'] = $subClass->name;
}
//$subclassMapping = $mapping;
if (! isset($mapping['inherited']) && ! $parentClass->isMappedSuperclass) {
$mapping['inherited'] = $parentClass->name;
}
if (! isset($mapping['declared'])) {
$mapping['declared'] = $parentClass->name;
}
$subClass->addInheritedAssociationMapping($mapping);
}
}
@@ -408,14 +478,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;
}
}
+172 -186
View File
@@ -68,83 +68,11 @@ use const PHP_VERSION_ID;
*
* @template-covariant T of object
* @template-implements ClassMetadata<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?: string,
* inherited?: class-string,
* originalClass?: class-string,
* originalField?: string,
* quoted?: bool,
* requireSQLConversion?: bool,
* declared?: class-string,
* declaredField?: string,
* options?: array<string, mixed>
* }
* @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,
* }
* @psalm-import-type AssociationMapping from \Doctrine\ORM\Mapping\ClassMetadata
* @psalm-import-type FieldMapping from \Doctrine\ORM\Mapping\ClassMetadata
* @psalm-import-type EmbeddedClassMapping from \Doctrine\ORM\Mapping\ClassMetadata
* @psalm-import-type JoinColumnData from \Doctrine\ORM\Mapping\ClassMetadata
* @psalm-import-type DiscriminatorColumnMapping from \Doctrine\ORM\Mapping\ClassMetadata
*/
class ClassMetadataInfo implements ClassMetadata
{
@@ -170,6 +98,8 @@ class ClassMetadataInfo implements ClassMetadata
/**
* TABLE_PER_CLASS means the class will be persisted according to the rules
* of <tt>Concrete Table Inheritance</tt>.
*
* @deprecated
*/
public const INHERITANCE_TYPE_TABLE_PER_CLASS = 4;
@@ -390,14 +320,35 @@ class ClassMetadataInfo implements ClassMetadata
public $isEmbeddedClass = false;
/**
* READ-ONLY: The names of the parent classes (ancestors).
* READ-ONLY: The names of the parent <em>entity</em> classes (ancestors), starting with the
* nearest one and ending with the root entity class.
*
* @psalm-var list<class-string>
*/
public $parentClasses = [];
/**
* READ-ONLY: The names of all subclasses (descendants).
* READ-ONLY: For classes in inheritance mapping hierarchies, this field contains the names of all
* <em>entity</em> subclasses of this class. These may also be abstract classes.
*
* This list is used, for example, to enumerate all necessary tables in JTI when querying for root
* or subclass entities, or to gather all fields comprised in an entity inheritance tree.
*
* For classes that do not use STI/JTI, this list is empty.
*
* Implementation note:
*
* In PHP, there is no general way to discover all subclasses of a given class at runtime. For that
* reason, the list of classes given in the discriminator map at the root entity is considered
* authoritative. The discriminator map must contain all <em>concrete</em> classes that can
* appear in the particular inheritance hierarchy tree. Since there can be no instances of abstract
* entity classes, users are not required to list such classes with a discriminator value.
*
* The possibly remaining "gaps" for abstract entity classes are filled after the class metadata for the
* root entity has been loaded.
*
* For subclasses of such root entities, the list can be reused/passed downwards, it only needs to
* be filtered accordingly (only keep remaining subclasses)
*
* @psalm-var list<class-string>
*/
@@ -406,7 +357,23 @@ class ClassMetadataInfo implements ClassMetadata
/**
* READ-ONLY: The names of all embedded classes based on properties.
*
* @psalm-var array<string, mixed[]>
* The value (definition) array may contain, among others, the following values:
*
* - <b>'inherited'</b> (string, optional)
* This is set when this embedded-class field is inherited by this class from another (inheritance) parent
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
* mapping information for this field. (If there are transient classes in the
* class hierarchy, these are ignored, so the class property may in fact come
* from a class further up in the PHP class hierarchy.)
* Fields initially declared in mapped superclasses are
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
*
* - <b>'declared'</b> (string, optional)
* This is set when the embedded-class field does not appear for the first time in this class, but is originally
* declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
* of the topmost non-transient class that contains mapping information for this field.
*
* @psalm-var array<string, EmbeddedClassMapping>
*/
public $embeddedClasses = [];
@@ -520,9 +487,23 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>scale</b> (integer, optional, schema-only)
* The scale of a decimal column. Only valid if the column type is decimal.
*
* - <b>'unique'</b> (string, optional, schema-only)
* - <b>'unique'</b> (boolean, optional, schema-only)
* Whether a unique constraint should be generated for the column.
*
* - <b>'inherited'</b> (string, optional)
* This is set when the field is inherited by this class from another (inheritance) parent
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
* mapping information for this field. (If there are transient classes in the
* class hierarchy, these are ignored, so the class property may in fact come
* from a class further up in the PHP class hierarchy.)
* Fields initially declared in mapped superclasses are
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
*
* - <b>'declared'</b> (string, optional)
* This is set when the field does not appear for the first time in this class, but is originally
* declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
* of the topmost non-transient class that contains mapping information for this field.
*
* @var mixed[]
* @psalm-var array<string, FieldMapping>
*/
@@ -625,6 +606,11 @@ class ClassMetadataInfo implements ClassMetadata
* - <b>fieldName</b> (string)
* The name of the field in the entity the association is mapped to.
*
* - <b>sourceEntity</b> (string)
* The class name of the source entity. In the case of to-many associations initially
* present in mapped superclasses, the nearest <em>entity</em> subclasses will be
* considered the respective source entities.
*
* - <b>targetEntity</b> (string)
* The class name of the target entity. If it is fully-qualified it is used as is.
* If it is a simple, unqualified class name the namespace is assumed to be the same
@@ -661,6 +647,20 @@ class ClassMetadataInfo implements ClassMetadata
* This field HAS to be either the primary key or a unique column. Otherwise the collection
* does not contain all the entities that are actually related.
*
* - <b>'inherited'</b> (string, optional)
* This is set when the association is inherited by this class from another (inheritance) parent
* <em>entity</em> class. The value is the FQCN of the topmost entity class that contains
* this association. (If there are transient classes in the
* class hierarchy, these are ignored, so the class property may in fact come
* from a class further up in the PHP class hierarchy.)
* To-many associations initially declared in mapped superclasses are
* <em>not</em> considered 'inherited' in the nearest entity subclasses.
*
* - <b>'declared'</b> (string, optional)
* This is set when the association does not appear in the current class for the first time, but
* is initially declared in another parent <em>entity or mapped superclass</em>. The value is the FQCN
* of the topmost non-transient class that contains association information for this relationship.
*
* A join table definition has the following structure:
* <pre>
* array(
@@ -1097,6 +1097,7 @@ class ClassMetadataInfo implements ClassMetadata
foreach ($this->embeddedClasses as $property => $embeddedClass) {
if (isset($embeddedClass['declaredField'])) {
assert($embeddedClass['originalField'] !== null);
$childProperty = $this->getAccessibleProperty(
$reflService,
$this->embeddedClasses[$embeddedClass['declaredField']]['class'],
@@ -1284,7 +1285,7 @@ class ClassMetadataInfo implements ClassMetadata
/**
* @param string $fieldName
* @param array $cache
* @psalm-param array{usage?: int, region?: string|null} $cache
* @psalm-param array{usage?: int|null, region?: string|null} $cache
*
* @return int[]|string[]
* @psalm-return array{usage: int, region: string|null}
@@ -1582,9 +1583,9 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, type?: mixed} $mapping The field mapping to validate & complete.
* @param array{fieldName: string, type?: string} $mapping The field mapping to validate & complete.
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
* @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping.
*/
private function validateAndCompleteTypedFieldMapping(array $mapping): array
{
@@ -1628,7 +1629,7 @@ class ClassMetadataInfo implements ClassMetadata
* enumType?: class-string,
* } $mapping The field mapping to validate & complete.
*
* @return mixed[] The updated mapping.
* @return FieldMapping The updated mapping.
*
* @throws MappingException
*/
@@ -1724,25 +1725,7 @@ class ClassMetadataInfo implements ClassMetadata
* @psalm-param array<string, mixed> $mapping The mapping.
*
* @return mixed[] The updated mapping.
* @psalm-return array{
* mappedBy: mixed|null,
* inversedBy: mixed|null,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array<array-key,string>,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* ?orphanRemoval: bool
* }
* @psalm-return AssociationMapping
*
* @throws MappingException If something is wrong with the mapping.
*/
@@ -1872,31 +1855,7 @@ class ClassMetadataInfo implements ClassMetadata
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
*
* @return mixed[] The validated & completed mapping.
* @psalm-return array{isOwningSide: mixed, orphanRemoval: bool, isCascadeRemove: bool}
* @psalm-return array{
* mappedBy: mixed|null,
* inversedBy: mixed|null,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array<string>,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* joinColumns?: array{0: array{name: string, referencedColumnName: string}}|mixed,
* id?: mixed,
* sourceToTargetKeyColumns?: array<string, string>,
* joinColumnFieldNames?: array<string, string>,
* targetToSourceKeyColumns?: array<string, string>,
* orphanRemoval: bool
* }
* @psalm-return AssociationMapping
*
* @throws RuntimeException
* @throws MappingException
@@ -1905,6 +1864,14 @@ class ClassMetadataInfo implements ClassMetadata
{
$mapping = $this->_validateAndCompleteAssociationMapping($mapping);
if (isset($mapping['joinColumns']) && $mapping['joinColumns'] && ! $mapping['isOwningSide']) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10654',
'JoinColumn configuration is not allowed on the inverse side of one-to-one associations, and will throw a MappingException in Doctrine ORM 3.0'
);
}
if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
$mapping['isOwningSide'] = true;
}
@@ -1986,22 +1953,7 @@ class ClassMetadataInfo implements ClassMetadata
* @psalm-param array<string, mixed> $mapping The mapping to validate and complete.
*
* @return mixed[] The validated and completed mapping.
* @psalm-return array{
* mappedBy: mixed,
* inversedBy: mixed,
* isOwningSide: bool,
* sourceEntity: string,
* targetEntity: string,
* fieldName: mixed,
* fetch: int|mixed,
* cascade: array<array-key,string>,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* orphanRemoval: bool
* }
* @psalm-return AssociationMapping
*
* @throws MappingException
* @throws InvalidArgumentException
@@ -2027,33 +1979,9 @@ class ClassMetadataInfo implements ClassMetadata
* Validates & completes a many-to-many association mapping.
*
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
* @psalm-param array<string, mixed> $mapping The mapping to validate & complete.
*
* @return mixed[] The validated & completed mapping.
* @psalm-return array{
* mappedBy: mixed,
* inversedBy: mixed,
* isOwningSide: bool,
* sourceEntity: class-string,
* targetEntity: string,
* fieldName: mixed,
* fetch: mixed,
* cascade: array<string>,
* isCascadeRemove: bool,
* isCascadePersist: bool,
* isCascadeRefresh: bool,
* isCascadeMerge: bool,
* isCascadeDetach: bool,
* type: int,
* originalField: string,
* originalClass: class-string,
* joinTable?: array{inverseJoinColumns: mixed}|mixed,
* joinTableColumns?: list<mixed>,
* isOnDeleteCascade?: true,
* relationToSourceKeyColumns?: array,
* relationToTargetKeyColumns?: array,
* orphanRemoval: bool
* }
* @psalm-return AssociationMapping
*
* @throws InvalidArgumentException
*/
@@ -2326,11 +2254,19 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
*
* @deprecated
*
* @return bool TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
* FALSE otherwise.
*/
public function isInheritanceTypeTablePerClass()
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10414/',
'Concrete table inheritance has never been implemented, and its stubs will be removed in Doctrine ORM 3.0 with no replacement'
);
return $this->inheritanceType === self::INHERITANCE_TYPE_TABLE_PER_CLASS;
}
@@ -2482,7 +2418,8 @@ class ClassMetadataInfo implements ClassMetadata
}
/**
* Sets the parent class names.
* Sets the parent class names. Only <em>entity</em> classes may be given.
*
* Assumes that the class names in the passed array are in the order:
* directParent -> directParentParent -> directParentParentParent ... -> root.
*
@@ -2536,12 +2473,16 @@ class ClassMetadataInfo implements ClassMetadata
$mapping = $this->associationMappings[$fieldName];
//if (isset($mapping['inherited']) && (count($overrideMapping) !== 1 || ! isset($overrideMapping['fetch']))) {
// TODO: Deprecate overriding the fetch mode via association override for 3.0,
// users should do this with a listener and a custom attribute/annotation
// TODO: Enable this exception in 2.8
//throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName);
//}
if (isset($mapping['inherited'])) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10470',
'Overrides are only allowed for fields or associations declared in mapped superclasses or traits. This is not the case for %s::%s, which was inherited from %s. This is a misconfiguration and will be an error in Doctrine ORM 3.0.',
$this->name,
$fieldName,
$mapping['inherited']
);
}
if (isset($overrideMapping['joinColumns'])) {
$mapping['joinColumns'] = $overrideMapping['joinColumns'];
@@ -2601,10 +2542,17 @@ class ClassMetadataInfo implements ClassMetadata
$mapping = $this->fieldMappings[$fieldName];
//if (isset($mapping['inherited'])) {
// TODO: Enable this exception in 2.8
if (isset($mapping['inherited'])) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10470',
'Overrides are only allowed for fields or associations declared in mapped superclasses or traits. This is not the case for %s::%s, which was inherited from %s. This is a misconfiguration and will be an error in Doctrine ORM 3.0.',
$this->name,
$fieldName,
$mapping['inherited']
);
//throw MappingException::illegalOverrideOfInheritedProperty($this->name, $fieldName);
//}
}
if (isset($mapping['id'])) {
$overrideMapping['id'] = $mapping['id'];
@@ -2747,6 +2695,14 @@ class ClassMetadataInfo implements ClassMetadata
*/
private function isInheritanceType(int $type): bool
{
if ($type === self::INHERITANCE_TYPE_TABLE_PER_CLASS) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10414/',
'Concrete table inheritance has never been implemented, and its stubs will be removed in Doctrine ORM 3.0 with no replacement'
);
}
return $type === self::INHERITANCE_TYPE_NONE ||
$type === self::INHERITANCE_TYPE_SINGLE_TABLE ||
$type === self::INHERITANCE_TYPE_JOINED ||
@@ -2799,7 +2755,7 @@ class ClassMetadataInfo implements ClassMetadata
* Adds a field mapping without completing/validating it.
* This is mainly used to add inherited field mappings to derived classes.
*
* @psalm-param array<string, mixed> $fieldMapping
* @psalm-param FieldMapping $fieldMapping
*
* @return void
*/
@@ -3035,7 +2991,7 @@ class ClassMetadataInfo implements ClassMetadata
/**
* Stores the association mapping.
*
* @psalm-param array<string, mixed> $assocMapping
* @psalm-param AssociationMapping $assocMapping
*
* @return void
*
@@ -3187,7 +3143,7 @@ class ClassMetadataInfo implements ClassMetadata
* @see getDiscriminatorColumn()
*
* @param mixed[]|null $columnDef
* @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string<BackedEnum>|null}|null $columnDef
* @psalm-param array{name: string|null, fieldName?: string, type?: string, length?: int, columnDefinition?: string|null, enumType?: class-string<BackedEnum>|null, options?: array<string, mixed>}|null $columnDef
*
* @return void
*
@@ -3275,6 +3231,21 @@ class ClassMetadataInfo implements ClassMetadata
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
}
$this->addSubClass($className);
}
/** @param array<class-string> $classes */
public function addSubClasses(array $classes): void
{
foreach ($classes as $className) {
$this->addSubClass($className);
}
}
public function addSubClass(string $className): void
{
// By ignoring classes that are not subclasses of the current class, we simplify inheriting
// the subclass list from a parent class at the beginning of \Doctrine\ORM\Mapping\ClassMetadataFactory::doLoadMetadata.
if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses, true)) {
$this->subClasses[] = $className;
}
@@ -3777,8 +3748,16 @@ class ClassMetadataInfo implements ClassMetadata
}
}
if (! (isset($mapping['class']) && $mapping['class'])) {
throw MappingException::missingEmbeddedClass($mapping['fieldName']);
}
$fqcn = $this->fullyQualifiedClassName($mapping['class']);
assert($fqcn !== null);
$this->embeddedClasses[$mapping['fieldName']] = [
'class' => $this->fullyQualifiedClassName($mapping['class']),
'class' => $fqcn,
'columnPrefix' => $mapping['columnPrefix'] ?? null,
'declaredField' => $mapping['declaredField'] ?? null,
'originalField' => $mapping['originalField'] ?? null,
@@ -3870,7 +3849,7 @@ class ClassMetadataInfo implements ClassMetadata
return $sequencePrefix;
}
/** @psalm-param array<string, mixed> $mapping */
/** @psalm-param AssociationMapping $mapping */
private function assertMappingOrderBy(array $mapping): void
{
if (isset($mapping['orderBy']) && ! is_array($mapping['orderBy'])) {
@@ -3883,7 +3862,14 @@ class ClassMetadataInfo implements ClassMetadata
{
$reflectionProperty = $reflService->getAccessibleProperty($class, $field);
if ($reflectionProperty !== null && PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) {
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
$declaringClass = $reflectionProperty->getDeclaringClass()->name;
if ($declaringClass !== $class) {
$reflectionProperty = $reflService->getAccessibleProperty($declaringClass, $field);
}
if ($reflectionProperty !== null) {
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
}
}
return $reflectionProperty;
@@ -21,7 +21,7 @@ class DefaultEntityListenerResolver implements EntityListenerResolver
private $instances = [];
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function clear($className = null)
{
@@ -38,7 +38,7 @@ class DefaultEntityListenerResolver implements EntityListenerResolver
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function register($object)
{
@@ -50,7 +50,7 @@ class DefaultEntityListenerResolver implements EntityListenerResolver
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function resolve($className)
{
@@ -17,7 +17,7 @@ use function substr;
class DefaultNamingStrategy implements NamingStrategy
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function classToTableName($className)
{
@@ -29,7 +29,7 @@ class DefaultNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function propertyToColumnName($propertyName, $className = null)
{
@@ -37,7 +37,7 @@ class DefaultNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
{
@@ -45,7 +45,7 @@ class DefaultNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function referenceColumnName()
{
@@ -53,7 +53,7 @@ class DefaultNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @param string $propertyName
* @param class-string $className
@@ -64,7 +64,7 @@ class DefaultNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
{
@@ -73,7 +73,7 @@ class DefaultNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function joinKeyColumnName($entityName, $referencedColumnName = null)
{
@@ -21,7 +21,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
use SQLResultCasing;
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getColumnName($fieldName, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -31,7 +31,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @todo Table names should be computed in DBAL depending on the platform
*/
@@ -53,7 +53,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -63,7 +63,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -73,7 +73,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getReferencedJoinColumnName(array $joinColumn, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -83,7 +83,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getJoinTableName(array $association, ClassMetadata $class, AbstractPlatform $platform)
{
@@ -104,7 +104,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getIdentifierColumnNames(ClassMetadata $class, AbstractPlatform $platform)
{
@@ -135,7 +135,7 @@ class DefaultQuoteStrategy implements QuoteStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function getColumnAlias($columnName, $counter, AbstractPlatform $platform, ?ClassMetadata $class = null)
{
@@ -43,7 +43,7 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
{
@@ -45,18 +45,29 @@ final class DiscriminatorColumn implements MappingAttribute
*/
public $enumType = null;
/** @param class-string<\BackedEnum>|null $enumType */
/**
* @var array<string, mixed>
* @readonly
*/
public $options = [];
/**
* @param class-string<\BackedEnum>|null $enumType
* @param array<string, mixed> $options
*/
public function __construct(
?string $name = null,
?string $type = null,
?int $length = null,
?string $columnDefinition = null,
?string $enumType = null
?string $enumType = null,
array $options = []
) {
$this->name = $name;
$this->type = $type;
$this->length = $length;
$this->columnDefinition = $columnDefinition;
$this->enumType = $enumType;
$this->options = $options;
}
}
@@ -313,15 +313,19 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
$discrColumnAnnot = $classAnnotations[Mapping\DiscriminatorColumn::class];
assert($discrColumnAnnot instanceof Mapping\DiscriminatorColumn);
$metadata->setDiscriminatorColumn(
[
'name' => $discrColumnAnnot->name,
'type' => $discrColumnAnnot->type ?: 'string',
'length' => $discrColumnAnnot->length ?? 255,
'columnDefinition' => $discrColumnAnnot->columnDefinition,
'enumType' => $discrColumnAnnot->enumType,
]
);
$columnDef = [
'name' => $discrColumnAnnot->name,
'type' => $discrColumnAnnot->type ?: 'string',
'length' => $discrColumnAnnot->length ?? 255,
'columnDefinition' => $discrColumnAnnot->columnDefinition,
'enumType' => $discrColumnAnnot->enumType,
];
if ($discrColumnAnnot->options) {
$columnDef['options'] = $discrColumnAnnot->options;
}
$metadata->setDiscriminatorColumn($columnDef);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
@@ -696,8 +700,8 @@ class AnnotationDriver extends CompatibilityAnnotationDriver
/**
* Parses the given method.
*
* @return callable[]
* @psalm-return list<callable-array>
* @return list<array{string, string}>
* @psalm-return list<array{string, (Events::*)}>
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
@@ -265,15 +265,19 @@ class AttributeDriver extends CompatibilityAnnotationDriver
if (isset($classAttributes[Mapping\DiscriminatorColumn::class])) {
$discrColumnAttribute = $classAttributes[Mapping\DiscriminatorColumn::class];
$metadata->setDiscriminatorColumn(
[
'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null,
'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string',
'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255,
'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null,
'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null,
]
);
$columnDef = [
'name' => isset($discrColumnAttribute->name) ? (string) $discrColumnAttribute->name : null,
'type' => isset($discrColumnAttribute->type) ? (string) $discrColumnAttribute->type : 'string',
'length' => isset($discrColumnAttribute->length) ? (int) $discrColumnAttribute->length : 255,
'columnDefinition' => isset($discrColumnAttribute->columnDefinition) ? (string) $discrColumnAttribute->columnDefinition : null,
'enumType' => isset($discrColumnAttribute->enumType) ? (string) $discrColumnAttribute->enumType : null,
];
if ($discrColumnAttribute->options) {
$columnDef['options'] = (array) $discrColumnAttribute->options;
}
$metadata->setDiscriminatorColumn($columnDef);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
@@ -433,6 +437,14 @@ class AttributeDriver extends CompatibilityAnnotationDriver
if ($joinTableAttribute->options) {
$joinTable['options'] = $joinTableAttribute->options;
}
foreach ($joinTableAttribute->joinColumns as $joinColumn) {
$joinTable['joinColumns'][] = $this->joinColumnToArray($joinColumn);
}
foreach ($joinTableAttribute->inverseJoinColumns as $joinColumn) {
$joinTable['inverseJoinColumns'][] = $this->joinColumnToArray($joinColumn);
}
}
foreach ($this->reader->getPropertyAttributeCollection($property, Mapping\JoinColumn::class) as $joinColumn) {
@@ -615,7 +627,8 @@ class AttributeDriver extends CompatibilityAnnotationDriver
/**
* Parses the given method.
*
* @return callable[]
* @return list<array{string, string}>
* @psalm-return list<array{string, (Events::*)}>
*/
private function getMethodCallbacks(ReflectionMethod $method): array
{
@@ -61,7 +61,7 @@ class DatabaseDriver implements MappingDriver
private const JSON_ARRAY = 'json_array';
/** @var AbstractSchemaManager */
private $_sm;
private $sm;
/** @var array<string,Table>|null */
private $tables = null;
@@ -90,7 +90,7 @@ class DatabaseDriver implements MappingDriver
public function __construct(AbstractSchemaManager $schemaManager)
{
$this->_sm = $schemaManager;
$this->sm = $schemaManager;
$this->inflector = InflectorFactory::create()->build();
}
@@ -294,7 +294,7 @@ class DatabaseDriver implements MappingDriver
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
foreach ($this->_sm->listTables() as $table) {
foreach ($this->sm->listTables() as $table) {
$tableName = $table->getName();
$foreignKeys = $table->getForeignKeys();
@@ -16,10 +16,10 @@ class SimplifiedXmlDriver extends XmlDriver
/**
* {@inheritDoc}
*/
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION)
public function __construct($prefixes, $fileExtension = self::DEFAULT_FILE_EXTENSION, bool $isXsdValidationEnabled = false)
{
$locator = new SymfonyFileLocator((array) $prefixes, $fileExtension);
parent::__construct($locator, $fileExtension);
parent::__construct($locator, $fileExtension, $isXsdValidationEnabled);
}
}
+14 -9
View File
@@ -204,15 +204,19 @@ class XmlDriver extends FileDriver
// Evaluate <discriminator-column...>
if (isset($xmlRoot->{'discriminator-column'})) {
$discrColumn = $xmlRoot->{'discriminator-column'};
$metadata->setDiscriminatorColumn(
[
'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null,
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255,
'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null,
'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null,
]
);
$columnDef = [
'name' => isset($discrColumn['name']) ? (string) $discrColumn['name'] : null,
'type' => isset($discrColumn['type']) ? (string) $discrColumn['type'] : 'string',
'length' => isset($discrColumn['length']) ? (int) $discrColumn['length'] : 255,
'columnDefinition' => isset($discrColumn['column-definition']) ? (string) $discrColumn['column-definition'] : null,
'enumType' => isset($discrColumn['enum-type']) ? (string) $discrColumn['enum-type'] : null,
];
if (isset($discrColumn['options'])) {
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
}
$metadata->setDiscriminatorColumn($columnDef);
} else {
$metadata->setDiscriminatorColumn(['name' => 'dtype', 'type' => 'string', 'length' => 255]);
}
@@ -985,6 +989,7 @@ class XmlDriver extends FileDriver
$result = [];
// Note: we do not use `simplexml_load_file()` because of https://bugs.php.net/bug.php?id=62577
$xmlElement = simplexml_load_string(file_get_contents($file));
assert($xmlElement !== false);
if (isset($xmlElement->entity)) {
foreach ($xmlElement->entity as $entityElement) {
@@ -903,11 +903,10 @@ class YamlDriver extends FileDriver
* Parse / Normalize the cache configuration
*
* @param mixed[] $cacheMapping
* @psalm-param array{usage: mixed, region: (string|null)} $cacheMapping
* @psalm-param array{usage: string, region?: string} $cacheMapping
* @psalm-param array{usage: string|null, region?: mixed} $cacheMapping
*
* @return mixed[]
* @psalm-return array{usage: int, region: string|null}
* @psalm-return array{usage: int|null, region: string|null}
*/
private function cacheToArray(array $cacheMapping): array
{
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Mapping;
use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\Deprecations\Deprecation;
/**
* @Annotation
@@ -28,6 +29,14 @@ final class InheritanceType implements MappingAttribute
/** @psalm-param 'NONE'|'JOINED'|'SINGLE_TABLE'|'TABLE_PER_CLASS' $value */
public function __construct(string $value)
{
if ($value === 'TABLE_PER_CLASS') {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/10414/',
'Concrete table inheritance has never been implemented, and its stubs will be removed in Doctrine ORM 3.0 with no replacement'
);
}
$this->value = $value;
}
}
@@ -33,7 +33,7 @@ class MappingException extends ORMException
}
/**
* @param string $entityName
* @param class-string $entityName
*
* @return MappingException
*/
@@ -346,7 +346,7 @@ class MappingException extends ORMException
}
/**
* @param string $className
* @param class-string $className
*
* @return MappingException
*/
@@ -908,8 +908,7 @@ class MappingException extends ORMException
{
return new self(
sprintf(
'Override for %s::%s is only allowed for attributes/associations ' .
'declared on a mapped superclass or a trait.',
'Overrides are only allowed for fields or associations declared in mapped superclasses or traits, which is not the case for %s::%s.',
$className,
$propertyName
)
+6 -3
View File
@@ -8,6 +8,9 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* A set of rules for determining the column, alias and table quotes.
*
* @psalm-import-type AssociationMapping from ClassMetadata
* @psalm-import-type JoinColumnData from ClassMetadata
*/
interface QuoteStrategy
{
@@ -39,7 +42,7 @@ interface QuoteStrategy
/**
* Gets the (possibly quoted) name of the join table.
*
* @param mixed[] $association
* @param AssociationMapping $association
*
* @return string
*/
@@ -48,7 +51,7 @@ interface QuoteStrategy
/**
* Gets the (possibly quoted) join column name.
*
* @param mixed[] $joinColumn
* @param JoinColumnData $joinColumn
*
* @return string
*/
@@ -57,7 +60,7 @@ interface QuoteStrategy
/**
* Gets the (possibly quoted) join column name.
*
* @param mixed[] $joinColumn
* @param JoinColumnData $joinColumn
*
* @return string
*/
+3 -3
View File
@@ -46,9 +46,9 @@ final class Table implements MappingAttribute
public $options = [];
/**
* @param array<Index> $indexes
* @param array<UniqueConstraint> $uniqueConstraints
* @param array<string,mixed> $options
* @param array<Index>|null $indexes
* @param array<UniqueConstraint>|null $uniqueConstraints
* @param array<string,mixed> $options
*/
public function __construct(
?string $name = null,
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use BackedEnum;
use ReflectionProperty;
interface TypedFieldMapper
@@ -11,9 +12,9 @@ interface TypedFieldMapper
/**
* Validates & completes the given field mapping based on typed property.
*
* @param array{fieldName: string, enumType?: string, type?: mixed} $mapping The field mapping to validate & complete.
* @param array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} $mapping The field mapping to validate & complete.
*
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
* @return array{fieldName: string, enumType?: class-string<BackedEnum>, type?: string} The updated mapping.
*/
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
}
@@ -73,7 +73,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function classToTableName($className)
{
@@ -85,7 +85,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function propertyToColumnName($propertyName, $className = null)
{
@@ -93,7 +93,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function embeddedFieldToColumnName($propertyName, $embeddedColumnName, $className = null, $embeddedClassName = null)
{
@@ -101,7 +101,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function referenceColumnName()
{
@@ -109,7 +109,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @param string $propertyName
* @param class-string $className
@@ -120,7 +120,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function joinTableName($sourceEntity, $targetEntity, $propertyName = null)
{
@@ -128,7 +128,7 @@ class UnderscoreNamingStrategy implements NamingStrategy
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function joinKeyColumnName($entityName, $referencedColumnName = null)
{
@@ -40,9 +40,9 @@ final class UniqueConstraint implements MappingAttribute
public $options;
/**
* @param array<string> $columns
* @param array<string> $fields
* @param array<string,mixed> $options
* @param array<string>|null $columns
* @param array<string>|null $fields
* @param array<string,mixed>|null $options
*/
public function __construct(
?string $name = null,
+1 -1
View File
@@ -40,7 +40,7 @@ final class NativeQuery extends AbstractQuery
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
protected function _doExecute()
{
@@ -22,6 +22,8 @@ use function sprintf;
/**
* Contains exception messages for all invalid lifecycle state exceptions inside UnitOfWork
*
* @psalm-import-type AssociationMapping from ClassMetadata
*/
class ORMInvalidArgumentException extends InvalidArgumentException
{
@@ -80,8 +82,8 @@ class ORMInvalidArgumentException extends InvalidArgumentException
}
/**
* @param array[][]|object[][] $newEntitiesWithAssociations non-empty an array
* of [array $associationMapping, object $entity] pairs
* @psalm-param non-empty-list<array{AssociationMapping, object}> $newEntitiesWithAssociations non-empty an array
* of [array $associationMapping, object $entity] pairs
*
* @return ORMInvalidArgumentException
*/
@@ -109,7 +111,7 @@ class ORMInvalidArgumentException extends InvalidArgumentException
/**
* @param object $entry
* @psalm-param array<string, string> $associationMapping
* @psalm-param AssociationMapping $associationMapping
*
* @return ORMInvalidArgumentException
*/
@@ -120,7 +122,7 @@ class ORMInvalidArgumentException extends InvalidArgumentException
/**
* @param object $entry
* @psalm-param array<string, string> $assoc
* @psalm-param AssociationMapping $assoc
*
* @return ORMInvalidArgumentException
*/
@@ -220,8 +222,8 @@ EXCEPTION
}
/**
* @param mixed[] $assoc
* @param mixed $actualValue
* @param AssociationMapping $assoc
* @param mixed $actualValue
*
* @return self
*/
@@ -271,7 +273,7 @@ EXCEPTION
/**
* @param object $entity
* @psalm-param array<string,string> $associationMapping
* @psalm-param AssociationMapping $associationMapping
*/
private static function newEntityFoundThroughRelationshipMessage(array $associationMapping, $entity): string
{
+8 -8
View File
@@ -13,12 +13,12 @@ use Doctrine\ORM\Exception\ORMException;
*/
class OptimisticLockException extends ORMException
{
/** @var object|null */
/** @var object|string|null */
private $entity;
/**
* @param string $msg
* @param object|null $entity
* @param string $msg
* @param object|string|null $entity
*/
public function __construct($msg, $entity)
{
@@ -30,7 +30,7 @@ class OptimisticLockException extends ORMException
/**
* Gets the entity that caused the exception.
*
* @return object|null
* @return object|string|null
*/
public function getEntity()
{
@@ -38,7 +38,7 @@ class OptimisticLockException extends ORMException
}
/**
* @param object $entity
* @param object|class-string $entity
*
* @return OptimisticLockException
*/
@@ -48,9 +48,9 @@ class OptimisticLockException extends ORMException
}
/**
* @param object $entity
* @param int|DateTimeInterface $expectedLockVersion
* @param int|DateTimeInterface $actualLockVersion
* @param object $entity
* @param int|string|DateTimeInterface $expectedLockVersion
* @param int|string|DateTimeInterface $actualLockVersion
*
* @return OptimisticLockException
*/
+52 -44
View File
@@ -12,6 +12,7 @@ use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping\ClassMetadata;
use ReturnTypeWillChange;
use RuntimeException;
use UnexpectedValueException;
use function array_combine;
use function array_diff_key;
@@ -36,6 +37,7 @@ use function spl_object_id;
* @psalm-template T
* @template-extends AbstractLazyCollection<TKey,T>
* @template-implements Selectable<TKey,T>
* @psalm-import-type AssociationMapping from ClassMetadata
*/
final class PersistentCollection extends AbstractLazyCollection implements Selectable
{
@@ -58,7 +60,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
* The association mapping the collection belongs to.
* This is currently either a OneToManyMapping or a ManyToManyMapping.
*
* @psalm-var array<string, mixed>|null
* @psalm-var AssociationMapping|null
*/
private $association;
@@ -113,7 +115,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
* describes the association between the owner and the elements of the collection.
*
* @param object $entity
* @psalm-param array<string, mixed> $assoc
* @psalm-param AssociationMapping $assoc
*/
public function setOwner($entity, array $assoc): void
{
@@ -161,7 +163,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
// If _backRefFieldName is set and its a one-to-many association,
// we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
if ($this->backRefFieldName && $this->getMapping()['type'] === ClassMetadata::ONE_TO_MANY) {
assert($this->typeClass !== null);
// Set back reference to owner
$this->typeClass->reflFields[$this->backRefFieldName]->setValue(
@@ -190,7 +192,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
// If _backRefFieldName is set, then the association is bidirectional
// and we need to set the back reference.
if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
if ($this->backRefFieldName && $this->getMapping()['type'] === ClassMetadata::ONE_TO_MANY) {
assert($this->typeClass !== null);
// Set back reference to owner
$this->typeClass->reflFields[$this->backRefFieldName]->setValue(
@@ -271,10 +273,14 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
/**
* INTERNAL: Gets the association mapping of the collection.
*
* @psalm-return array<string, mixed>|null
* @psalm-return AssociationMapping
*/
public function getMapping(): ?array
public function getMapping(): array
{
if ($this->association === null) {
throw new UnexpectedValueException('The underlying association mapping is null although it should not be');
}
return $this->association;
}
@@ -291,8 +297,8 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
if (
$this->association !== null &&
$this->association['isOwningSide'] &&
$this->association['type'] === ClassMetadata::MANY_TO_MANY &&
$this->getMapping()['isOwningSide'] &&
$this->getMapping()['type'] === ClassMetadata::MANY_TO_MANY &&
$this->owner &&
$this->em !== null &&
$this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()
@@ -333,7 +339,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function remove($key)
{
@@ -351,9 +357,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
if (
$this->association !== null &&
$this->association['type'] & ClassMetadata::TO_MANY &&
$this->getMapping()['type'] & ClassMetadata::TO_MANY &&
$this->owner &&
$this->association['orphanRemoval']
$this->getMapping()['orphanRemoval']
) {
$this->getUnitOfWork()->scheduleOrphanRemoval($removed);
}
@@ -362,7 +368,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function removeElement($element): bool
{
@@ -376,9 +382,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
if (
$this->association !== null &&
$this->association['type'] & ClassMetadata::TO_MANY &&
$this->getMapping()['type'] & ClassMetadata::TO_MANY &&
$this->owner &&
$this->association['orphanRemoval']
$this->getMapping()['orphanRemoval']
) {
$this->getUnitOfWork()->scheduleOrphanRemoval($element);
}
@@ -387,15 +393,15 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function containsKey($key): bool
{
if (
! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
&& isset($this->association['indexBy'])
! $this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
&& isset($this->getMapping()['indexBy'])
) {
$persister = $this->getUnitOfWork()->getCollectionPersister($this->association);
$persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping());
return $this->unwrap()->containsKey($key) || $persister->containsKey($this, $key);
}
@@ -404,14 +410,14 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @template TMaybeContained
*/
public function contains($element): bool
{
if (! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->getUnitOfWork()->getCollectionPersister($this->association);
if (! $this->initialized && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping());
return $this->unwrap()->contains($element) || $persister->contains($this, $element);
}
@@ -420,22 +426,22 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get($key)
{
if (
! $this->initialized
&& $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
&& isset($this->association['indexBy'])
&& $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
&& isset($this->getMapping()['indexBy'])
) {
assert($this->em !== null);
assert($this->typeClass !== null);
if (! $this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->association['indexBy'])) {
if (! $this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->getMapping()['indexBy'])) {
return $this->em->find($this->typeClass->name, $key);
}
return $this->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key);
return $this->getUnitOfWork()->getCollectionPersister($this->getMapping())->get($this, $key);
}
return parent::get($key);
@@ -443,7 +449,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
public function count(): int
{
if (! $this->initialized && $this->association !== null && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
if (! $this->initialized && $this->association !== null && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->getUnitOfWork()->getCollectionPersister($this->association);
return $persister->count($this) + ($this->isDirty ? $this->unwrap()->count() : 0);
@@ -453,7 +459,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function set($key, $value): void
{
@@ -467,7 +473,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function add($value): bool
{
@@ -485,7 +491,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
/* ArrayAccess implementation */
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function offsetExists($offset): bool
{
@@ -493,7 +499,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
#[ReturnTypeWillChange]
public function offsetGet($offset)
@@ -502,7 +508,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function offsetSet($offset, $value): void
{
@@ -516,7 +522,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
}
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @return object|null
*/
@@ -539,11 +545,12 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
return;
}
$uow = $this->getUnitOfWork();
$uow = $this->getUnitOfWork();
$association = $this->getMapping();
if (
$this->association['type'] & ClassMetadata::TO_MANY &&
$this->association['orphanRemoval'] &&
$association['type'] & ClassMetadata::TO_MANY &&
$association['orphanRemoval'] &&
$this->owner
) {
// we need to initialize here, as orphan removal acts like implicit cascadeRemove,
@@ -559,7 +566,7 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$this->initialized = true; // direct call, {@link initialize()} is too expensive
if ($this->association['isOwningSide'] && $this->owner) {
if ($association['isOwningSide'] && $this->owner) {
$this->changed();
$uow->scheduleCollectionDeletion($this);
@@ -598,8 +605,8 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
*/
public function slice($offset, $length = null): array
{
if (! $this->initialized && ! $this->isDirty && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->getUnitOfWork()->getCollectionPersister($this->association);
if (! $this->initialized && ! $this->isDirty && $this->getMapping()['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
$persister = $this->getUnitOfWork()->getCollectionPersister($this->getMapping());
return $persister->slice($this, $offset, $length);
}
@@ -650,8 +657,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
return $this->unwrap()->matching($criteria);
}
if ($this->association['type'] === ClassMetadata::MANY_TO_MANY) {
$persister = $this->getUnitOfWork()->getCollectionPersister($this->association);
$association = $this->getMapping();
if ($association['type'] === ClassMetadata::MANY_TO_MANY) {
$persister = $this->getUnitOfWork()->getCollectionPersister($association);
return new ArrayCollection($persister->loadCriteria($this, $criteria));
}
@@ -663,11 +671,11 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$criteria = clone $criteria;
$criteria->where($expression);
$criteria->orderBy($criteria->getOrderings() ?: $this->association['orderBy'] ?? []);
$criteria->orderBy($criteria->getOrderings() ?: $association['orderBy'] ?? []);
$persister = $this->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
$persister = $this->getUnitOfWork()->getEntityPersister($association['targetEntity']);
return $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
return $association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
? new LazyCriteriaCollection($persister, $criteria)
: new ArrayCollection($persister->loadCriteria($criteria));
}
@@ -6,6 +6,7 @@ namespace Doctrine\ORM\Persisters\Collection;
use BadMethodCallException;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
@@ -24,11 +25,13 @@ use function sprintf;
/**
* Persister for many-to-many collections.
*
* @psalm-import-type AssociationMapping from ClassMetadata
*/
class ManyToManyPersister extends AbstractCollectionPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function delete(PersistentCollection $collection)
{
@@ -49,7 +52,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{
@@ -80,7 +83,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get(PersistentCollection $collection, $index)
{
@@ -99,7 +102,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function count(PersistentCollection $collection)
{
@@ -169,7 +172,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function containsKey(PersistentCollection $collection, $key)
{
@@ -246,10 +249,15 @@ class ManyToManyPersister extends AbstractCollectionPersister
foreach ($parameters as $parameter) {
[$name, $value, $operator] = $parameter;
$field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
$params[] = $value;
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
$field = $this->quoteStrategy->getColumnName($name, $targetClass, $this->platform);
if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
} else {
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
$params[] = $value;
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
}
}
$tableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
@@ -286,7 +294,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
* JOIN.
*
* @param mixed[] $mapping Array containing mapping information.
* @psalm-param array<string, mixed> $mapping
* @psalm-param AssociationMapping $mapping
*
* @return string[] ordered tuple:
* - JOIN condition to add to the SQL
@@ -339,7 +347,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
* Generate ON condition
*
* @param mixed[] $mapping
* @psalm-param array<string, mixed> $mapping
* @psalm-param AssociationMapping $mapping
*
* @return string[]
* @psalm-return list<string>
@@ -24,7 +24,7 @@ use function is_string;
class OneToManyPersister extends AbstractCollectionPersister
{
/**
* {@inheritdoc}
* {@inheritDoc}
*
* @return int|null
*/
@@ -50,7 +50,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function update(PersistentCollection $collection)
{
@@ -61,7 +61,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function get(PersistentCollection $collection, $index)
{
@@ -87,7 +87,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function count(PersistentCollection $collection)
{
@@ -103,7 +103,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function slice(PersistentCollection $collection, $offset, $length = null)
{
@@ -114,7 +114,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function containsKey(PersistentCollection $collection, $key)
{
@@ -138,7 +138,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function contains(PersistentCollection $collection, $element)
{
@@ -158,7 +158,7 @@ class OneToManyPersister extends AbstractCollectionPersister
}
/**
* {@inheritdoc}
* {@inheritDoc}
*/
public function loadCriteria(PersistentCollection $collection, Criteria $criteria)
{

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