Initializing e.g. a readonly ID does not require loading any data from
the database. However, calling isInitialized() on the reflection of a
readonly property triggers the native lazy object initialization.
If we have a lazy property at hand, then the property cannot be initialized
already, so it is safe to skip the call.
* FIX: Handle int-backed enums for values stored as string values in MySQL ENUM columns
Related issue: doctrine#12274
* FIX: Apply coding standard changes
Related issue: doctrine#12274
* FIX: Add unit test cases
Related issue: doctrine#12274
* Update branch metadata
3.6.0 has been released. As a consequence:
- 3.7.x is the next minor branch;
- 3.6.x is the current branch;
- 3.5.x is no longer maintained.
* doc: drop old releases
We should reduce the number of versions we have, so let's remove docs
for versions that have less than 2k downloads per day.
Right now, the ORM handles the conversion of strings that happen to be
default expressions for date, time and datetime columns into the
corresponding value objects.
Let us allow users to specify these value objects directly, and
deprecate relying on the aforementioned conversion.
I think #11289 did not completely fix problem for eager fetch.
Change in that PR checked if primary key of target class is composite but that does not matter when loading collection by foreign key.
It should check if foreign key on target class is composite.
Fix from that PR did not work for me because i had entity with regular autogenerated id (single column), but foreign key referenced entity with composite primary key, like SecondLevelWithoutCompositePrimaryKey in this PR.
Checking if foreign key is composite fixed the problem for me.
This adds the membervariable hints to the QueryBuilder to enable setting hints
that will be applied to the query when it is created. This can help trigger
custom walker classes when the query is not adressable driectly e.g. in
Symfony Form Extensions where the quer_builder normalizer is handed the querybuilder
directly. Also see #11849
The feature mirrors the hint feature from the Query class.
This also adds tests for the hints in the QueryBuilder to ensure that those are added
correctly and applied to the query itself on creation
This addresses the deprecation introduced in
https://github.com/doctrine/dbal/pull/7195
A follow-up should be to deprecate not using these value objects in
field mappings, so that we do not just reproduce the same checks that
the DBAL wants to remove.
DQL arbitrary joins are semantically equivalent to SQL joins, so using
the same keyword reduces confusion. It also means that in next major
version, the WITH keyword will only be about applying adhoc filtering on
relations instead of having 2 responsibilities.
* Do not eagerly set metadata from ResolveTargetEntityListener
When using the `ResolveTargetEntityListener` to substitute `targetEntities` in association mappings, do not eagerly put the resolved (target) entity into the class metadata cache under the class name of the original entity.
#### Motivation
I have a library that wants to distribute a MappedSuperclass as the base for some functionality. It will be necessary that clients using the library will extend the MappedSuperclass to fill in some blanks, creating the first real `#[Entity]` instance of it.
This client-provided entity will be the primary means of working with the class. Thus, I was following the [note in the documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/inheritance-mapping.html#mapped-superclasses:~:text=It%20is%2C%20however) and using the `ResolveTargetEntityListener` to declare that whenever an association refers to that mapped superclass, the particular entity class shall be used instead.
> 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 [ResolveTargetEntityListener](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/cookbook/resolve-target-entity-listener.html) 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.
#### Changes made
The `ResolveTargetEntityListener` primarily does what its name suggests: For newly loaded class metadata, it inspects all associations declared and replaces the `targetEntity` with new (resolved) values.
But additionally, when a loaded class is the target of such a resolution, it would also put the class metadata into the cache under the name of the original entity.
I think that extra step is wrong, and this PR removes it. It had the side effect that when other classes extending the MappedSuperclass were loaded _after_ the resolve target class has been seen for the first time, the metadata for those classes would not inherit from the mapped superclass anymore, but from the target entity class instead. In my real-life use case, this causes weird mapping errors down the road; as of ^3.0, it would throw a mapping exception asking to configure inheritance mapping. But note that there would be no inheritance between the two entity classes at all.
#### More background
The documentation [describes the use of `ResolveTargetEntityListener`](https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/cookbook/resolve-target-entity-listener.html) with an interface that is resolved to an entity class. For an interface, adding the extra metadata does not make a difference, since it never interferes with actual entity or mapped superclasses.
The initial idea of adding a copy of the entity class metadata under the interface name came from commit
9c7f3f2747 in #385. The goal was to make it possible to also find entities by interface names, like so:
```
$em->find('Foo\BarBundle\Entity\PersonInterface', 1);
```
It then [turned out that this only worked when the resolution had already been applied](https://github.com/doctrine/orm/pull/385#issuecomment-6658893). So, the new `onClassMetadataNotFound` event was added and the resolution map would be checked in that case as well (#1181). The inital code stayed in place, possibly giving a small performance gain.
In my real-world use case and the test case I added in this PR, the associations are even self-referencing. That should not really be necessary for the problem to surface. I decided to keep it this way to show that the `targetEntity` need not be an interface after all, and that a MappedSuperclass can be used in the same way.
This PR fixes the `Criteria` matching API for `IN` and `NIN` conditions with values that are arrays, by making sure that type information for the matched field is passed to the DBAL level correctly.
Passing the right parameter type to DBAL is important to make sure parameter conversions are applied before matching at the database level.
Memory-based collections (`ArrayCollection`s or initialized collection fields) would perform matching on the objects in memory where no type conversion to the database representation is required, giving correct results.
But uninitialized collections that have their conditions evaluated at the database level need to convert parameter values to the database representation before performing the comparison.
One extra challenge is that the DBAL type system does currently not support array-valued parameters for custom types. Only a [limited list of types](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.2/reference/data-retrieval-and-manipulation.html#list-of-parameters-conversion) is supported.
I discussed this with @morozov at the Doctrine Hackathon and came to the conclusion that it would be best to work around this limitation at the ORM level. Thus, this fix recognizes array-valued parameters and creates multiple placeholders (like `?, ?, ?`) for them, flattening out the arrays in the parameter list and repeating the type information for each one of them.
Previous stalled attempt to fix this was in #11897.
The order of results is not guaranteed unless we do so, and the test can
fail in some cases:
There was 1 failure:
1) Doctrine\Tests\ORM\Functional\QueryTest::testToIterableWithMixedResultArbitraryJoinsScalars
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'Doctrine 2'
+'lala 2'
/home/runner/work/orm/orm/tests/Tests/ORM/Functional/QueryTest.php:481
* 3.5.x:
Fix missing import
Remove calls to getMockForAbstractClass() (#12003)
Upgrade to doctrine/coding-standard 14
Bump doctrine/.github from 7.3.0 to 8.0.0
* 3.5.x:
Add a CI job that fails on deprecations (#12188)
use the empty string instead of null as an array offset (#12181)
do not call setAccessible() on PHP >= 8.1 (#12182)
Fix docs on final entities (#12176)
Remove Database and Model First chapters that said little of value.
Switch to IgnoreDeprecations
docs: consistent PostgreSQL's name case
docs: generation strategies differences between DBAL 3 and 4
Check extra condition to decide if a test was skipped
Use PHPUnit 11 when possible
Migrate away from annotations in tests
Migrate away from assertStringNotMatchesFormat()
Migrate to willReturn()
Migrate away from getMockForAbstractClass()
Fix `IN`/`NOT IN` expression handling and support enums when matching on to-many-collections
In the scope of https://github.com/doctrine/persistence/pull/433
(available from `doctrine/persistence` >= 4.1) there was added
`ColocatedMappingDriver::$classLocator` (`ClassLocator`) property,
which allows passing any instance of `ClassLocator` for the mapping
driver to use. This commit integrates those changes into `AttributeDriver`.
Since `doctrine/orm` maintains the support for `doctrine/persistence`
of older versions, tests ensure that `ClassLocator` actually exists.
The old paths' behaviour can be adapted into the new by passing
`FileClassLocator` into `AttributeDriver`
(see `FileClassLocator::createFromDirectories($directoryPaths)`).
It seems that this could happen with PHPUnit 10, then tearDown() would
crash when calling `clear()` on null, but then PHPUnit 10 did not show
that exception.
Using a nullable column that references another table as part of a
primary key makes no sense, and is ignored by DBAL. Let us ignore it at
the ORM level.
Fix JoinedSubclassPersister as BasicEntityPersister was already fixed in GH-10735.
The fix can be verified by modifying UnitOfWork to execute `BasicEntityPersister::executeInserts()` for multiple entities at once for the same entity class/persister instance - https://github.com/doctrine/orm/blob/2.20.3/src/UnitOfWork.php#L1186 - then reproducible on `Doctrine\Tests\ORM\Functional\Ticket\GH10531Test::testInserts` test.
As extending/modifying UnitOfWork in tests in not easily possible, I submit this fix for v2.x without a test.
That test was testing too many thing and not really making it clear what
the expected output was, given some output. Instead, let us create 2
tests, each pertaining to the class under test.
In aa141bf001, I wrongly assumed that
$tableName would never contain a dot as I was not able to write a test
that caused that to happen.
The secret recipe appears to be to define a schema and to quote the
table name.
To fix it for the table name, I am calling quoteSingleIdentifier()
before doing the concatenation between schema name and table name.
To fix it for the sequence name, which seems only useful when using DBAL
3 for some reason, I reuse some of the logic of the deprecated method.
Fixes#12041
When the persister is extended to do a multi update, the caching is not
wanted. The impact is minimal as the CPU/time overhead per query is
much bigger and the prepared statement is not cached anyway.
This should fix the build. Maybe some of the reported issues can be
addressed, but if that is the case, it should probably be done on the
next minor branch.
This fixes a dangerous bug where LIMIT is silently ignored in DELETE
operations, potentially causing developers to delete all rows instead
of just the intended subset. The setMaxResults() method would be
silently omitted from the final query, making operations like
delete last entry accidentally delete entire tables.
Add proper handling for binary primary key parameter types that were
previously causing runtime exceptions. The existing parameter type
switch statement was missing a case for binary types, leading to
unhandled scenarios when working with binary primary keys.
This ensures consistent parameter type handling across all supported
primary key data types in the ORM.
That method has been deprecated for almost 15 years, in
85d40847ac.
On top of that I'm adding a deprecation for something related that was
scheduled for deprecation at in the same commit.
These tests and benchmarks are still relevant with lazy objects.
I am not setting up an extra job to test phpbench without native lazy
objects. Instead, I'm bumping the PHP version to 8.4 so that native lazy
objects are in use.
Using the VarExporter Hydrator to assign default values of properties when marking an entity as initialized is needed only when using var-exporter proxies.
For lazy objects, this behavior is already provided by `ReflectionClass::markLazyObjectAsInitialized`
Currently we have ORMSetup::create*Configuration methods with a
$proxyDir argument that is used to configure the proxy directory, but
also as a seed for generating a namespace for cache systems.
Since these methods could be used with named arguments, renaming the
argument is not really an option and we need separate methods.
Adds a new option to Column mapping to add indexes to class fields
directly instead of having to use the Index() class attribute.
This allows users to define indexes in traits
where access to the class isn't available.
Fixes#11982
This reverts commit 12c721f528.
This feature introduces several issues:
- It adds alias.*, which is a first, for instance you cannot do
SELECT u.* FROM User u
- If introduces coupling between property order in mapping fields and
the result.
When using native lazy objects, it should be possible to omit these
arguments, hence the default value.
Also, when using native lazy objects, one should not have to configure
the corresponding Configuration attributes, which means
EntityManager__construct() should be able to pass null to this class,
hence the nullability.
Fixes#11997
In 3.0.0, it is no longer possible to disable lazy ghost objects, and
likewise, it is no longer possible to disable rejecting id collisions in
the identity map, so let us deprecate the related methods.
I was supposed to do this in 3.1.0.
The `SequenceGenerator` is potentially used for PostgreSQL table auto-generated fields, but
the `SequenceGenerator` is not a **POST**-insert generator.
Because the `SequenceGenerator` is used in the middle of `INSERT` operations performed
by persisters, we cannot rely on it in batching operations: disabling it, so we get a green
test suite on PostgreSQL.
This change makes `GH10531Test` pass on PostgreSQL: see #10531
This logic also brings a minor benefit in reducing the number of times `ListenersInvoker#getSubscribedSystems`
is queried.
TODOs:
* [ ] integration test this - it is expected to reduce the number of `EntityPersister#executeInserts()` calls
* [ ] refactor this by creating a new `@internal` class for the batch, and perhaps batch via a generator
* [ ] reduce amount of repeated `getClassMetadata()` calls
* [ ] reduce overall size of `UnitOfWork` code, instead of increasing it
This command's purpose is to provide structured data, except for a call
to caution() that warns the user in case they do not have any mapped
entities or they have errors.
Symfony 7.3 is not available to all of our users, so we cannot switch to
native lazy objects, which require a PHP version higher than the lowest
PHP version we support.
In f256d996cc, I did a global move to
stderr for notifications, and went a bit overboard for
MappingDescribeCommand, which purpose is to output a description.
For some reason, it does not appear to work when nested inside a
code-block directive. Anyway, if you specify the language attribute, you
get markup identical to what you obtain when using code-block and
literalinclude, so this wrapping seems unneeded.
PostLoad listeners might initialize values for transient properties, so
the proxy should not skip initialization when using transient
properties.
Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
The old proxy implementation of doctrine/common was triggered by public
methods rather than access to properties (making public properties
unsupported in entities), so tests could use public instance properties
to track the state of postLoad lifecycle callbacks without triggering
the proxy initialization when reading that state (which then changes the
state of triggering the postLoad callback).
As the new proxy implementation hooks into properties instead, the tests
now use a static method (ensuring it is reset properly before loading
the instance for which we care about the tracking) instead of an
instance property.
This fixes that using a `Criteria` with an `IN` or `NIN` expression on a to-many collection currently leads to an SQL error (#6173). The `ManyToMany` persister needs to know about the slightly different SQL syntax for `[NOT] IN ()`.
In the case of `[NOT] IN` expressions, the value will be an array, which also required me to change (I guess "fix") the parameter type handling. I have pulled the necessary code from the `BasicEntityPersister` and placed it as static helper methods in `PersisterHelper`.
This is somewhat inspired by #11516, which aims at fixing #11481: By re-using the parameter type handling code, it also fixes using backed enums in `EQ`, `IN` and `NIN` expressions within `Criteria` when `matching()` on one-to-many and many-to-many collections.
* Introduce PHP 8.4 lazy proxy/ghost API.
* Call setRawValueWithoutLazyInitialization for support with lazy proxy.
* Refactorings
* Revert test change partially and skip with lazy objects.
* Houskeeping: phpcs
* Run with ENABLE_LAZY_PROXY=1 in php 8.4 matrix.
* Fix ci
* Transient properties are not skipping lazy initialization anymore, to expensive and could lead to errors. Adjust lifecycle test that uses transient properittes for assertions.
* Restore behavior preventing property hook use in 8.4 in unsupported coditions
* Add \ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE
Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
* Rename isNativeLazyObjectsEnabled/enableNativeLazyObjects.
* Housekeeping: phpcs
* Update advanced-configuration docs and make proxy config variables not required anymore with native lazy objects.
* Move code around
* Apply suggestions from code review
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
* Pick suggestions
---------
Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
The dev configuration and CI configuration should not diverge this much.
I do not think the current situation was intended. A difference that
remains after my changes is the bootstrap file, which in dev seems aimed
at helping contributors setup their environment.
If scheduleForInsert was called in prePersist hook already, then persistNew need to check this case first, otherwise a ORMInvalidArgumentException will be thrown
This fixes a bug that arises when using Pagination and an entity relation is mapped with fetch-mode EAGER but setFetchMode LAZY (or anything that is not EAGER) has been used on the query. If the query use WITH condition, an exception is incorrectly raised (Associations with fetch-mode=EAGER may not be using WITH conditions).
The class LimitSubqueryOutputWalker clones the query, but not its parameters and hints, so the generated subquery does not know that fetch-mode has been overridden.
Fixes#11741
The bug related (#11694) and fixed mapping of sql column alias to field in entity (#11783) and
invalidate cache [cache/persisted/entity|cache/persisted/collection] when sql filter changes
* 2.20.x:
Introduce testNotListedValueInEnumArray
Fix documentation for JoinColumn nullable (#11798)
Ignore deprecations from doctrine/common
Fix fields of transient classes being considered duplicate with `reportFieldsWhereDeclared`
just one bracket (...) gives
Exception : [Doctrine\ORM\Query\QueryException] [Syntax Error] line 0, col xx: Error: Expected Doctrine\ORM\Query\Lexer::T_IDENTIFIER, got '('
Previously, when using a custom naming strategy, explicitly declaring a JoinColumn required specifying the referencedColumnName always as it would default to id no matter the naming strategy. This PR changes it to be determines correctly.
Ref #9558
When there are no conflicts between branches, we create pull requests
where the head branch is a branch on the origin repository. That branch
points to a commit that should already have coverage information
provided by the build that happens after merging a regular pull request.
The thing is, coverage information provided by builds that happen before
merging a pull request are associated with the commit of the head
repository. This means that when merging up 1.2 into 1.3, the build
produces coverage information that is the result of a merge between 1.2
and 1.3, and associates it with 1.2, although it is run on with a
codebase that is much closer to 1.3 (and is in fact supposed to become
1.3 after the merge).
This means that when we create a merge up PR from 1.2 to anything else,
the coverage information is going to be wrong until a PR targeting 1.2
gets merged.
I do not think we need coverage about conflictless merge up PRs more
than we need accurate numbers, so I propose we disable the upload for
those instead of, say, trying to associate them with the temporary merge
commit.
CachedPersisterContext::$selectJoinSql should be clear or regenerated when sqlFilter changed
The problem reproduce when in use fetch=EAGER and use additional sql filter on this property
I think it would be great to use literalinclude for big code snippets,
because our IDEs could warn us about issues, and it would be easily to
showcase our coding standard. Before we do that though, let us validate
that it renders as expected. I have picked a complex example where we
have a configuration block.
* Add a test covering the #11112 issue
* Add new OutputWalker and SqlFinalizer interfaces
* Add a SingleSelectSqlFinalizer that can take care of adding offset/limit as well as locking mode statements to a given SQL query.
Add a FinalizedSelectExecutor that executes given, finalized SQL statements.
* In SqlWalker, split SQL query generation into the two parts that shall happen before and after the finalization phase.
Move the part that generates "pre-finalization" SQL into a dedicated method. Use a side channel in SingleSelectSqlFinalizer to access the "finalization" logic and avoid duplication.
* Fix CS violations
* Skip the GH11112 test while applying refactorings
* Avoid a Psalm complaint due to invalid (?) docblock syntax
* Establish alternate code path - queries can obtain the sql executor through the finalizer, parser knows about output walkers yielding finalizers
* Remove a possibly premature comment
* Re-enable the #11112 test
* Fix CS
* Make RootTypeWalker inherit from SqlOutputWalker so it becomes finalizer-aware
* Update QueryCacheTest, since first/max results no longer need extra cache entries
* Fix ParserResultSerializationTest by forcing the parser to produce a ParserResult of the old kind (with the executor already constructed)
* Fix WhereInWalkerTest
* Update lib/Doctrine/ORM/Query/Exec/PreparedExecutorFinalizer.php
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
* Fix tests
* Fix a Psalm complaint
* Fix a test
* Fix CS
* Make the NullSqlWalker an instance of SqlOutputWalker
* Avoid multiple cache entries caused by LimitSubqueryOutputWalker
* Fix Psalm complaints
* Fix static analysis complaints
* Remove experimental code that I committed accidentally
* Remove unnecessary baseline entry
* Make AddUnknownQueryComponentWalker subclass SqlOutputWalker
That way, we have no remaining classes in the codebase subclassing SqlWalker but not SqlOutputWalker
* Use more expressive exception classes
* Add a deprecation message
* Move SqlExecutor creation to ParserResult, to minimize public methods available on it
* Avoid keeping the SqlExecutor in the Query, since it must be generated just in time (e. g. in case Query parameters change)
* Address PHPStan complaints
* Fix tests
* Small refactorings
* Add an upgrade notice
* Small refactorings
* Update the Psalm baseline
* Add a missing namespace import
* Update Psalm baseline
* Fix CS
* Fix Psalm baseline
---------
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
It maybe happen that the SQL COMMIT statement is successful, but then
something goes wrong. In that kind of case, you do not want to attempt a
rollback.
This was implemented in UnitOfWork::commit(), but for some reason not in
the similar EntityManager methods.
We use this method only from within one of our own test cases, and I don't see how it would be useful to anybody else outside – it has to be called on the `Parser` instance which exists internally in the `Query` only.
Deprecating and removing it in 3.x allows for a slight simplification in the `Parser` there, since we do no longer need the field (it can be a local variable).
People that might have experimented with property hooks while still
using ORM < 2.20.0 need to know that they need to remove their
experiment or upgrade to a version that explicitly supports them.
* 2.19.x:
Make nullable parameters explicit in generated entities (#11625)
Update attributes-reference.rst
Bump doctrine/.github from 5.0.1 to 5.1.0 (#11616)
Move orphan metadata to where it belongs
PHPStan 1.12 (#11585)
When adding the same lifecycle event callback to two or more lifecycle events, the generator will create a stub for each event resulting in fatal 'Cannot redeclare' errors. That is, only if the callback name contains uppercase letters.
If the source entity for an inverse (non-owning) 1-1 relationship is
identified by an association then the identifying association may not
be set when an inverse one-to-one association is resolved. This means
that no data is available in the entity to resolve the needed column
value for the join query.
The original entity data can be retrieved from the unit of work and
is used as a fallback to populate the query condition.
Fixes#11108
single-inheritence entity parent as targetEntity.
When using the parent entity for a single-inheritence table as the
targetEntity for a property, the discriminator value should be all
of the values in the discriminator map.
OneToManyPersister::deleteEntityCollection has been amended to
reflect this.
This change makes CountWalker use COUNT(*) instead of
COUNT(tbl.id), when the user declared that their query
does not need to use (SELECT) DISTINCT, which is
commonly the case when there are no JOINs in the query,
or when the JOINs are only *ToOne.
Research showed that COUNT(*) allows databases to use
index(-only) scans more eagerly from any of the
indexed columns, especially when the query is using
a WHERE-condition that filters on an indexed column.
The Query class (used for DQL queries) takes care of using the value and
type as is when a type was specified for a parameter instead of going
through the default processing of values.
The NativeQuery class was missing the equivalent check, making the
custom type work only if the default processing of values does not
convert the value to a different one.
When a ManyToOne attribute is encountered on an Embeddable class, the exception message reads "Attribute "Doctrine\ORM\Mapping\OneToMany" on embeddable [class] is not allowed.". This should be "Doctrine\ORM\Mapping\ManyToOne" on embeddable [class] is not allowed.".
When using ghost objects, the method was leaking a `static` return type due to the way it was implemented, which is incompatible with the native return type that will be added in doctrine/persistence v4.
Remove redundant condition to check if target class contains foreign
identifier in order to allow fetching a null for relations with
composite keys, when part of the key value is null.
* 2.19.x:
Fix OneToManyPersister::deleteEntityCollection missing discriminator column/value. (GH-11500)
Skip joined entity creation for empty relation (#10889)
ci: maintained and stable mariadb version (11.4 current lts) (#11490)
fix(docs): use string value in `addAttribute`
Replace assertion with exception (#11489)
Use ramsey/composer-install in PHPBench workflow
update EntityManager#transactional to EntityManager#wrapInTransaction
Fix cloning entities
Consider usage of setFetchMode when checking for simultaneous usage of fetch-mode EAGER and WITH condition.
* 2.19.x:
Fix OneToManyPersister::deleteEntityCollection missing discriminator column/value. (GH-11500)
Skip joined entity creation for empty relation (#10889)
ci: maintained and stable mariadb version (11.4 current lts) (#11490)
fix(docs): use string value in `addAttribute`
Replace assertion with exception (#11489)
Use ramsey/composer-install in PHPBench workflow
update EntityManager#transactional to EntityManager#wrapInTransaction
Fix cloning entities
Consider usage of setFetchMode when checking for simultaneous usage of fetch-mode EAGER and WITH condition.
This fixes a bug that arises when an entity relation is mapped with
fetch-mode EAGER but setFetchMode LAZY (or anything that is not EAGER)
has been used on the query. If the query use WITH condition, an
exception is incorrectly raised (Associations with fetch-mode=EAGER may
not be using WITH conditions).
Fixes#11128
Co-Authored-By: Albert Prat <albert.prat@interactiu.cat>
* 3.1.x:
Fix failed merge (#11464)
Test with actual lock modes (#11465)
Backport test for Query::setLockMode() (#11463)
Fix return type of Query::getLockMode() (#11462)
* 3.1.x:
Using an integer as discriminator value with ORM v3
Using an integer as discriminator value with ORM v3
Bump ramsey/composer-install from 2 to 3 (#11442)
Use ramsey/composer-install in PHPBench workflow (#11444)
Bump doctrine/.github from 3.0.0 to 5.0.1
Upgrade codecov/codecov-action
Setup Dependabot
This fixes a bug that occurred when configuring integers as discriminator values and using DQL instanceOf function in the queries. Doctrine throws a type error whenever the application generates these queries.
This fixes a bug that occurred when configuring integers as discriminator values. Doctrine throws a type error whenever the application generates queries.
Targeting 2.19.x, since we want the updates to bubble up. Since
Dependabot has had no effect on doctrine/dbal yet, I suppose that means
that "dependabot.yml" must be present on the default branch.
If the entity gets reloaded from database before the deletions are
executed UnitOfWork needs to be able to return the original instance in
REMOVED state.
Changed capitalized column names to lowercase for consistency. Other occurances of column names mentioned as lowercase several times at this same page.
See https://github.com/doctrine/collections/issues/368 for the same
issue in doctrine/collections which has been fixed there.
The issue happens when using ->contains(). Running psalm emits
> InvalidArgument - Argument 1 of Doctrine\ORM\PersistentCollection::contains
> expects
> TMaybeContained:fn-doctrine\common\collections\readablecollection::contains
> as mixed, but … provided.
Solution: we should either not define @template TMaybeContained or
re-define the complete psalm docblock from ReadableCollection.
Repairing the docblock necessitates an update to the psalm baseline:
one "known issue" is no longer an issue and thus removed.
* 3.1.x:
Adjust PHPBench mocks
Set column length explicitly (#11393)
Add missing import
Remove unused variable (#11391)
Fixed proxy initialization for EnumReflectionProperty
Remove older versions from the docs (#11383)
[Documentation] Removing "Doctrine Mapping Types" ... (#11384)
[GH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (#11380)
Improve lazy ghost performance by avoiding self-referencing closure. (#11376)
Remove outdated git metadata files (#11362)
Switch join columns around, otherwise index doesnt match
Key on fk
Fix entities and mapping.
Minor code style fix in AbstractRemoteControl
Do not schedule batch loading for target classes with composite identifier.
Cleanup tests not to use model sets.
provides a test case for github issue 11154
The properties `indexes` and `uniqueConstraints` were used by the
`AnnotationDriver` but were never implemented for the `AttributeDriver`.
Since the `AnnotationDriver` doesn't exist anymore these can become
deprecated and will then be removed afterwards.
Change `PESSIMISTIC_READ` to `PESSIMISTIC_WRITE`. Otherwise, the solution to the race condition at the bottom of the article would allow concurrent reads, which would not solve the presented race condition problem.
* Fix loading SchemaTool::getSchemaFromMetadata() uniqueConstraint without a name
Fixes a type miss-match exception when reading a UniqueConstraint defined on an Entity which doesn't have a predefined name.
* Fix deprecation on DBAL 3
---------
Co-authored-by: Alexander M. Turek <me@derrabus.de>
* Use class from persistence package
It is meant to remove duplication between the ORM and the ODM.
* Update UPGRADE.md
Co-authored-by: Steve Todd <stodd@mashbo.com>
---------
Co-authored-by: Alexander M. Turek <me@derrabus.de>
Co-authored-by: Steve Todd <stodd@mashbo.com>
`getAssociationMappedByTargetField()` returns `null` when called with
the owning side of an association.
This is undocumented and wrong because the phpdoc advertises a string as
a return type.
Instead, callers should ensure they are calling that method with an
inverse side.
Closes#11250
In 2.x, getAssociationMappedByTargetField() used to return null when
called with the owning side of an association.
That was undocumented and wrong because the phpdoc advertises a string
as a return type.
In 6ce0cf4a3d, I wrongly assumed that
nobody would be calling this method with the owning side of an
association.
Let us throw a full fledged exception and advertise the proper way of
avoiding this situation.
Closes#11250
The phpdoc comment for the return type of
ClassMetadata::fullyQualifiedClassName() says that the return type will
be null if the input value is null. I have made it more precise by
using "if and only if", made the null check more strict and translated
that into template annotations. Also, since we say we return a
class-string, I've asserted that.
Although this method is guaranteed to return either null or something
that can be used as a fully qualified class name, it never actually
checks that the class actually exists. Adding such a check breaks
several tests, including some that expect a exceptions at some later
points in the execution.
* 3.0.x:
Test different ways of settings query parameters
Be less restrictive in DiscriminatorColumnMapping phpdoc (#11226)
Allow (Array)ParameterType in QueryBuilder
After 2.17 (some?) EAGER fetched OneToMany associations stopped working, if they have multiple join columns. Loads for these associations will trigger a `MessingPositionalParameter` exception "Positional parameter at index 1 does not have a bound value".
This test case should reproduce this issue, so it can be fixed.
The comment above mentions that on some platforms, it might return
false, and this is why there is a check in the first place. Let us do
exactly what is mentioned in the comment.
* 3.0.x:
Remove broken assertion from DateAddFunction and DateSubFunction (#11243)
Remove unused trait
[Documentation] Adding link to Postgres upgrade article (#11257)
fix: support array-type arg in QB variadic calls (#11242)
* 2.19.x:
Fix Static Analysis folder reference (#11281)
docs: recommend safer way to disable logging (#11269)
Remove unused baseline entries
Treat '0' as a legitimate trim char
Add type field mapper documentation to the sidebar
Mark document as orphan
Use correction sectionauthor syntax
Make docs valid according to guides 0.3.3 (#11252)
* Remove trailing newlines
* Recommend safer way to disable logging
Resetting the middlewares on the configuration object will only work if
the connection object hasn't been built from that configuration object
yet. Instead, people should find the logger bound to the logging
middleware and disable it.
* 3.0.x:
Revert "Merge pull request #11229 from greg0ire/add-columns"
Add columns for 3.1.x and 4.0x
Update version ORM from 2 to 3 in docs (#11221)
Clean up outdated sentence (#11224)
Update README.md
Point link to correct upgrade guide (#11220)
Ignore subclasses without discriminatorValue when generating discriminator column condition SQL (#11200)
Update branches in README
* 2.18.x:
Point link to correct upgrade guide (#11220)
Ignore subclasses without discriminatorValue when generating discriminator column condition SQL (#11200)
Update branches in README
* Add TokenType class
Co-authored-by: Alexander M. Turek <me@derrabus.de>
* Deprecated Lexer constants in favour of TokenType
* Replace all Lexer::T_ occurrences with TokenType::T_
* Add upgrade note
* Fixed import Lexer => TokenType
* Fixed deprecation phpdoc
* Replaced int value with matching constant of TokenType
* Update src/Query/Lexer.php
---------
Co-authored-by: Alexander M. Turek <me@derrabus.de>
After commit 4e8e3ef30b when `\Doctrine\ORM\Query\SqlWalker` generates dicsriminator column condition SQL (method `\Doctrine\ORM\Query\SqlWalker::generateDiscriminatorColumnConditionSQL`) it adds an empty string to the list of possible values if the inheritance hierarchy contains a non-root abstract class.
When the discriminator column is implemented with a custom type in PostgreSQL (equivalent of Enum) the query fails because the type cannot have a value of an empty string. It boils down to the fact that `\Doctrine\ORM\Mapping\ClassMetadataInfo::$subClasses` contains an abstract class and in its Metadata the value of `\Doctrine\ORM\Mapping\ClassMetadataInfo::$discriminatorValue` is `null`.
#### Previous behavior
In version 2.14.1 `\Doctrine\ORM\Mapping\ClassMetadataInfo::$subClasses` does not contain an abstract class.
Fixes#11199, fixes#11177, fixes#10846.
---------
Co-authored-by: Michael Skvortsov <michael.skvortsov@eleving.com>
Co-authored-by: Matthias Pigulla <mp@webfactory.de>
The "any" tags inside the definition for mapped superclasses and
embeddables duplicate what is already done for entities.
The other removed "any" tags are also redundant, as they duplicate
what's already done inside the grandparent "choice" tag.
Starting with version libxml 2.12, such redundant tags cause errors
about the content model not being "determinist".
Fixes#11117
When using `AttributeOverride` to override mapping information inherited from a parent class (a mapped superclass), make sure to keep information about where the field was originally declared.
This is important for `private` fields: Without the correct `declared` information, it will lead to errors when cached mapping information is loaded, reflection wakes up and looks for the private field in the wrong class.
#10927 reported that #10455 broke the way how the default `@SequenceGeneratorDefinition` is created and inherited by subclasses for ID columns using `@GeneratedValue(strategy="SEQUENCE")`.
First, I had to understand how `@SequenceGeneratorDefinition` has been handled before #10455 when entity inheritance comes into play:
* Entity and mapped superclasses inherit the ID generator type (as given by `@GeneratedValue`) from their parent classes
* `@SequenceGeneratorDefinition`, however, is not generally inherited
* ... instead, a default sequence generator definition is created for every class when no explicit configuration is given. In this case, sequence names are based on the current class' table name.
* Once a root entity has been identified, all subclasses inherit its sequence generator definition unchanged.
#### Why did #10455 break this?
When I implemented #10455, I was mislead by two tests `BasicInheritanceMappingTest::testGeneratedValueFromMappedSuperclass` and `BasicInheritanceMappingTest::testMultipleMappedSuperclasses`.
These tests check the sequence generator definition that is inherited by an entity class from a mapped superclass, either directly or through an additional (intermediate) mapped superclass.
The tests expect the sequence generator definition on the entity _to be the same_ as on the base mapped superclass.
The reason why the tests worked before was the quirky behaviour of the annotation and attribute drivers that #10455 was aiming at: The drivers did not report the `@SequenceGeneratorDefinition` on the base mapped superclass where it was actually defined. Instead, they reported this `@SequenceGeneratorDefinition` for the entity class only.
This means the inheritance rules stated above did not take effect, since the ID field with the sequence generator was virtually pushed down to the entity class.
In #10455, I did not realize that these failing tests had to do with the quirky and changed mapping driver behaviour. Instead, I tried to "fix" the inheritance rules by passing along the sequence generator definition unchanged once the ID column had been defined.
#### Consequences of the change suggested here
This PR reverts the changes made to `@SequenceGeneratorDefinition` inheritance behaviour that were done in #10455.
This means that with the new "report fields where declared" driver mode (which is active in our functional tests) we can not expect the sequence generator definition to be inherited from mapped superclasses. The two test cases from `BasicInheritanceMappingTest` are removed.
I will leave a notice in #10455 to indicate that the new driver mode also affects sequence generator definitions.
The `GH10927Test` test case validates the sequence names generated in a few cases. In fact, I wrote this test against the `2.15.x` branch to make sure we get results that are consistent with the previous behaviour.
This also means `@SequenceGeneratorDefinition` on mapped superclasses is pointless: The mapped superclass does not make use of the definition itself (it has no table), and the setting is never inherited to child classes.
Fixes#10927. There is another implementation with slightly different inheritance semantics in #11052, in case the fix is not good enough and we'd need to review the topic later on.
In order to resolve#10348, some changes were included in #10547 to improve the computed _delete_ order for entities.
One assumption was that foreign key references with `ON DELETE SET NULL` or `... CASCADE` need not need to be taken into consideration when planning the deletion order, since the RDBMS would unset or cascade-delete such associations by itself when necessary. Only associations that do _not_ use RDBMS-level cascade handling would be sequenced, to make sure the referring entity is deleted before the referred-to one.
This assumption is wrong for `ON DELETE CASCADE`. The following examples give reasons why we need to also consider such associations, and in addition, we need to be able to deal with cycles formed by them.
In the following diagrams, `odc` means `ON DELETE CASCADE`, and `ref` is a regular foreign key with no extra `ON DELETE` semantics.
```mermaid
graph LR;
C-->|ref| B;
B-->|odc| A;
```
In this example, C must be removed before B and A. If we ignore the B->A dependency in the delete order computation, the result may not to be correct. ACB is not a working solution.
```mermaid
graph LR;
A-->|odc| B;
B-->|odc| A;
C-->|ref| B;
```
This is the situation in #10912. We have to deal with a cycle in the graph. C must be removed before A as well as B. If we ignore the B->A dependency (e.g. because we set it to "optional" to get away with the cycle), we might end up with an incorrect order ACB.
```mermaid
graph LR;
A-->|odc| B;
B-->|odc| A;
A-->|ref| C;
C-->|ref| B;
```
This example has no possible remove order. But, if we treat `odc` edges as optional, A -> C -> B would wrongly be deemed suitable.
```mermaid
graph LR;
A-->|ref| B;
B-->|odc| C;
C-->|odc| B;
D-->|ref| C;
```
Here, we must first remove A and D in any order; then, B and C in any order. If we treat one of the `odc` edges as optional, we might find the invalid solutions ABDC or DCAB.
#### Solution implemented in this PR
First, build a graph with a node for every to-be-removed entity, and edges for `ON DELETE CASCADE` associations between those entities. Then, use [Tarjan's algorithm](https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm) to find strongly connected components (SCCs) in this graph. The significance of SCCs is that whenever we remove one of the entities in a SCC from the database (no matter which one), the DBMS will immediately remove _all_ the other entities of that group as well.
For every SCC, pick one (arbitrary) entity from the group to represent all entities of that group.
Then, build a second graph. Again we have nodes for all entities that are to be removed. This time, we insert edges for all regular (foreign key) associations and those with `ON DELETE CASCADE`. `ON DELETE SET NULL` can be left out. The edges are not added between the entities themselves, but between the entities representing the respective SCCs.
Also, for all non-trivial SCCs (those containing more than a single entity), add dependency edges to indicate that all entities of the SCC shall be processed _after_ the entity representing the group. This is to make sure we do not remove a SCC inadvertedly by removing one of its entities too early.
Run a topological sort on the second graph to get the actual delete order. Cycles in this second graph are a problem, there is no delete order.
Fixes#10912.
It will make fuzzy matchers more efficient, and configuration files more readable.
- lib/Doctrine/ORM becomes just src
- tests/Doctrine/ becomes just tests
Spotted while trying to merge https://github.com/doctrine/orm/pull/11076
(among other things) up into 3.0.x. On that branch, it is no longer
possible for an entity to extend another entity without specifying an
inheritance mapping type.
I think the goal of that inheritance was just to reuse the identifier
anyway, so let's just duplicate the identifier declaration instead.
This PR changes a detail in the commit order computation for depended-upon entities.
We have a parent-child relationship between two entity classes. The association is parent one-to-many children, with the child entities containing the (owning side) back-reference.
Cascade-persist is not used, so all entities have to be passed to `EntityManager::persist()`.
Before v2.16.0, two child entities C1 and C2 will be inserted in the same order in which they are passed to `persist()`, and that is regardless of whether the parent entity was passed to `persist()` before or after the child entities.
As of v2.16.0, passing the parent entity to `persist()` _after_ the child entities will lead to an insert order that is _reversed_ compared to the order of `persist()` calls.
This PR makes the order consistent in both cases, as it was before v2.16.0.
#### Cause
When the parent is passed to `persist()` after the children, commit order computation has to re-arrange the entities. The parent must be inserted first since it is referred to by the children.
The implementation of the topological sort from #10547 processed entities in reverse `persist()` order and unshifted finished nodes to an array to obtain the final result. That leads to dependencies (parent → before c1, parent → before c2) showing up in the result in the reverse order of which they were added.
This PR changes the topological sort to produce a result in the opposite order ("all edges pointing left"), which helps to avoid the duplicate array order reversal.
#### Discussion
* This PR _does not_ change semantics of the `persist()` so that entities would (under all ciscumstances) be inserted in the order of `persist()` calls.
* It fixes an unnecessary inconsistency between versions before 2.16.0 and after. In particular, it may be surprising that the insert order for the child entities depends on whether another referred-to entity (the parent) was added before or after them.
* _Both_ orders (c1 before or after c2) are technically and logically correct with regard to the agreement that `commit()` is free to arrange entities in a way that allows for efficient insertion into the database.
Fixes#11058.
Property names as returned by a cast to array are mangled, and that
mangling is not documented. Returning unprefixed produces the same
result, and is more likely to be supported by external tools relying on
the documented possible return values of __sleep.
For instance symfony/var-exporter does not support mangled names, which
leads to issues when caching query parsing results in Symfony
applications.
* derrabus/3.0.x:
Deprecate annotation classes for named queries
Fix typos
Housekeeping: Revert change to AbstractExporter, not needed without subselect fetch.
Address review comments.
Explain internals of eager loading in a bit more detail and how its configured.
1:1 and M:1 associations also use fetch batch size configuration now.
Add another testcase for DQL based fetch eager of collection.
last violation hopefully
Static analysis
Housekeeping: phpcs
Directly load many to many collections, batching not supported yet. fix tests.
Avoid new fetch mode, use this strategy with fetch=EAGER for collections.
Make sure to many assocatinos are also respecting AbstractQuery::setFetchMode
Disallow use of fetch=SUBSELECT on to-one associations.
Go through Persister API instead of indirectly through repository.
Introduce configuration option for subselect batch size.
Houskeeping: phpcs
Disallow WITH keyword on fetch joined associatiosn via subselect.
[GH-1569] Add new SUBSELECT fetch mode for OneToMany associations.
* 2.17.x:
Deprecate annotation classes for named queries
Fix typos
Housekeeping: Revert change to AbstractExporter, not needed without subselect fetch.
Address review comments.
Explain internals of eager loading in a bit more detail and how its configured.
1:1 and M:1 associations also use fetch batch size configuration now.
Add another testcase for DQL based fetch eager of collection.
last violation hopefully
Static analysis
Housekeeping: phpcs
Directly load many to many collections, batching not supported yet. fix tests.
Avoid new fetch mode, use this strategy with fetch=EAGER for collections.
Make sure to many assocatinos are also respecting AbstractQuery::setFetchMode
Disallow use of fetch=SUBSELECT on to-one associations.
Go through Persister API instead of indirectly through repository.
Introduce configuration option for subselect batch size.
Houskeeping: phpcs
Disallow WITH keyword on fetch joined associatiosn via subselect.
[GH-1569] Add new SUBSELECT fetch mode for OneToMany associations.
When unserializing from a cache entry in the previous format, the
sqlStatements need to be copied from the legacy property to the new
property before the reference is created.
The idea here is that instead of having a backward compatibility layer
in the next major branch, we can have a forward compatibility layer in
this branch.
This requires heavily adapting tests, because the proxy instance must:
- be an instance of InternalProxy (easy)
- be a valid entity (hard, especially for PHPUnit)
When transforming these phpdoc types into native types, things break
down. They are correct according to the EBNF, but in practice, there are
so-called phase 2 optimizations that allow using ConditionalPrimary,
ConditionalFactor and ConditionalTerm instances in places where
ConditionalExpression is used.
doctrine/common has been split in several packages. A lot of what was
true about doctrine/common is true about doctrine/persistence today, so
let us simply reuse the existing paragraphs and mention persistence
instead of common.
This reduces our dependency to this shared library that now holds very
little code we use.
The class has not been copied verbatim:
- Unused parameters and methods have been removed.
- The class is final and internal.
- Coding standards have been enforced, including enabling strict_types,
which lead to casting a variable to string before feeding it to
explode().
- A bug found by static analysis has been addressed, where an INI
setting obtained with ini_get() was compared with true, which is never
returned by that function.
- Tests are improved to run on all PHP versions
It is no longer possible to use the "PARTIAL" keyword in a DQL query, or
to artificially build an AST with a partial object expression. It is
still possible to use the result set mapping API to build partial
objects.
What was optimal 10 years ago no longer is, and things might change in
the future. Using AUTO is still the best solution in most cases, and it
should be easy to make it mean something else when it is not.
* 2.17.x:
Allow creating mocks of the Query class (#10990)
Add missing "deprecated" annotation on the annotation driver
Deprecate EntityManager*::getPartialReference()
* 2.17.x:
document Paginator::HINT_ENABLE_DISTINCT
allow to disable "DISTINCT" added to the sql query by the limit subquery walker
Test against php 8.3 (#10963)
update checkout version to version 4
* Use lazy ghosts unconditionally
* Stop extending proxy factory from doctrine/common
Extending it no longer serves any purpose.
* Transform annotation into actual method
It is useful to catch misconfigured dependency constraints. It was
removed in 413c33274d.
This implies configuring mocks so as to support psr/cache 1
psr/cache 1 does not use native return types, and phpdoc is not enough
to obtain a mock that has typed methods.
* 2.16.x:
PHPStan 1.10.35, Psalm 5.15.0 (#10958)
docs: in text, refer to attributes when talking about metadata (#10956)
Fix bullet list layout (#10951)
docs[query-builder]: fix rendering of `Doctrine\DBAL\ParameterType::*` (#10945)
tests[ORMSetupTest]: testCacheNamespaceShouldBeGeneratedForApcu requires enabled apc (#10940)
docs: use modern named arguments syntax
Ignore "Unknown directive" error
Use a stable release
Remove output directory argument
tutorials[getting-started]: example fix bug id type definition
Verify UnitOfWork::HINT_DEFEREAGERLOAD exists and is true
This avoid situations where you might map a decimal to a float, when it
should really be mapped to a string. This is a big pitfall because in
that situation, the ORM will consider the field changed every time.
We have a lot of errors about "Unknown directive" that we should make
known when implementing guides for Doctrine, but cannot address by
modifying the docs.
The unknown directives are:
- configuration-block
- toc
- tocheader
- sectionauthor
* 2.16.x:
Use required classes for Lifecycle Callback examples (#10916)
Add space before backquote (#10918)
Add back throws annotation to getSingleScalarResult (#10907)
Fix link on known issues docs (#10904)
According to the RST docs,
> [inline markup] it must be separated from surrounding text by non-word
> characters. Use a backslash escaped space to work around that: thisis\ *one*\ word.
Because we were missing a space before backquotes here, the links were
not rendered. Escaping the space allow not to actually produce a space
in the output.
See https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#inline-markup
The changes from #10547, which landed in 2.16.0, cause problems for users calling `EntityManager::flush()` from within `postPersist` event listeners.
* When `UnitOfWork::commit()` is re-entered, the "inner" (reentrant) call will start working through all changesets. Eventually, it finishes with all insertions being performed and `UoW::$entityInsertions` being empty. After return, the entity insertion order, an array computed at the beginning of `UnitOfWork::executeInserts()`, still contains entities that now have been processed already. This leads to a strange-looking SQL error where the number of parameters used does not match the number of parameters bound. This has been reported as #10869.
* The fixes made to the commit order computation may lead to a different entity insertion order than previously. `postPersist` listener code may be affected by this when accessing generated IDs for other entities than the one the event has been dispatched for. This ID may not yet be available when the insertion order is different from the one that was used before 2.16. This has been mentioned in https://github.com/doctrine/orm/pull/10906#issuecomment-1682417987.
This PR suggests to address both issues by dispatching the `postPersist` event only after _all_ new entities have their rows inserted into the database. Likewise, dispatch `postRemove` only after _all_ deletions have been executed.
This solves the first issue because the sequence of insertions or deletions has been processed completely _before_ we start calling event listeners. This way, potential changes made by listeners will no longer be relevant.
Regarding the second issue, I think deferring `postPersist` a bit until _all_ entities have been inserted does not violate any promises given, hence is not a BC break. In 2.15, this event was raised after all insertions _for a particular class_ had been processed - so, it was never an "immediate" event for every single entity. #10547 moved the event handling to directly after every single insertion. Now, this PR moves it back a bit to after _all_ insertions.
Fix regression introduced in #10870
`$result = $this->execute(null, $hydrationMode);` in `getSingleResult` can still throw NoResultException exception.
#10880 reports a case where the changes from #10785 cause entity updates to be missed.
Upon closer inspection, this change seems to be causing it:
https://github.com/doctrine/orm/pull/10785/files#diff-55a900494fc8033ab498c53929716caf0aa39d6bdd7058e7d256787a24412ee4L2990-L3003
The code was changed to use `registerManaged()` instead, which basically does the same things, but (since #10785) also includes an additional check against duplicate entity instances.
But, one detail slipped through tests and reviews: `registerManaged()` also updates `\Doctrine\ORM\UnitOfWork::$originalEntityData`, which is used to compute entity changesets. An empty array `[]` was passed for $data here.
This will make the changeset computation assume that a partial object was loaded and effectively ignore all field updates here:
a616914887/lib/Doctrine/ORM/UnitOfWork.php (L762-L764)
I think that, effectively, it is sufficient to call `registerManaged()` only in the two cases where a proxy was created.
Calling `registerManaged()` with `[]` as data for a proxy object is consistent with e. g. `\Doctrine\ORM\EntityManager::getReference()`.
In the case that a full entity has to be loaded, we need not call `registerManaged()` at all, since that will already happen inside `EntityManager::find()` (or, more specifically, `UnitOfWork::createEntity()` called inside it).
Note that the test case has to make some provisions so that we actually reach this case:
* Load an entity that uses `fetch="EAGER"` on a to-one association
* That association being against a class that uses inheritance (why's that?)
* 2.16.x:
Turn identity map collisions from exception to deprecation notice (#10878)
Add possibility to set reportFieldsWhereDeclared to true in ORMSetup (#10865)
Fix UnitOfWork->originalEntityData is missing not-modified collections after computeChangeSet (#9301)
Add an UPGRADE notice about the potential changes in commit order (#10866)
Update branch metadata (#10862)
In #10785, a check was added that prevents entity instances from getting into the identity map when another object for the same ID is already being tracked.
This caused regressions for users that work with application-provided IDs and expect this condition to fail with `UniqueConstraintViolationExceptions` when flushing to the database.
Thus, this PR turns the exception into a deprecation notice. Users can opt-in to the new behavior. In 3.0, the exception will be used.
Implements #10871.
* Fix original data incomplete after flush
* Apply suggestions from code review
Co-authored-by: Alexander M. Turek <me@derrabus.de>
---------
Co-authored-by: Alexander M. Turek <me@derrabus.de>
This excludes such associations from the commit order computation, since the foreign key constraint will be satisfied when inserting the row.
See https://github.com/doctrine/orm/pull/10735/ for more details about this edge case.
Since support for persistence 2 has been dropped, this method may no
longer acces an aliased class name.
Besides, providing an FQCN with a leading backslash should work since
removing it is the first thing that happens inside
AbstractClassMetadataFactory::getMetadataFor().
1. Inherit ClassMetadataInfo::requiresFetchAfterChange flag from root entity when process parent columns mapping (see ClassMetadataInfo::addInheritedFieldMapping(), it uses same condition as ClassMetadataInfo::mapField()) so JoinedSubclassPersister::assignDefaultVersionAndUpsertableValues() to be called in JoinedSubclassPersister::executeInserts().
2. Override JoinedSubclassPersister::fetchVersionAndNotUpsertableValues() to fetch all parent tables (see $this->getJoinSql() call) generated columns. So make protected BasicEntityPersister::identifierFlattener stateless service (use it flattenIdentifier() method) and BasicEntityPersister::extractIdentifierTypes() (to avoid copy-paste).
3. JoinedSubclassPersister::fetchVersionAndNotUpsertableValues() doesnt check empty $columnNames because it would be an error if ClassMetadataInfo::requiresFetchAfterChange is true while no generated columns in inheritance hierarchy.
4. Initialize JoinedInheritanceRoot not-nullable string properties with insertable=false attribute to avoid attempt to insert default null data which cause error:
PDOException: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column "rootwritablecontent" of relation "joined_inheritance_root" violates not-null constraint
DETAIL: Failing row contains (5, null, dbDefault, dbDefault, , nonUpdatable).
while $rootTableStmt->executeStatement() because JoinedSubclassPersister::getInsertColumnList() have no $insertData (prepared later) to decide is generated column provided by client code or not (so need to skip column)
1. Postgres gives error when insert root entity ($rootTableStmt->executeStatement()) in JoinedSubclassPersister::executeInserts():
PDOException: SQLSTATE[08P01]: <<Unknown error>>: 7 ERROR: bind message supplies 4 parameters, but prepared statement "" requires 6
so exclude notInsertable columns from JoinedSubclassPersister::getInsertColumnList() like it done in parent::prepareInsertData() which call BasicEntityPersister::prepareUpdateData(isInsert: true) where we have condition:
if ($isInsert && isset($fieldMapping['notInsertable']))
2. Try to get generated (notInsertable|notUpdatable) column value on flush() with JoinedSubclassPersister::executeInserts() also fails:
Unexpected empty result for database query.
because method it calls $this->assignDefaultVersionAndUpsertableValues() after insert root entity row, while generated columns in child-entity table, so move call just after insert child row
3. Use option['default'] = 'dbDefault' in functional test entities, to emulate generated value on insert, but declare as generated = 'ALWAYS' for tests purpose (correctness of JoinedSubclassPersister::fetchVersionAndNotUpsertableValues() sql-query)
4. Use JoinedInheritanceRoot::rootField to skip JoinedSubclassPersister::update() optimization for empty changeset in updatable:false columns tests
In 7fa3e6ec7c, a global search and replace
was used for http and https.
This broke the documentation examples in that as soon as you turn on XSD
validation, it will fail because the namespace in the XML file does not
match the ones defined in the XSD file, which do not exhibit the https.
Note that this is not a security concern, because these URIs are
not meant to be actually resolved, but to serve as a unique identifier
for the namespace in which we define our elements.
This change improves scheduling of extra updates in the `BasicEntityPersister`.
Extra updates can be avoided when
* the referred-to entity has already been inserted during the current insert batch/transaction
* we have a self-referencing entity with application-provided ID values (the `NONE` generator strategy).
As a corollary, with this change applications that provide their own IDs can define self-referencing associations as not NULLable.
I am considering this a bugfix since the ORM previously executed additional queries that were not strictly necessary, and that required users to work with NULLable columns where conceptually a non-NULLable column would be valid and more expressive.
One caveat, though:
In the absence of entity-level commit ordering (#10547), it is not guaranteed that entities with self-references (at the class level) will be inserted in a suitable order. The order depends on the sequence in which the entities were added with `persist()`.
Fixes#7877, closes#7882.
Co-authored-by: Sylvain Fabre <sylvain.fabre@assoconnect.com>
The way we have our docs, the sidebar is a separate document and as
such, needs a title. Let us prepend a dummy title until the guides-cli
provides a way to ignore that particular error.
* 2.15.x: (23 commits)
Fix cloning entities when using lazy-ghost proxies
Fixes recomputation of single entity change set when entity contains enum attributes. Due to the fact that originalEntityData contains enum objects and ReflectionEnumProperty::getValue() returns value of enum, comparison of values are always falsy, resulting to update columns value even though it has not changes.
Fix code style issues
Use absolute references
Avoid colon followed by double colon
Use correct syntax for references
Fix invalid reference syntax
Use rst syntax
Use internal link
Escape pipes
Introduce new workflow to test docs
Remove lone dash (#10812)
Treat id field proprites same as regular field
Move three "Ticket/"-style tests to the right namespace
Follow recommendation about multiline type
Fix unserialize() errors when running tests on PHP 8.3 (#10803)
Explain `EntityManager::getReference()` peculiarities (#10800)
Upgrade to Psalm 5.13
test: assert `postLoad` has data first
distinct() updates QueryBuilder state correctly
...
Due to the fact that originalEntityData contains enum objects and
ReflectionEnumProperty::getValue() returns value of enum,
comparison of values are always falsy, resulting to update
columns value even though it has not changes.
According to the Sphinx docs, when in reference/architecture.rst, a
reference to reference/inheritance-mapping would resolve to
reference/reference/inheritance-mapping.rst, because it is relative to
the current document
There are a few requests (#5742, #5368, #5109, #6776) that ask to change the order of operations in the UnitOfWork to perform "deletes before inserts", or where such a switch appears to solve a reported problem.
I don't want to say that this is not doable. But this PR at least adds two tricky examples where INSERTs need to be done before an UPDATE can refer to new database rows; and where the UPDATE needs to happen to release foreign key references to other entities before those can be DELETEd.
So, at least as long as all operations of a certain type are to be executed in blocks, this example allows no other order of operations than the current one.
While trying to understand #3037, I found that it may happen that we have more entries in `\Doctrine\ORM\UnitOfWork::$entityIdentifiers` than in `\Doctrine\ORM\UnitOfWork::$identityMap`.
The former is a mapping from `spl_object_id` values to ID hashes, the latter an array first of entity class names and then from ID hash to entity object instances.
(Basically, "ID hash" is a concatenation of all field values making up the `@Id` for a given entity.)
This means that at some point, we must have _different_ objects representing the same entity, or at least over time different objects are used for the same entity without the UoW properly updating its `::$entityIdentifiers` structure.
I don't think it makes sense to overwrite an entity in the identity map, since that means a currently `MANAGED` entity is replaced with something else.
If it makes sense at all to _replace_ an entity, that should happen through dedicated management methods to first detach the old entity before persisting, merging or otherwise adding the new one. This way we could make sure the internal structures remain consistent.
Apparently, there is consensus about multiline types between:
- PHPStan
- Psalm
- Slevomat Coding Standard
See https://github.com/slevomat/coding-standard/issues/1586#issuecomment-1610195706
Using parenthesis is less ambiguous, it makes it clear to the parser
where the type begins and where it ends.
The change has a positive impact on the Psalm baseline, showing
that psalm-return annotation was not really understood previously.
`EntityManager::merge()` has been deprecated in #8461 and removed in #9488.
This PR removes a few remaining references and artefacts that - to my understanding - refer to it.
A lot of our tests mention it, but I do not think it is important to the
test. Maybe it was a way to have more efficient tests? Most times,
removing the hint does not affect the test outcome.
* Explain `EntityManager::getReference()` peculiarities
As one takeaway from https://github.com/doctrine/orm/issues/3037#issuecomment-1605657003 and #843, we should look into better explaining the `EntityManager::getReference()` method, it’s semantics, caveats and potential responsibilities placed on the user.
This PR tries to do that, so it fixes#10797.
* Update docs/en/reference/advanced-configuration.rst
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
---------
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
Previously calling distinct() when the QueryBuilder was in clean state would cause subsequent getDQL() calls to ignore the distinct queryPart
Fixes#10784
This PR tries to improve the situation/problem explained in #3037:
Under certain conditions – there may be multiple and not all are known/well-understood – we may get inconsistencies between the `\Doctrine\ORM\UnitOfWork::$entityIdentifiers` and `\Doctrine\ORM\UnitOfWork::$identityMap` arrays.
Since the `::$identityMap` is a plain array holding object references, objects contained in it cannot be garbage-collected.
`::$entityIdentifiers`, however, is indexed by `spl_object_id` values. When those objects are destructed and/or garbage-collected, the OID may be reused and reassigned to other objects later on.
When the OID re-assignment happens to be for another entity, the UoW may assume incorrect entity states and, for example, miss INSERT or UPDATE operations.
One cause for such inconsistencies is _replacing_ identity map entries with other object instances: This makes it possible that the old object becomes GC'd, while its OID is not cleaned up. Since that is not a use case we need to support (IMHO), #10785 is about adding a safeguard against it.
In this test shown here, the `merge()` operation is currently too eager in creating a proxy object for another referred-to entity. This proxy represents an entity already present in the identity map at that time, potentially leading to this problem later on.
Add tests for entity insertion and deletion that require the commit order calculation to happen on the entity level. This demonstrates the necessity for the changes in #10547.
This PR contains two tests with carefully constructed entity relationships, where we have a non-nullable `parent` foreign key relationships between entities stored in the same table.
Class diagram:
```mermaid
classDiagram
direction LR
class A
class B
A --> B : parent
B --|> A
```
Object graph:
```mermaid
graph LR;
b1 --> b2;
b2 --> a;
b3 --> b2;
```
#### Situation before #10547
The commit order is computed by looking at the associations at the _table_ (= _class_) level. Once the ordering of _tables_ has been found, entities being mapped to the same table will be processed in the order they were given to `persist()` or `remove()`.
That means only a particular ordering of `persist()` or `remove()` calls (see comment in the test) works:
For inserts, the order must be `$a, $b2, $b1, $b3` (or `... $b3, $b1`), for deletions `$b1, $b3, $b2, $a`.
#### Situation with entity-level commit order computation (as in #10547)
The ORM computes the commit order by considering associations at the _entity_ level. It will be able to find a working order by itself.
This refactoring does two things:
* We can avoid collecting the post insert IDs in a cumbersome array structure that will be returned by the EntityPersisters and processed by the UoW right after. Instead, use a more expressive API: Make the EntityPersisters tell the UoW about the IDs immediately.
* IDs will be available in inserted entities a tad sooner. That may help to resolve#10735, where we can use the IDs to skip extra updates.
When collection updates/join table cleanups do not happen through specialized Entity-/CollectionPersister methods but instead as "plain" updates, we may issue a lot more queries than expected.
In my case a custom doctrine type of Uuid object is converted to string by simply casting it, resulting in a hex DELETE FROM x WHERE id = ? query,
whilst it should've been converted along the way to it's binary representation. This leads to no deletions being made at all as you would expect making use of doctrine custom type's as an identifier.
This commit fixes usage of ramsey/uuid or symfony/uid as custom id types when making use of orphan removal.
This is the result of the contradiction between the phpdoc
(ClassMetadata), and the condition, which guarantees $metadata is not a
ClassMetadata. Relaxing the phpdoc leads to other phpstan issues, about
properties that exist in ClassMetadata but not in
PersistentClassMetadata. The right way to fix this would be to switch
from a deprecation to an exception, but that is not the path we have
taken, and all this will disappear in 3.0.x anyway, so let's not bother.
Add tests for entity insertion and deletion that require writes to different tables in an interleaved fashion, and that have to re-visit a particular table.
#### Background
In #10531, I've given an example where it is necessary to compute the commit order on the entity (instead of table) level.
Taking a closer look at the UoW to see how this could be achieved, I noticed that the current, table-level commit order manifests itself also in the API between the UoW and `EntityPersister`s.
#### Current situation
The UoW computes the commit order on the table level. All entity insertions for a particular table are passed through `EntityPersister::addInsert()` and finally written through `EntityPersister::executeInserts()`.
#### Suggested change
The test in this PR contains a carefully constructed set of four entities. Two of them are of the same class (are written to the same table), but require other entities to be processed first.
In order to be able to insert this set of entities, the ORM must be able to perform inserts for a given table repeatedly, interleaved with writing other entities to their respective tables.
This is part of the series of issues fixed by #10547. In particular, the changes from #10566 were relevant.
See #10348 for the bug description.
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
This is the third step to break https://github.com/doctrine/orm/pull/10547 into smaller PRs suitable for reviewing. It uses the new topological sort implementation from #10592 and the refactoring from #10651 to compute the UoW's commit order for entity insertions not on the entity class level, but for single entities and their actual dependencies instead.
#### Current situation
`UnitOfWork::getCommitOrder()` would compute the entity sequence on the class level with the following code:
70477d81e9/lib/Doctrine/ORM/UnitOfWork.php (L1310-L1325)
#### Suggested change
* Instead of considering the classes of all entities that need to be inserted, updated or deleted, consider the new (inserted) entities only. We only need to find a sequence in situations where there are foreign key relationships between two _new_ entities.
* In the dependency graph, add edges for all to-one association target entities.
* Make edges "optional" when the association is nullable.
#### Test changes
I have not tried to fully understand the few changes necessary to fix the tests. My guess is that those are edge cases where the insert order changed and we need to consider this during clean-up.
Keep in mind that many of the functional tests we have assume that entities have IDs assigned in the order that they were added to the EntityManager. That does not change – so the order of entities is generally stable, equal to the previous implementation.
When computing the commit order for entity removals, we have to look out for `@ORM\JoinColumn(onDelete="SET NULL")` to find places where cyclic associations can be broken.
#### Background
The UoW computes a "commit order" to find the sequence in which tables shall be processed when inserting entities into the database or performing delete operations.
For the insert case, the ORM is able to schedule _extra updates_ that will be performed after all entities have been inserted. Associations which are configured as `@ORM\JoinColumn(nullable=true, ...)` can be left as `NULL` in the database when performing the initial `INSERT` statements, and will be updated once all new entities have been written to the database. This can be used to break cyclic associations between entity instances.
For removals, the ORM does not currently implement up-front `UPDATE` statements to `NULL` out associations before `DELETE` statements are executed. That means when associations form a cycle, users have to configure `@ORM\JoinColumn(onDelete="SET NULL", ...)` on one of the associations involved. This transfers responsibility to the DBMS to break the cycle at that place.
_But_, we still have to perform the delete statements in an order that makes this happen early enough. This may be a _different_ order than the one required for the insert case. We can find it _only_ by looking at the `onDelete` behaviour. We must ignore the `nullable` property, which is irrelevant, since we do not even try to `NULL` anything.
#### Example
Assume three entity classes `A`, `B`, `C`. There are unidirectional one-to-one associations `A -> B`, `B -> C`, `C -> A`. All those associations are `nullable= true`.
Three entities `$a`, `$b`, `$c` are created from these respective classes and associations are set up.
All operations `cascade` at the ORM level. So we can test what happens when we start the operations at the three individual entities, but in the end, they will always involve all three of them.
_Any_ insert order will work, so the improvements necessary to solve #10531 or #10532 are not needed here. Since all associations are between different tables, the current table-level computation is good enough.
For the removal case, only the `A -> B` association has `onDelete="SET NULL"`. So, the only possible execution order is `$b`, `$c`, `$a`. We have to find that regardless of where we start the cascade operation.
The DBMS will set the `A -> B` association on `$a` to `NULL` when we remove `$b`. We can then remove `$c` since it is no longer being referred to, then `$a`.
#### Related cases
These cases ask for the ORM to perform the extra update before the delete by itself, without DBMS-level support:
* #5665
* #10548
mappedBy is never defined on an inverse relationship.
Bi-directional self-referencing should IMO still result in 2 separate
associations, on 2 separate fields of the same class, like mentor or
mentee.
Maybe we do not know enough about the parameter to determine the type of
the returned relationship, but we can at least narrow it down to 3
possibilites.
These methods assert the type of the mapping provided by the collection
according to the name of the class they are in: the one to many
persister only ever deals with one to many associations, and the many to
many persister only ever deals with many to many associations.
Throughout the codebase, there is this pattern where we ensure we have
the owning side of an association.
It involves accessing it from the associationMappings array. In the end,
static analysis cannot know that the association is indeed owning.
By introducing this convenience method, we make this clear, and also
delegate the complexity to the class metadata factory.
Interfaces cannot have properties, and we do not have a concept of
sealed classes available to us without installing third party packages.
Interfaces can have methods however, which allows us to simplify calling
code.
I've been avoiding introducing getters for mapping properties because I
do not know what the performance implications are, but here, I think it
is sensible to make an exception, given the benefits.
This is the first chunk to break #10547 into smaller PRs suitable for reviewing. It adds a new topological sort implementation.
#### Background
Topological sort is an algorithm that sorts the vertices of a directed acyclic graph (DAG) in a linear order such that for every directed edge from vertex A to vertex B, vertex A comes before vertex B in the ordering. This ordering is called a topological order.
Ultimately (beyond the scope of this PR), in the ORM we'll need this to find an order in which we can insert new entities into the database. When one entity needs to refer to another one by means of a foreign key, the referred-to entity must be inserted before the referring entity. Deleting entities is similar.
A topological sorting can be obtained by running a depth first search (DFS) on the graph. The order in which the DFS finishes on the vertices is a topological order. The DFS is possible iif there are no cycles in the graph. When there are cycles, the DFS will find them.
For more information about topological sorting, as well as a description of an DFS-based topological sorting algorithm, see https://en.wikipedia.org/wiki/Topological_sorting.
#### Current situation
There is a DFS-based topological sorting implemented in the `CommitOrderCalculator`. This implementation has two kinks:
1. It does not detect cycles
When there is a cycle in the DAG that cannot be resolved, we need to know about it. Ultimately, this means we will not be able to insert entities into the database in any order that allows all foreign keys constraints to be satisfied.
If you look at `CommitOrderCalculator`, you'll see that there is no code dealing with this situation.
2. It has an obscure concept of edge "weights"
To me, it is not clear how those are supposed to work. The weights are related to whether a foreign key is nullable or not, but can (could) be arbitrary integers. An edge will be ignored if it has a higher (lower) weight than another, already processed edge... 🤷🏻?
#### Suggested change
In fact, when inserting entities into the database, we have two kinds of foreign key relationships: Those that are `nullable`, and those that are not.
Non-nullable foreign keys are hard requirements: Referred-to entities must be inserted first, no matter what. These are "non-optional" edges in the dependency graph.
Nullable foreign keys can be set to `NULL` when first inserting an entity, and then coming back and updating the foreign key value after the referred-to (related) entity has been inserted into the database. This is already implemented in `\Doctrine\ORM\UnitOfWork::scheduleExtraUpdate`, at the expense of performing one extra `UPDATE` query after all the `INSERT`s have been processed. These edges are "optional".
When finding a cycle that consists of non-optional edges only, treat it as a failure. We won't be able to insert entities with a circular dependency when all foreign keys are non-NULLable.
When a cycle contains at least one optional edge, we can use it to break the cycle: Use backtracking to go back to the point before traversing the last _optional_ edge. This omits the edge from the topological sort order, but will cost one extra UPDATE later on.
To make the transition easier, the new implementation is provided as a separate class, which is marked as `@internal`.
#### Outlook
Backtracking to the last optional edge is the simplest solution for now. In general, it might be better to find _another_ (optional) edge that would also break the cycle, if that edge is also part of _other_ cycles.
Remember, every optional edge skipped costs an extra UPDATE query later on. The underlying problem is known as the "minimum feedback arc set" problem, and is not easily/efficiently solvable. Thus, for the time being, picking the nearest optional edge seems to be reasonable.
This PR will make the annotations and attribute mapping drivers report mapping configuration for the classes where it is declared, instead of omitting it and reporting it for subclasses only. This is necessary to be able to catch mis-configurations in `ClassMetadataFactory`.
Fixes#10417, closes#10450, closes#10454.
#### ⚠️ Summary for users getting `MappingExceptions` with the new mode
When you set the `$reportFieldsWhereDeclared` constructor parameters to `true` for the AnnotationDriver and/or AttributesDriver and get `MappingExceptions`, you may be doing one of the following:
* Using `private` fields with the same name in different classes of an entity inheritance hierarchy (see #10450)
* Redeclaring/overwriting mapped properties inherited from mapped superclasses and/or other entities (see #10454)
As explained in these two PRs, the ORM cannot (or at least, was not designed to) support such configurations. Unfortunately, due to the old – now deprecated – driver behaviour, the misconfigurations could not be detected, and due to previously missing tests, this in turn was not noticed.
#### Current situation
The annotations mapping driver has the following condition to skip properties that are reported by the PHP reflection API:
69c7791ba2/lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php (L345-L357)
This code has been there basically unchanged since the initial 2.0 release. The same condition can be found in the attribute driver, probably it has been copied when attributes were added.
I _think_ what the driver tries to do here is to deal with the fact that Reflection will also report `public`/`protected` properties inherited from parent classes. This is supported by the observation (see #5744) that e. g. YAML and XML drivers do not contain this logic.
The conditions are not precise enough for edge cases. They lead to some fields and configuration values not even being reported by the driver.
Only since the fields would be "discovered" again when reflecting on subclasses, they eventually end up in class metadata structures for the subclasses. In one case of inherited ID generator mappings, the `ClassMetadataFactory` would also rely on this behaviour.
Two potential bugs that can result from this are demonstrated in #10450 and #10454.
#### Suggested solution
In order to find a more reliable way of separating properties that are merely reported again in subclasses from those that are actual re-declarations, use the information already available in `ClassMetadata`. In particular, `declared` tells us in which non-transient class a "field" was first seen.
Make the mapping driver skip only those properties for which we already know that they have been declared in parent classes, and skip them only when the observed declaring class matches the expectation.
For all other properties, report them to `ClassMetadataFactory` and let that deal with consistency checking/error handling.
#10450 and #10454 are merged into this PR to show that they pass now.
#### Soft deprecation strategy
To avoid throwing new/surprising exceptions (even for misconfigurations) during a minor version upgrade, the new driver mode is opt-in.
Users will have to set the `$reportFieldsWhereDeclared` constructor parameters to `true` for the `AnnotationDriver` and/or `AttributesDriver`. Unless they do so, a deprecation warning will be raised.
In 3.0, the "new" mode will become the default. The constructor parameter can be deprecated (as of ORM 3.1, probably) and is a no-op.
We need to follow up in other places (DoctrineBundle, ... – what else?) to make this driver parameter an easy-to-change configuration setting.
- Each type is now either final, abstract or an interface.
- The mappedBy attribute is no longer nullable and moved down the
hierarchy.
- The inversedBy attribute is still nullable and also moved down the
hierarchy.
- Code common to ManyToOneAssociationMapping and
OneToOneOwningSideMapping is de-duplicated and moved up the hierarchy
- Code inside ToManyInverseSideMapping and ToManyOwningSideMapping comes
from a trait to avoid duplication.
Instead of ensuring every mapping array has a mappedBy and an inversedBy
field, let us do the opposite, and remove them when they are null.
Likewise if there is a joinColumns field, it is useless if null or
empty.
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.
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
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`.
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.
This is the second chunk to break #10547 into smaller PRs suitable for reviewing. It prepares the `UnitOfWork` to work with a commit order computed on the entity level, as opposed to a class-based ordering as before.
#### Background
#10531 and #10532 show that it is not always possible to run `UnitOfWork::commit()` with a commit order given in terms of entity _classes_. Instead it is necessary to process entities in an order computed on the _object_ level. That may include
* a particular order for multiple entities of the _same_ class
* a particular order for entities of _different_ classes, possibly even going back and forth (entity `$a1` of class `A`, then `$b` of class `B`, then `$a2` of class `A` – revisiting that class).
This PR is about preparing the `UnitOfWork` so that its methods will be able to perform inserts and deletions on that level of granularity. Subsequent PRs will deal with implementing that particular order computation.
#### Suggested change
Change the private `executeInserts` and `executeDeletions` methods so that they do not take a `ClassMetadata` identifying the class of entities that shall be processed, but pass them the list of entities directly.
The lists of entities are built in two dedicated methods. That happens basically as previously, by iterating over `$this->entityInsertions` or `$this->entityDeletions` and filtering those by class.
#### Potential (BC breaking?) changes that need review scrutiny
* `\Doctrine\ORM\Persisters\Entity\EntityPersister::addInsert()` was previously called multiple times, before the insert would be performed by a call to `\Doctrine\ORM\Persisters\Entity\EntityPersister::executeInserts()`. With the changes here, this batching effectively no longer takes place. `executeInserts()` will always see one entity/insert only, and there may be multiple `executeInserts()` calls during a single `UoW::commit()` phase.
* The caching in `\Doctrine\ORM\Cache\Persister\Entity\AbstractEntityPersister` previously would cache entities from the last executed insert batch only. Now it will collect entities across multiple batches. I don't know if that poses a problem.
* Overhead in `\Doctrine\ORM\Persisters\Entity\BasicEntityPersister::executeInserts` is incurred multiple times; that may, however, only be about SQL statement preparation and might be salvageable.
* The `postPersist` event previously was not dispatched before _all_ instances of a particular entity class had been written to the database. Now, it will be dispatched immediately after every single entity that has been inserted.
During a recent refactoring, I had to pick a relationship type for this
piece of code, and I chose wrong, because a many-to-one cannot have a
mappedBy field.
Since PHPUnit 10, it is possible to display details when notices and
warnings happen, and to fail the test suite on notice.
failOnWarning is older than that.
These properties only make sense for the owning side of a many-to-many
relationship.
Moving them down allows us simplify the serialization code, because the
case when these properties are empty no longer happen.
This makes the array access implementation of FieldMapping useful only
to consumers, it is no longer useful internally, and should be
deprecated as of Doctrine 3.1.0
* Add test case for https://github.com/doctrine/orm/issues/7717
* Do not hide null equality checks in `SqlValueVisitor::walkComparison`
* Annotate `GH7717Parent::$children` type
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#10471Fixes#10334
In the past, it has been decided to use arrays for this out of
legitimate performance concerns. But PHP has evolved, and now, it is
more performant and memory efficient to use objects.
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.
This means that a class implementing a doctrine/persistence interface or
extending a class from that package can throw a specialized exception
that will still be caught by code in the parent package.
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
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.
* 2.15.x:
Skip test instead of commenting it out (#10560)
Add missing return statements to Command:configure methods
Fix a Markdown/RST formatting glitch
Add mapping configurations for classes that were used in tests as entities, but never declared
Allow to-many associations on mapped superclasses w/ ResolveTargetEntityListener
* 2.15.x:
Ignore the cache dir of PHPUnit 10 (#10546)
Make data providers static (#10545)
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
* 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
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.
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}.
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.
Allow to-many associations to be used on mapped superclasses when the owning (inverse) side does not refer back to the mapped superclass, thanks to `ResolveTargetEntityListener`.
#### Current situation
The [documentation states](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html):
> No database table will be created for a mapped superclass itself
> [...] persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all.
That's a though limitation.
~Obviously~ ~apparently~ Probably the limitation comes from the fact that in a to-many association the "many" side has to hold a foreign key. Since the mapped superclass does not have a database table (it's not an entity), no such backreference can be established.
Currently, to-many associations trigger an exception as soon as they are seen on a mapped superclass:
d6c0031d44/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php (L459-L461)
#### `ResolveTargetEntityListener`
The `ResolveTargetEntityListener` can be used to substitute interface or class names in mapping configuration at runtime, during the metadata load phase.
When this gimmick is used to replace _all_ references to the mapped superclass with an entity class in time, it should be possible to have to-many associations on the inheriting entity classes.
#### Suggested solution
Instead of rejecting to-many associations on mapped superclasses right away, validate that at the end of the day (after the `loadClassMetadata` event has been processed) no association may target at a non-entity class. That includes mapped superclasses as well as transient classes.
#### Motivating example
Consider a library that comes with a `User` base class. This class is `abstract` and has to be subclassed/filled when the library is used.
By making this a mapped superclass, library users have the freedom to either have a simple user entity class or a user class hierarchy, but we do not impose any requirements on them. (NB we also don't want to have a root entity in the library, because that would have to declare the entire class hierarchy, including library users' classes.)
The actual user class to be used will be configured through the `ResolveTargetEntityListener`.
The library also includes a `SocialMediaAccount` entity. A `User` can have multiple of these accounts, and we want to be able to navigate the accounts from the user side.
To make the example even more fancy, there is a self-referencing association on the `User`: A `User` has been created by another user, and holds a collection of all other `User`s it created.
The test case contained in this PR contains this example and validates that all association mappings look just as if the final user class had been written as an entity directly, without the superclass.
#### Potential review talking points
- Am I missing other reasons why to-many is not feasible?
- We now reject association mappings with `targetEntity`s that are not entities; relevant BC break? (IMHO: no.)
#### Review tip
Review commit by commit, not all files at once. The last commit adds a lot of entity declarations that were previously missed in tests and now raised exceptions; that's a lot of clutter in the PR.
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.
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.
* 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
"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.
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.
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
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.
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.
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.
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>
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
1753d03500/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:
4b3391725f/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.
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.
* 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
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.
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.
2138cc9383/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.
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.
* 2.15.x:
Use more precise types for class strings (#10381)
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)
* 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)
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?
These are the lowest hanging fruits I could find after running Rector: I
looked for files with a diff of 2 lines.
I did not include some changes that I find controversial, such as
marking some constants as final when we should maybe consider making
classes themselves final.
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.
* 2.15.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)
Allow doctrine/instantiator 2 (#10351)
* 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)
* 2.15.x:
Support of NOT expression from doctrine/collections ^2.1 (#10234)
Fix Psalm errors with Collection 2.1.2 (#10343)
Added warning about query cache in relation to parameters (#10276)
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.
Since doctrine/annotations 1.10, autoloading is used when everything
else has failed. Using registerFile() has been deprecated in favor of
that later on.
Previously, only a predefined set of automatic mappings was allowed, such as int, float, boolean, DateTime etc.
With this extension, it is possible to supply custom TypedFieldMapper implementation which takes as parameter the ReflectionProperty of a given field and decides the appropriate mapping.
A new configuration option was added to set and get the TypedFieldMapper.
The old logic was moved into a class DefaultTypedFieldMapper which is used by default when no mapper is supplied.
The selected TypedFieldMapper is passed into ClassMetadataInfo constructor. If empty, the DefaultTypedFieldMapper is used.
There is also ChainTypedFieldMapper class which allows chaining multiple TypedFieldMappers and apply them in a cascade (always if a field gets type assigned by the earlier mapper in the list, it will not be changed later).
This commit adds enumType option to DiscriminatorColumn as well as support for custom DBAL types which use PHP enums for PHP values.
Previously, the enumType option was completely missing, but also even using custom types that used PHP enums would end up in exception because ObjectHydrator would try to convert enums to string using (string) explicit conversion.
Apart from hydrators, ClassMetadataBuilder was extended to support specifying enumType.
Documentation was updated.
Previously, array of enums were incorrectly compared in UoW::computeChangeSet() resulting always in false positive, since the original data for comparison is fetched using ReflectionEnumProperty::getValue(), which returns the enum values, not enum objects.
This fix ensures comparing the individual enum array members' values.
Enum fields as ID have worked for some time, but referencing these fields from other entities in association mappings such as OneToOne, ManyToOne and ManyToMany resulted in fatal error as there was an attempt to convert enum value to string improperly.
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.
* Migrate AST namespace to PHP 8 syntax
* Use typed properties when type is non-object
We know the phpdoc types in that namespace are pretty messed up, but it
should be safe to assume that's only the case when the type is an object
type.
In 68bc00b6c6, while fixing coding style,
I introduced many, many use statements in that file. Using
half-qualified names sometimes, and unqualified names some other times
makes no sense.
If AST\ is used at least once, use it always.
It is not stable yet, but should be good enough, and this will help
taking care of #10118
Let us baseline all the new issues, just because they are new does not
mean they are more important than already-baselined errors. Besides, it
is more important to have higher standards for new code than to get an
increased baseline.
* 2.14.x:
Remove fragile assertions (#10239)
update help for RunDqlCommand (#10233)
Make the code easier to statically analyse
Widen parameter type
Document property as non-nullable
This exception is used in two places, one where $generatedMode is
clearly a string, and another where typing it as a string will cause a
type error, because $generatedMode is supposed to be an int there.
Inside the Query\AST namespace, many classes use public properties that
are supposed to be set from outside the class. Let us ignore
PropertyNotSetInConstructor for that entire namespace instead of doing
it on a case by case basis.
* 2.14.x:
Add a constructor to CacheKey (#10212)
Psalm 4.30.0, PHPStan 1.9.2 (#10213)
Allow "Expr\Func" as condition in join (#10202)
refactor: use list type in SchemaTool (#10199)
* Remove file used by annotation registry
* Remove meta-annotations
They should have been removed when the annotation driver was removed.
* Remove unneeded coding standard rule exclusion
* Remove annotation documentation of indexBy
* 2.14.x:
Deprecate the Annotation interface (#10178)
Bump CI to PHP 8.2 and latest database versions (#10180)
Remove reference to deprecated DriverChain from docs (#10179)
* 2.14.x:
PHPStan 1.8.11 (#10182)
Add isMemberOf and isInstanceOf to Expr helper list (#10104)
Migrate more references to annotations (#10176)
Fix grammer in working-with-objects (#10120)
Automap events in AttachEntityListenersListener (#10122)
Adapt use statements to the code (#10174)
I used the following config:
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;
return function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/tests',
]);
$rectorConfig->sets([
DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
]);
};
* 2.14.x:
Deprecate EntityManager::create() (#9961)
Address deprecation of SchemaDiff::toSaveSql()
Address deprecation of SchemaDiff::toSql()
Use error style for notifications
Fix calls to AbstractSchemaManager::createSchema() (#10165)
Fix build with DBAL 3.5 (#10163)
Adjust comments (#10160)
Deprecate methods related to the annotation driver
Use correct link
Deprecate annotations
Remove trailing whitespace
Migrate more documentation towards attributes
Remove wrong sentence
Do not export phpstan stuff (#10154)
stderr is not a great name for something that is not meant to be
processed (piped into) a program, but to be read by humans.
Most commands should use stderr, and some of them should partially use
stdout, typically when dumping SQL requests.
The previous rework missed some details / left the documentation in an
inconsistent state. Searching for "annotation" case insensitively allows
to find discrepancies and forgotten things.
* 2.14.x:
Modernize documentation code
Add CI jobs for SQLite3 driver (#10141)
Fix type doc blocks in annotation classes (#10145)
Fix FieldMapping for generated key (#10144)
Stop triggering static analysis workflows on tests
- migrate to attributes;
- add helpful phpdoc annotations;
- use typed properties;
- add type declarations.
Co-authored-by: Alexander M. Turek <me@derrabus.de>
The downside of this is that we will have to tweak the settings so that
no job is required anymore.
The upside is that builds should be faster, and less resource-intensive.
__sleep() does not list these properties as suposed to be serialized.
This means we have to assert that it is not in the many scenarios where
we resort to these properties. Introducing a private method allows us to
centralize the assertions.
* Add missing variable name in param phpdoc
* Use psalm-assert instead of psalm-param
The parameter can in fact be any string, but if an exception is not
thrown, then it was a valid value.
* 2.14.x:
PHPStan 1.8.5, Psalm 4.27.0 (#10033)
Fix EnumType not being hydrated with HYDRATE_ARRAY (#9995)
"Strange" return lines in documentation of inheritance-mapping.rst (#10027)
More strange break lines in inheritance-mapping.rst (#10028)
Add phpdoc for discriminatorColumn
* 2.14.x:
Bump Ubuntu version and shared workflows (#10020)
Make paginator covariant (#10002)
Fail gracefully if EM could not be constructed in tests (#10008)
* 2.14.x:
Bump coding standard to 9.0.2
Fix tests for doctrine/common 3.4
Fix static analysis errors for Collections 1.7
Fix type in docs (#9994)
Improve orphan removal documentation - recommend using cascade=persist (#9848)
* 2.14.x:
Backport type fixes for EntityListenerResolver (#9977)
Undeprecate LifecycleEventArgs (#9980)
Update documentation to not use deprecated method (#9979)
Deprecating this class means that users using SA tools have to
update their code specifying the ObjectManager implementation.
Instead of this, dedicated classes for each event should be
created before deprecating this class.
* 2.13.x:
Address DBAL 3.4 deprecations (#9969)
Improve phpdoc for ClassMetadataInfo (#9965)
Fix build (#9964)
fix: class normalisation test (#9966)
Support native enum hydration when using `NEW` operator (#9936)
* 2.13.x:
Deprecate QueryBuilder APIs exposing its internal state (#9945)
Update branch info in README and .doctrine-project.json (#9943)
Psalm 4.25.0, PHPStan 1.8.2 (#9941)
Stop passing event manager to constructor (#9938)
Use a more precise phpdoc for ClassMetadataInfo::versionField than mixed (#9937)
Make EntityManager `@final` and its constructor public
* 2.13.x:
Add helper function TreeWalkerAdapter::getMetadataForDqlAlias() (#9932)
Simplify LanguageRecognitionTest (#9930)
test/added test for foreign keys with custom id object types
Widen types for DiscriminatorMap (#9922)
schema tool: remove useless text from --dump-sql output
Update IdentifierFlattener.php
Update IdentifierFlattener.php
the description and semantics of the `--dump-sql` switch
indicate and sql dump. without that useless line
The following SQL statements will be executed:
the output can actually be used to generate a plain
dump.sql file that can be fed into sql command-
consuming programs.
resolves#8263 and #7186.
doctrine/dbal is the component responsible for generating the queries.
Let us make the test suite more robust by asserting that things work
from a functional point of view.
* 2.12.x:
PHPStan 1.8.0 (#9887)
Fix typo in AbstractQuery
ObjectHydrator: defer initialization of potentially empty collections
Migrate more usages of SchemaTool::createSchema()
preUpdate: Add restriction that changed field needs to be in computed changeset (#9871)
If ObjectHydrator faces an empty row to an uninitialized collection,
it initializes it, to prevent it from querying again (DDC-1526).
However, if that row is the first but not the only in the collection,
the next rows will be ignored, as the collection will be considered
"existing", and "existing" collections are only replaced if REFRESH hint
is present. To prevent it, we defer initialization to the end of the
hydration.
Fixes GH-9807
*FlushEventArgs classes should extend ManagerEventArgs from
doctrine/persistence to be able to use ManagerEventArgs for any
persistance implementation.
OnClearEventArgs should extend from OnClearEventArgs from
doctrine/persistence.
When I introduced OrmFunctionalTestCase::createSchemaForModels(), I made
several wrong assumptions:
- the call is always wrapped in a try / catch;
- that try / catch is always typed with "Exception".
Because of that, I missed, many, many occurrences of
SchemaTool::createSchema().
I recently noticed that contributors kept using the
SchemaTool::createSchema() and figured not everything had been
migrated.
Migrating some of them did not result in something far better until I
also introduced similar methods for
SchemaTool::getUpdateSchemaSql() and SchemaTool::getSchemaFromMetadata().
* 2.13.x:
Deprecate omitting the alias in QueryBuilder (#9765)
Run tests on PHP 8.2 (#9840)
PHPStan 1.7.13 (#9844)
Flip conditional extension of legacy AnnotationDriver class (#9843)
PHP CodeSniffer 3.7 (#9842)
Make Reflection available to ConvertMappingCommand (#9619)
Add missing property declaration
Use proper API for introspection of tables
This causes "Cannot assign string to property
Doctrine\Tests\Models\Cache\ComplexAction::$action2 of type
Doctrine\Tests\Models\Cache\Action" for some reason.
This causes the following error for some reason:
Typed property Doctrine\Tests\ORM\Functional\TrainOrder::$train must not
be accessed before initialization
* 2.13.x:
Add missing import (#9796)
Deprecate calling setters without arguments (#9791)
Move duplicate fixture into dedicated file (#9789)
MockTreeWalker should be an SqlWalker (#9790)
Hello, I would like to make a small change.
The need arose when using \Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator in "symfony/doctrine-bridge" composite foreign keys
I'm sure these changes will not hurt performance
and allow other objects to be used as identifiers
* 2.13.x:
Make phpdoc more precise
Deprecate setting fetch mode to random integers
Prepare split of output walkers and tree walkers (#9786)
PHPStan 1.7.0 (#9785)
Deprecate passing null to Query::setDQL()
Kill call_user_func(_array) (#9780)
Fix wrong types for AbstractQuery and child classes (#9774)
Document callable as possible
Remove override phpdoc tag
Add use statement (#9769)
* Remove comment about BC
I do not think we actually want to force our users to build an array
collection when they want to use setParameters().
* Make phpdoc more accurate
This allows to get rid of tearDown(), which contained a special handling
that is no longer necessary since we switched away from explicitely
managed sequences, and caused the test suite to fail.
These tests were using the fact that some arguments of some methods of
the naming strategy interface are optional or nullable for now to avoid
providing some. In practice, these arguments are always provided, and
that should also be the case in tests.
When computing a foreign key column name, the referenced column name
may be null in the case of a self referencing entity with join columns
defined in the mapping.
* Fix inaccurate and imprecise phpdoc
* Remove extra argument
The method signature does not specify it, and it's always set to null.
It looks like this test and its data provider were copy/pasted from the
previous one.
* Specify what concrete class is passed
This is important as the signature of the concrete classes don't
necessarily match the one of the interface. Here, an extra argument that
is only defined in the classes is used.
* Remove disconnected class metadata factory
It is unused apart from a tests where it is easily replaced.
* Remove ClassMetadataInfo
It has been deprecated for a long, long time.
Everything except the name key might be undefined when accessing to this public property, for instance in a LoadMetadataEvent
Co-authored-by: Pierre Bourdet <pbourdet@worldia.com>
ClassMetadataInfo used to be useful during entity generation, because it
allowed the entity not to exist. We no longer do entity generation, and
even if we did, the reflection methods have been moved to
ClassMetadataInfo as of 76e4f5a80b .
This documentation must be very old because this is no longer valid as
of e9e36dcf32 . The interface and abstract
file driver have since then been moved to doctrine/common, and the to
doctrine/persistence.
Although properties and methods are currently located in
ClassMetadataInfo, it is better to refer to ClassMetadata as the former
is deprecated in favor of the latter.
This was super annoying as UpdateCommand printed sqls prefixed with `*` so it was not possible to copy statements anymore without manually removing those asterisks.
This removes prefixing sqls and makes it consistent with Create and Drop commands.
* 2.12.x:
Indicate support for doctrine/persistence 3 (#9656)
Fix tests for enum ID hydration (#9658)
Revert "Use charset/collation from column or table default when creating relations (#9636)"
Fix test file/class names (#9649)
This reverts commit 03f4468be2.
The inferring process seems fragile and MySQL-specific. The ORM might
not be the correct place to fix this issue (if it needs to be fixed at
all).
* Fix composer install in contributing readme
People that contribute know how to use composer.
* Fix static analysis for Persistence 2.5 (#9648)
Co-authored-by: Ruud Kamphuis <ruudk@users.noreply.github.com>
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
In setups where you have many parameters, or do not even realise you are
using an entity, that additional piece of context can be helpful. The
parameter name is not always available where the old exception was
called though.
* Add support for array of enums
This allows the use of 'array' and 'simple_array' in combination
with the enumType parameter.
* Reference is_array and array_map through a use statement nstead of global fallback
* Return the value of an array of enums correctly
* Add enum array mapping test
* Fix order of use parameters
* Fix return type docblock
* Apply phpcs feedback
* Fix static closure
* Add missing return type to static closure
* Add helper method for enum initialization to reduce code duplication
* Fix CS
* Replace mixed typehints with more specific ones
* Update docblock type hint to allow for array of string/int
* Fix types
* Fix types
Co-authored-by: Alexander M. Turek <me@derrabus.de>
* 2.12.x:
Update psalm.xml
PHPStan 1.5.0 (#9607)
Remove Sphinx config
Indicate what feature is deprecated
Implement int-mask-of where appropriate
Use correct syntax for external links
Update XmlExporter.php - Type problem in php8.x (#9589)
Ignore deprecation from Persistence
Stands with Ukraine (#9567)
Use internal links when self-referencing
Link to docs for the stable version
* 2.11.x:
PHPStan 1.5.0 (#9607)
Remove Sphinx config
Use correct syntax for external links
Update XmlExporter.php - Type problem in php8.x (#9589)
Ignore deprecation from Persistence
Stands with Ukraine (#9567)
Use internal links when self-referencing
Link to docs for the stable version
I do not think this file is still useful, since AFAIK we are not using
Sphinx anymore. Besides, this is the only Doctrine project I could find
that still has that file. It was last updated 6 years ago.
Please see PHP interface SimpleXMLElement::addAttribute(string $name, string $value = null, string $namespace = null): void ....
The $value must be string or null.
This allows us to decouple further from doctrine/annotations, and to fix
some static analysis issues.
The assumption being made here is that the abstract class we are no
longer extending is not used in type declarations and instanceof checks.
* 2.12.x:
Leverage MemcachedAdapter::isSupported() (#9578)
Baseline Psalm errors caused by DBAL 3.3.3 (#9577)
Make sure MemcachedAdapter is supported before tring to use it (#9574)
Fixing `:doc:` link (#9569)
Adding PHP attributes (#9555)
Remove reference to removed class
* 2.11.x:
Baseline Psalm errors caused by DBAL 3.3.3 (#9577)
Make sure MemcachedAdapter is supported before tring to use it (#9574)
Fixing `:doc:` link (#9569)
Adding PHP attributes (#9555)
Remove reference to removed class
Wrong validation message is displayed when an incorrect bidirectional
bi-directional mapping is set up. When the owning side is configured
correctly and the target side is missing the back reference, the ORM
suggests adding inverseBy instead of mappedBy, with the field name
missing. This commit fixes this problem.
* 2.12.x:
Make creating test models more straightforward
Trigger the desired code path
Fix syntax typo in attributes reference (#9513)
Constructor-Argument "options" has the same type as the associated property. (#9501)
* 2.11.x:
Make creating test models more straightforward
Trigger the desired code path
Fix syntax typo in attributes reference (#9513)
Constructor-Argument "options" has the same type as the associated property. (#9501)
In https://github.com/doctrine/orm/pull/8962, I established that
swallowing exceptions while creating model was bad because it could hide
other helpful exceptions.
As it turns out however, swallowing exceptions is really the way to go
here, because of the performance implication of calling dropSchema(). It
is possible to catch a more precise exception as well, which should
preserve the benefits of not swallowing them.
It looks like I based my grep on the comment inside the catch and today,
I found many other occurrences of this pattern, without the easy to grep
comment.
I decided to fix them as well, but in a lazier way: one no longer has to
remember to call dropSchema in tearDown() now, it is automated.
* 2.12.x:
Deprecate methods removed in 3.0 (#9475)
Skip tests related to PersistentObject if that class is missing (#9472)
Run Postgres 14 and MariaDB 10.6 in CI (#9470)
* 2.12.x:
Check requirements for metadata drivers (#9459)
PDO is not a required extension (#9457)
Check requirements for metadata drivers (#9452)
Remove trailing underscore (#9446)
* 2.12.x:
Introduce DoctrineSetup as a replacement for Setup (#9443)
Introduce __unserialize behaviour in docs (#9390)
Adapt test logic to PHP and SQLite II (#9442)
Use the identify generator strategy
Added php 8.1 to CI
Psalm 4.19.0, PHPStan 1.4.3 (#9438)
Ignore PHPUnit result cache everywhere (#9425)
* 2.12.x:
Add support for PHP 8.1 enums in embedded classes (#9419)
Switch tests to the middleware logging system (#9418)
Added class-string typehint on $targetEntity (#9415)
Allow DiscriminatorColumn with length=0 (#9410)
Move UnderscoreNamingStrategyTest to correct namespace (#9414)
* 2.11.x:
Add support for PHP 8.1 enums in embedded classes (#9419)
Added class-string typehint on $targetEntity (#9415)
Allow DiscriminatorColumn with length=0 (#9410)
Move UnderscoreNamingStrategyTest to correct namespace (#9414)
* 2.12.x:
Deprecate MultiGetRegion (#9397)
Fix type on loadCacheEntry (#9398)
Update baselines for DBAL 3.3 (#9393)
Accessing private properties and methods from the same class is forbidden (#9311)
Expose enumType to DBAL to make native DB Enum possible (#9382)
* 2.12.x:
Allow using Enum from different namespace than Entity (#9384)
Corrected ORM version and added missing dependency (#9386)
PHPStan 1.4.0 (#9385)
[GH-9380] Bugfix: Delegate ReflectionEnumProperty::getAttributes(). (#9381)
Support enum cases as parameters (#9373)
Add detach as of list cascade-all operations (#9357)
* 2.11.x:
Use EntityManagerInterface in type declarations (#9325)
Add errors caused by the lexer update to the baselines (#9360)
Generated/Virtual Columns: Insertable / Updateable (#9118)
Remove the composer/package-versions-deprecated package
Relax assertion to include null as possible outcome (#9355)
* 2.11.x:
Leverage generic ObjectManagerDecorator (#9312)
Fix WhereInWalker description to better describe the behaviour of this class (#9268)
Regenerate Psalm baseline
Update Psalm baseline for Persistence 2.3 (#9349)
Support readonly properties for read operations (#9316)
* 2.11.x:
PHPStan 1.3.3, Psalm 4.18.1
Remove Psalm job for analyzing DBAL 2
Use the readonly annotation (#9340)
Add support for custom types with requireSQLConversion and ResultSetMappingBuilder::generateSelectClause()
PSR-6 second level cache
Fix type errors in AbstractQuery and QueryBuilder (#9275)
Mark columnName as always set
Add support for PHP 8.1 enums.
Remove ignore rules for issues fixed upstream (#9336)
[GH-9277] deprecate php driver (#9309)
* 2.11.x:
Added runtime deprecation to `UnitOfWork::commit()` and `clear()` (#9327)
Document return type of getEntityState() (#9328)
Fix broken type declaration (#9330)
Enable some previously disabled PHPCS rules (#9324)
* 2.11.x:
Run static analysis with language level PHP 8.1 (#9314)
Document LockMode enums (#9319)
Document PHPUnit mocks with intersection types (#9318)
Run PHP CodeSniffer on PHP 8.1 (#9317)
Psalm 4.17.0 (#9315)
Run static analysis on PHP 8.1 (#9310)
* 2.11.x:
Leverage get_debug_type() (#9297)
Fix return type (#9295)
Synchronize Psalm baseline (#9296)
Fix union type on QueryExpressionVisitorTest::testWalkComparison() (#9294)
Allow arithmetic expressions within IN operator (#9242)
Bump reusable workflows
* 2.11.x:
Enable UnusedUse sniff again (#9267)
Whitelist composer plugins used by this repository (#9286)
Fix XML export for `change-tracking-policy` (#9285)
Allow symfony/cache 6 (#9283)
Put actual value instead of index inside $originalEntityData. (#9244)
Fix return types of cache interfaces (#9271)
Better explain limitations of DQL "DELETE" (#9281)
Fix docblocks on nullable EM properties (#9273)
Docs: use canonical order for phpdoc tags, add missed semicolon (#9190)
Make PrimaryReadReplicaConnection enforcement explicit (#9239)
Regenerate Psalm baseline (#9272)
Improve compatibility with Doctrine DBAL 4 (#9266)
[docs] Fix wording for attributes=>parameters. (#9265)
Support for nesting attributes with PHP 8.1 (#9241)
Revert "Fix SchemaValidator with abstract child class in discriminator map (#9096)" (#9262)
Docs: consistency for FQCN, spacing, etc (#9232)
* 2.11.x:
Add missing deprecations for YAML metadata mapping (#9206)
Drop support for DBAL 3.1
Psalm 4.13.1, PHPStan 1.2.0 (#9204)
Add a psalm type for field mapping
Use `equal to` instead of `equal of` in `assertSqlGeneration()` (#9195)
Adding Attributes code block (#9161)
Currently to get count of all items there is need to provide empty array
to count() method as $criteria parameter is required. I believe there
shouldn't be a need to provide it if I want to count all Entities
without any criteria.
about: Have you encountered an issue during upgrade? 💣
---
<!--
Before reporting a BC break, please consult the upgrading document to make sure it's not an expected change: https://github.com/doctrine/orm/blob/2.9.x/UPGRADE.md
-->
### BC Break Report
<!-- Fill in the relevant information below to help triage your issue. -->
| Q | A
|------------ | ------
| BC Break | yes
| Version | x.y.z
#### Summary
<!-- Provide a summary describing the problem you are experiencing. -->
#### Previous behavior
<!-- What was the previous (working) behavior? -->
#### Current behavior
<!-- What is the current (broken) behavior? -->
#### How to reproduce
<!--
Provide steps to reproduce the BC break.
If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc.
Adding a failing Unit or Functional Test would help us a lot - you can submit it in a Pull Request separately, referencing this bug report.
..sectionauthor:: Roman Borschel (roman@code-factory.org)
As explained in the
`restrictions for entity classes in the manual <https://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
it is usually not allowed for an entity to implement ``__wakeup``
or ``__clone``, because Doctrine makes special use of them.
However, it is quite easy to make use of these methods in a safe
way by guarding the custom wakeup or clone code with an entity
identity check, as demonstrated in the following sections.
Safely implementing __wakeup
----------------------------
To safely implement ``__wakeup``, simply enclose your
implementation code in an identity check as follows:
..code-block::php
<?php
classMyEntity
{
private$id;// This is the identifier of the entity.
// ...
publicfunction__wakeup()
{
// If the entity has an identity, proceed as normal.
if($this->id){
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
// ...
}
Safely implementing __clone
---------------------------
Safely implementing ``__clone`` is pretty much the same:
..code-block::php
<?php
classMyEntity
{
private$id;// This is the identifier of the entity.
// ...
publicfunction__clone()
{
// If the entity has an identity, proceed as normal.
if($this->id){
// ... Your code here as normal ...
}
// otherwise do nothing, do NOT throw an exception!
}
// ...
}
Summary
-------
As you have seen, it is quite easy to safely make use of
``__wakeup`` and ``__clone`` in your entities without adding any
really Doctrine-specific or Doctrine-dependant code.
These implementations are possible and safe because when Doctrine
invokes these methods, the entities never have an identity (yet).
Furthermore, it is possibly a good idea to check for the identity
in your code anyway, since it's rarely the case that you want to
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
..code-block::php
<?php
@@ -154,8 +154,8 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
* This var contains the classname of the strategy
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine ORM)
*
* This is a doctrine field, so make sure that you use an @column annotation or setup your
* yaml or xml files correctly
* This is a doctrine field, so make sure that you use a
#[Column] attribute or setup your xml files correctly
* @var string
*/
protected$strategyClassName;
@@ -177,7 +177,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
publicfunctiongetStrategyClassName(){
return$this->strategyClassName;
}
/**
* Returns the instantiated strategy
*
@@ -186,7 +186,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
publicfunctiongetStrategyInstance(){
return$this->strategyInstance;
}
/**
* Sets the strategy this block / panel should work as. Make sure that you've used
* this method before persisting the block!
@@ -213,28 +213,29 @@ This might look like this:
..code-block::php
<?php
use\Doctrine\ORM,
\Doctrine\Common;
useDoctrine\Common\EventSubscriber;
useDoctrine\ORM\Event\LifecycleEventArgs;
useDoctrine\ORM\Events;
/**
* The BlockStrategyEventListener will initialize a strategy after the
but be aware that it is deprecated since PHP 8.1. We do not recommend its usage.
- PHP 7.4 introduces :doc:`the new magic method <https://php.net/manual/en/language.oop5.magic.php#object.unserialize>`
``__unserialize``, which changes the execution priority between
``__wakeup`` and itself when used. This can cause unexpected behaviour in
an Entity.
- An entity class can be final or read-only when
you use :ref:`native lazy objects <reference-native-lazy-objects>`.
It may contain final methods or read-only properties too.
- Any two entity classes in a class hierarchy that inherit
directly or indirectly from one another must not have a mapped
property with the same name. That is, if B inherits from A then B
must not have a mapped field with the same name as an already
mapped field that is inherited from A.
- An entity cannot make use of func_get_args() to implement variable parameters.
Generated proxies do not support this for performance reasons and your code might
actually fail to work when violating this restriction.
- Entity cannot access private/protected properties/methods of another entity of the same class or :doc:`do so safely <../cookbook/accessing-private-properties-of-the-same-class-from-different-instance>`.
Entities support inheritance, polymorphic associations, and
polymorphic queries. Both abstract and concrete classes can be
@@ -111,6 +100,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
~~~~~~~~~~~~~
@@ -157,17 +165,13 @@ Serializing entities
Serializing entities can be problematic and is not really
recommended, at least not as long as an entity instance still holds
references to proxy objects or is still managed by an
EntityManager. If you intend to serialize (and unserialize) entity
instances that still hold references to proxy objects you may run
into problems with private properties because of technical
limitations. Proxy objects implement ``__sleep`` and it is not
possible for ``__sleep`` to return names of private properties in
parent classes. On the other hand it is not a solution for proxy
objects to implement ``Serializable`` because Serializable does not
work well with any potential cyclic object references (at least we
did not find a way yet, if you did, please contact us). The
``Serializable`` interface is also deprecated beginning with PHP 8.1.
references to proxy objects or is still managed by an EntityManager.
By default, serializing proxy objects does not initialize them. On
unserialization, resulting objects are detached from the entity
manager and cannot be initialized anymore. You can implement the
``__serialize()`` method if you want to change that behavior, but
then you need to ensure that you won't generate large serialized
object graphs and take care of circular associations.
The EntityManager
~~~~~~~~~~~~~~~~~
@@ -201,5 +205,3 @@ typical implementation of the
to keep track of all the things that need to be done the next time
``flush`` is invoked. You usually do not directly interact with a
``UnitOfWork`` but with the ``EntityManager`` instead.
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
::
"symfony/yaml": "*"
Inside the ``Setup`` methods several assumptions are made:
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
- If ``$isDevMode`` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayAdapter``. Proxy objects are recreated on every request.
- If ``$isDevMode`` is false, check for Caches in the order APCu, Redis (127.0.0.1:6379), Memcache (127.0.0.1:11211) unless `$cache` is passed as fourth argument.
- If ``$isDevMode`` is false, set then proxy classes have to be explicitly created through the command line.
- If third argument `$proxyDir` is not set, use the systems temporary directory.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
..note::
In order to have ``ORMSetup`` configure the cache automatically, the library ``symfony/cache``
has to be installed as a dependency.
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration </reference/advanced-configuration>` section.
..note::
You can learn more about the database connection configuration in the
@@ -180,10 +180,10 @@ not need to lazy load the association with another query.
Doctrine allows you to walk all the associations between
all the objects in your domain model. Objects that were not already
loaded from the database are replaced with lazyload proxy
instances. Non-loaded Collections are also replaced by lazy-load
loaded from the database are replaced with lazy-loading proxy
instances. Non-loaded Collections are also replaced by lazy-loading
instances that fetch all the contained objects upon first access.
However relying on the lazy-load mechanism leads to many small
However relying on the lazy-loading mechanism leads to many small
queries executed against the database, which can significantly
affect the performance of your application. **Fetch Joins** are the
solution to hydrate most or all of the entities that you need in a
@@ -319,11 +319,11 @@ With Nested Conditions in WHERE Clause:
<?php
$query=$em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
$query->setParameters(array(
$query->setParameters([
'name'=>'Bob',
'name2'=>'Alice',
'id'=>321,
));
]);
$users=$query->getResult();// array of ForumUser objects
With COUNT DISTINCT:
@@ -464,6 +464,11 @@ hierarchies:
$query=$em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee');
$query=$em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1');
$query=$em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1');
Get all users visible on a given website that have chosen certain gender:
@@ -485,7 +490,7 @@ where you can generate an arbitrary join with the following syntax:
..code-block::php
<?php
$query=$em->createQuery('SELECT u FROM User u JOIN Banlist b WITH u.email = b.email');
$query=$em->createQuery('SELECT u FROM User u JOIN Banlist b ON u.email = b.email');
With an arbitrary join the result differs from the joins using a mapped property.
The result of an arbitrary join is an one dimensional array with a mix of the entity from the ``SELECT``
@@ -508,19 +513,21 @@ it loads all the related ``Banlist`` objects corresponding to this ``User``. Thi
when the DQL is switched to an arbitrary join.
..note::
The differences between WHERE, WITH and HAVING clauses may be
The differences between WHERE, WITH, ON and HAVING clauses may be
confusing.
- WHERE is applied to the results of an entire query
-WITH is applied to a join as an additional condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
the WITH is required, even if it is 1 = 1
-ON is applied to arbitrary joins as the join condition. For
arbitrary joins (SELECT f, b FROM Foo f, Bar b ON f.id = b.id)
the ON is required, even if it is 1 = 1. WITH is also
supported as alternative keyword for that case for BC reasons.
- WITH is applied to an association join as an additional condition.
- HAVING is applied to the results of a query after
aggregation (GROUP BY)
Partial Object Syntax
^^^^^^^^^^^^^^^^^^^^^
Partial Hydration Syntax
^^^^^^^^^^^^^^^^^^^^^^^^
By default when you run a DQL query in Doctrine and select only a
subset of the fields for a given entity, you do not receive objects
@@ -528,7 +535,7 @@ back. Instead, you receive only arrays as a flat rectangular result
set, similar to how you would if you were just using SQL directly
and joining some data.
If you want to select partial objects you can use the ``partial``
If you want to select partial objects or fields in array hydration you can use the ``partial``
DQL keyword:
..code-block::php
@@ -537,12 +544,13 @@ DQL keyword:
$query=$em->createQuery('SELECT partial u.{id, username} FROM CmsUser u');
$users=$query->getResult();// array of partially loaded CmsUser objects
You use the partial syntax when joining as well:
You can use the partial syntax when joining as well:
..code-block::php
<?php
$query=$em->createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a');
$usersArray=$query->getArrayResult();// array of partially loaded CmsUser and CmsArticle fields
$users=$query->getResult();// array of partially loaded CmsUser objects
"NEW" Operator Syntax
@@ -582,7 +590,101 @@ And then use the ``NEW`` DQL keyword :
$query=$em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c');
$users=$query->getResult();// array of CustomerDTO
Note that you can only pass scalar expressions to the constructor.
$query=$em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, NEW AddressDTO(a.street, a.city, a.zip)) FROM Customer c JOIN c.email e JOIN c.address a');
$users=$query->getResult();// array of CustomerDTO
Note that you can only pass scalar expressions or other Data Transfer Objects to the constructor.
If you use your data transfer objects for multiple queries, and you would rather not have to
specify arguments that precede the ones you are really interested in, you can use named arguments.
Consider the following DTO, which uses optional arguments:
..code-block::php
<?php
classCustomerDTO
{
publicfunction__construct(
publicstring|null$name=null,
publicstring|null$email=null,
publicstring|null$city=null,
publicmixed|null$value=null,
publicAddressDTO|null$address=null,
){
}
}
You can specify arbitrary arguments in an arbitrary order by using the named argument syntax, and the ORM will try to match argument names with the selected column names.
The syntax relies on the NAMED keyword, like so:
..code-block::php
<?php
$query=$em->createQuery('SELECT NEW NAMED CustomerDTO(a.city, c.name) FROM Customer c JOIN c.address a');
$users=$query->getResult();// array of CustomerDTO
To define a custom name for a DTO constructor argument, you can either alias the column with the ``AS`` keyword.
The ``NAMED`` keyword must precede all DTO you want to instantiate :
..code-block::php
<?php
$query=$em->createQuery('SELECT NEW NAMED CustomerDTO(c.name, NEW NAMED AddressDTO(a.street, a.city, a.zip) AS address) FROM Customer c JOIN c.address a');
$users=$query->getResult();// array of CustomerDTO
If two arguments have the same name, a ``DuplicateFieldException`` is thrown.
If a field cannot be matched with a property name, a ``NoMatchingPropertyException`` is thrown. This typically happens when using functions without aliasing them.
You can hydrate an entity nested in a DTO :
..code-block::php
<?php
$query=$em->createQuery('SELECT NEW CustomerDTO(c.name, a AS address) FROM Customer c JOIN c.address a');
$users=$query->getResult();// array of CustomerDTO
@@ -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 based on external inputs.
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
We try to make using Doctrine2 a very pleasant experience.
We try to make using Doctrine ORM a very pleasant experience.
Therefore we think it is very important to be honest about the
current limitations to our users. Much like every other piece of
software Doctrine2 is not perfect and far from feature complete.
software the ORM is not perfect and far from feature complete.
This section should give you an overview of current limitations of
Doctrine ORM as well as critical known issues that you should know
about.
@@ -65,15 +65,6 @@ Where the ``attribute_name`` column contains the key and
The feature request for persistence of primitive value arrays
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
Cascade Merge with Bi-directional Associations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
Make sure to study the behavior of cascade merge if you are using it:
-`DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
-`DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
Custom Persisters
~~~~~~~~~~~~~~~~~
@@ -130,10 +121,71 @@ included in the core of Doctrine ORM. However there are already two
extensions out there that offer support for Nested Set with
* query = "SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username"
<query>SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username</query>
query:SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username
sqlResultSetMappings:
mappingMultipleJoinsEntityResults:
name:mappingMultipleJoinsEntityResults
columnResult:
0:
name:numphones
entityResult:
0:
entityClass:__CLASS__
fieldResult:
0:
name:id
column:u_id
1:
name:name
column:u_name
2:
name:status
column:u_status
1:
entityClass:Address
fieldResult:
0:
name:id
column:a_id
1:
name:zip
column:a_zip
2:
name:country
column:a_country
Things to note:
- The resultset mapping declares the entities retrieved by this native query.
- Each field of the entity is bound to a SQL alias (or column name).
- All fields of the entity including the ones of subclasses
and the foreign key columns of related entities have to be present in the SQL query.
- Field definitions are optional provided that they map to the same
column name as the one declared on the class property.
- ``__CLASS__`` is an alias for the mapped class
In the above example,
the ``fetchJoinedAddress`` named query use the joinMapping result set mapping.
This mapping returns 2 entities, User and Address, each property is declared and associated to a column name,
actually the column name retrieved by the query.
Let's now see an implicit declaration of the property / column.
In this example, we only describe the entity member of the result set mapping.
The property / column mappings is done using the entity mapping values.
In this case the model property is bound to the model_txt column.
If the association to a related entity involve a composite primary key,
a @FieldResult element should be used for each foreign key column.
The @FieldResult name is composed of the property name for the relationship,
followed by a dot ("."), followed by the name or the field or property of the primary key.
..configuration-block::
..code-block::php
<?php
namespaceMyProject\Model;
/**
* @NamedNativeQueries({
* @NamedNativeQuery(
* name = "fetchJoinedAddress",
* resultSetMapping= "mappingJoinedAddress",
* query = "SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?"
<query>SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?</query>
query:SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?
sqlResultSetMappings:
mappingJoinedAddress:
entityResult:
0:
entityClass:__CLASS__
fieldResult:
0:
name:id
1:
name:name
2:
name:status
3:
name:address.id
column:a_id
4:
name:address.zip
column:a_zip
5:
name:address.city
column:a_city
6:
name:address.country
column:a_country
If you retrieve a single entity and if you use the default mapping,
you can use the resultClass attribute instead of resultSetMapping:
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.