Compare commits

...

168 Commits

Author SHA1 Message Date
Alexander M. Turek 9c56071392 Adjust PHPBench mocks 2024-03-21 12:37:52 +01:00
Alexander M. Turek 0a1988b349 Merge branch '2.19.x' into 3.1.x
* 2.19.x:
  Set column length explicitly (#11393)
  Add missing import
  Remove unused variable (#11391)
  [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)
2024-03-21 12:05:05 +01:00
Alexander M. Turek 1a5a4c674a Set column length explicitly (#11393) 2024-03-21 12:01:42 +01:00
Alexander M. Turek 95795c87a8 Add missing import 2024-03-21 10:38:59 +01:00
Alexander M. Turek db6e702088 Remove unused variable (#11391) 2024-03-21 10:32:55 +01:00
Grégoire Paris 4175edf311 Merge pull request #11387 from valkars/enum-reflection
Fixed proxy initialization for EnumReflectionProperty
2024-03-21 10:20:42 +01:00
Valentin Karnauhov 67ac5a82da Fixed proxy initialization for EnumReflectionProperty 2024-03-21 10:54:26 +02:00
Claudio Zizza e384978e0b Remove older versions from the docs (#11383)
To reduce Algolia operations and indexes older versions get removed
2024-03-20 23:35:25 +01:00
Thomas Landauer 5ccbc201bf [Documentation] Removing "Doctrine Mapping Types" ... (#11384)
... in favor of https://www.doctrine-project.org/projects/doctrine-dbal/en/3.8/reference/types.html#reference

Page: https://www.doctrine-project.org/projects/doctrine-orm/en/2.19/reference/basic-mapping.html#doctrine-mapping-types

As announced in https://github.com/doctrine/dbal/pull/6336#issuecomment-2003720361 , the goal is to remove this duplicated type information from ORM and replace it with a link to DBAL.

In https://github.com/doctrine/dbal/pull/6341 , I'm adding any detail which I'm deleting here to the DBAL.
2024-03-20 23:34:10 +01:00
Benjamin Eberlei d15624f72f [GH-11185] Bugfix: do not use collection batch loading for indexBy assocations. (#11380) 2024-03-20 15:45:47 +01:00
Benjamin Eberlei 9d1a4973ae Improve lazy ghost performance by avoiding self-referencing closure. (#11376)
* Improve lazy ghost performance by avoiding self-referencing closure.

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>

* update baselien

---------

Co-authored-by: Nicolas Grekas <nicolas.grekas@gmail.com>
2024-03-19 09:19:25 +01:00
Grégoire Paris 55c4845d57 Merge pull request #11379 from greg0ire/3.1.x
Merge 2.19.x up into 3.1.x
2024-03-18 21:07:12 +01:00
Grégoire Paris a38f473a92 Merge remote-tracking branch 'origin/2.19.x' into 3.1.x 2024-03-18 20:57:55 +01:00
Grégoire Paris 40a0964f06 Merge pull request #11289 from themasch/reproduce-issue-11154-composite-key-eager-fetch-one
Do not use batch loading for collections with composite identifier
2024-03-18 20:12:56 +01:00
Grégoire Paris 08a9e60ed0 Remove outdated git metadata files (#11362)
Some of it seems related to the previous documentation build system,
some of it seems related to IntelliJ.
2024-03-17 23:06:30 +01:00
Benjamin Eberlei 3e3c023c95 Switch join columns around, otherwise index doesnt match 2024-03-17 19:50:56 +01:00
Benjamin Eberlei 5e6d5c06a9 Key on fk 2024-03-17 19:43:26 +01:00
Benjamin Eberlei 1622b7877d Fix entities and mapping. 2024-03-17 18:02:11 +01:00
Benjamin Eberlei 80aae2796d Merge pull request #11373 from kaznovac/patch-3
Minor code style fix in AbstractRemoteControl
2024-03-17 17:20:01 +01:00
Marko Kaznovac 528ef40fc4 Minor code style fix in AbstractRemoteControl 2024-03-17 15:55:54 +01:00
Grégoire Paris 7178b9d6b7 Merge pull request #11370 from greg0ire/forgotten-array-access
Avoid another occurrence of ArrayAccess
2024-03-17 10:42:16 +01:00
Grégoire Paris 8a14eee67a Avoid another occurrence of ArrayAccess 2024-03-17 09:38:54 +01:00
Grégoire Paris c5315f86fb Merge pull request #11368 from greg0ire/address-deprecation
Avoid array access
2024-03-17 09:02:38 +01:00
Grégoire Paris 5820bb8f49 Avoid array access
It is deprecated.
2024-03-16 23:37:00 +01:00
Benjamin Eberlei 820a0da4c1 Do not schedule batch loading for target classes with composite identifier. 2024-03-16 23:05:28 +01:00
Benjamin Eberlei fcd02b1ee2 Cleanup tests not to use model sets. 2024-03-16 23:04:57 +01:00
Grégoire Paris b0d07ffaba Merge pull request #11363 from greg0ire/3.1.x
Merge 2.19.x up into 3.1.x
2024-03-16 21:48:21 +01:00
Grégoire Paris 196d3a6996 Merge remote-tracking branch 'origin/2.19.x' into 3.1.x 2024-03-16 21:38:16 +01:00
Grégoire Paris abcad6fa45 Merge pull request #11090 from dbannik/2.17.x-failed-getting-entity-with-fetch-eager
[2.17.x] Failed getting entity with fetch eager property
2024-03-16 21:23:13 +01:00
Benjamin Eberlei 1b6cf58a1a Rename tables to avoid pg related illegal table name 2024-03-16 21:08:30 +01:00
Benjamin Eberlei 6501890ab5 Static analysis enforces the extra isset() even though that just masks no sense. 2024-03-16 20:48:15 +01:00
Benjamin Eberlei e399d21fb3 Simplify condition, improve comment on this edge case. 2024-03-16 20:41:24 +01:00
Benjamin Eberlei 16f355f0cc Remove tests for already working case as they add no value other than exploration, and we only need the regression test. 2024-03-16 20:31:09 +01:00
Grégoire Paris 94d45a036f Merge pull request #11347 from greg0ire/remove-orphan
Remove guides-specific markup
2024-03-11 21:08:16 +01:00
Grégoire Paris 9acca2252f Remove guides-specific markup
doctrine/rst-parser does not appear to support orphan metadata yet, and
renders it verbatim on the website.

Let's move this to the CI job.
2024-03-11 20:31:22 +01:00
Alexander M. Turek 716fc97b70 Merge branch '2.19.x' into 3.1.x
* 2.19.x:
  Prepare releases 2.19 and 3.1 (#11335)
2024-03-03 18:45:20 +01:00
Alexander M. Turek a809a71aa6 Prepare releases 2.19 and 3.1 (#11335) 2024-03-03 18:43:41 +01:00
Alexander M. Turek 4617a5e310 Merge branch '2.19.x' into 3.1.x
* 2.19.x:
2024-03-03 18:03:42 +01:00
Alexander M. Turek e77c5a3a5e Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  Fix annotation
  Bump CI workflows (#11336)
  Fix SchemaTool::getSchemaFromMetadata() uniqueConstraint without a predefined name (#11314)
2024-03-03 18:03:17 +01:00
Alexander M. Turek c3cc0fdd8c Merge branch '2.18.x' into 3.0.x
* 2.18.x:
  Fix annotation
  Bump CI workflows (#11336)
  Fix SchemaTool::getSchemaFromMetadata() uniqueConstraint without a predefined name (#11314)
2024-03-03 17:13:16 +01:00
Alexander M. Turek bd4449c462 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Fix annotation
2024-03-03 16:49:22 +01:00
Alexander M. Turek e3e96745cc Fix annotation 2024-03-03 16:49:00 +01:00
Alexander M. Turek 12e0cefba1 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Bump CI workflows (#11336)
  Fix SchemaTool::getSchemaFromMetadata() uniqueConstraint without a predefined name (#11314)
2024-03-03 16:46:50 +01:00
Alexander M. Turek 21221f73cc Bump CI workflows (#11336) 2024-03-03 16:46:12 +01:00
Rok Motaln ab5e9e393b Fix SchemaTool::getSchemaFromMetadata() uniqueConstraint without a predefined name (#11314)
* 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>
2024-03-03 16:02:48 +01:00
Alexander M. Turek 507c73c073 Merge branch '2.19.x' into 3.1.x
* 2.19.x:
  Refator array_map into simple loop for performance. (#11332)
2024-03-03 14:21:12 +01:00
Grégoire Paris ba0ea8953b Use class from persistence package (#11330)
* 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>
2024-03-03 13:08:37 +01:00
Benjamin Eberlei e62571c8f4 Refator array_map into simple loop for performance. (#11332) 2024-03-02 23:11:11 +01:00
Alexander M. Turek 53763d432b Merge branch '2.19.x' into 3.1.x
* 2.19.x:
2024-03-01 10:57:47 +01:00
Alexander M. Turek 154920a0b3 Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  Psalm 5.22.2 (#11326)
2024-03-01 10:56:28 +01:00
Alexander M. Turek b8d0a85017 Merge branch '2.18.x' into 3.0.x
* 2.18.x:
  Psalm 5.22.2 (#11326)
2024-03-01 10:51:50 +01:00
Alexander M. Turek 98f9de2af6 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Psalm 5.22.2 (#11326)
2024-03-01 10:48:12 +01:00
Alexander M. Turek 52a6a21387 Psalm 5.22.2 (#11326) 2024-03-01 10:47:18 +01:00
Alexander M. Turek cb497826be Bump Doctrine Collections to 2.2 (#11325) 2024-03-01 09:27:30 +01:00
Alexander M. Turek ba0d3842a9 Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  Use enum_exists() for enums
2024-03-01 08:56:20 +01:00
Alexander M. Turek bf49055a1f Use enum_exists() for enums 2024-03-01 08:56:07 +01:00
Alexander M. Turek 29e1935c65 Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  Remove PHP 7 workarounds (#11324)
2024-03-01 08:51:50 +01:00
Alexander M. Turek 694413a888 Remove PHP 7 workarounds (#11324) 2024-03-01 08:51:21 +01:00
Alexander M. Turek 33e02b2796 Merge branch '2.19.x' into 3.1.x
* 2.19.x:
2024-02-29 17:17:59 +01:00
Alexander M. Turek 26f7588479 Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  PHPStan 1.10.59 (#11320)
  Address deprecations from Collection 2.2 (#11315)
  Fix sql walker phpdoc
2024-02-29 17:17:42 +01:00
Alexander M. Turek 20a6efdff6 Merge branch '2.18.x' into 3.0.x
* 2.18.x:
  PHPStan 1.10.59 (#11320)
2024-02-29 16:52:42 +01:00
Alexander M. Turek 83c81f6c41 Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  PHPStan 1.10.59 (#11320)
2024-02-29 16:48:49 +01:00
Alexander M. Turek 4fc8629414 PHPStan 1.10.59 (#11320) 2024-02-29 16:47:35 +01:00
Grégoire Paris 791667a9e4 Merge pull request #11317 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-28 23:05:01 +01:00
Grégoire Paris 95da667862 Merge remote-tracking branch 'origin/2.18.x' into 3.0.x 2024-02-28 22:57:35 +01:00
Alexander M. Turek feb27f00c1 Address deprecations from Collection 2.2 (#11315) 2024-02-27 17:37:52 +01:00
Grégoire Paris c02ddd692f Merge pull request #11312 from greg0ire/3.1.x
Merge 3.0.x up into 3.1.x
2024-02-26 20:53:44 +01:00
Grégoire Paris 151a3fba9d Merge remote-tracking branch 'origin/3.0.x' into 3.1.x 2024-02-26 20:39:59 +01:00
Grégoire Paris b187bc8588 Merge pull request #11308 from greg0ire/throw-instead-of-assert
Throw a full-fledged exception on invalid call
2024-02-26 20:38:49 +01:00
Grégoire Paris 1e056842fe Merge pull request #11310 from greg0ire/3.1.x
Merge 2.19.x up into 3.1.x
2024-02-26 20:38:36 +01:00
Grégoire Paris ebb0c67ecc Merge remote-tracking branch 'origin/2.19.x' into 3.1.x 2024-02-26 08:48:34 +01:00
Grégoire Paris abd9186d00 Merge pull request #11309 from greg0ire/deprecate-invalid-call
Deprecate invalid method call
2024-02-26 08:45:50 +01:00
Grégoire Paris 719d007a81 Merge pull request #11298 from VincentLanglet/sqlWalkerPhpdoc
Fix sqlWalker::walkSimpleArithmeticExpression phpdoc
2024-02-26 08:21:47 +01:00
Grégoire Paris 08d3f72755 Deprecate invalid method call
`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
2024-02-25 22:09:47 +01:00
Grégoire Paris 3f7a3333ad Throw a full-fledged exception on invalid call
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
2024-02-25 21:49:03 +01:00
Grégoire Paris 2a8802af12 Merge pull request #11305 from doctrine/typo
Remove extra word
2024-02-25 13:20:55 +01:00
Grégoire Paris 9cc11d2541 Remove extra word 2024-02-25 11:20:44 +01:00
Grégoire Paris ee5b2ce5b0 Merge pull request #11294 from greg0ire/sa-fqcn
Translate comment into code and annotations
2024-02-25 10:01:27 +01:00
Grégoire Paris d54c9678d0 Deprecate passing null to ClassMetadata::fullyQualifiedClassName()
It can easily be avoided by the only caller.
2024-02-25 09:31:02 +01:00
Grégoire Paris 859e6af972 Translate comment into code and annotations
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.
2024-02-24 22:13:13 +01:00
Grégoire Paris 8c3c9f115d Merge pull request #11303 from doctrine/3.0.x
Merge 3.0.x up into 3.1.x
2024-02-24 21:21:41 +01:00
Grégoire Paris 3907872046 Merge pull request #11302 from greg0ire/3.0.x
Merge 2.18.x up into 3.0.x
2024-02-24 21:03:34 +01:00
Grégoire Paris 779781173a Merge pull request #11301 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-24 20:51:45 +01:00
Grégoire Paris 54cd70002c Merge remote-tracking branch 'origin/2.18.x' into 3.0.x 2024-02-24 20:47:36 +01:00
Grégoire Paris 76c4539ffa Merge pull request #11293 from greg0ire/wrong-type
Remove wrong annotation about return type
2024-02-24 13:05:08 +01:00
Vincent Langlet 0f8d193512 Fix sql walker phpdoc 2024-02-23 15:11:15 +01:00
Grégoire Paris cc314d0fb7 Remove wrong annotation about return type
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.
2024-02-22 23:14:52 +01:00
Alexander M. Turek 2df4d75565 Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  Test different ways of settings query parameters
  Be less restrictive in DiscriminatorColumnMapping phpdoc (#11226)
  Allow (Array)ParameterType in QueryBuilder
2024-02-22 13:26:11 +01:00
Alexander M. Turek dc21ab63ac Merge branch '2.19.x' into 3.1.x
* 2.19.x:
  Backport QueryParameterTest (#11288)
2024-02-22 13:25:30 +01:00
Alexander M. Turek 2a250b5814 Merge branch '2.18.x' into 3.0.x
* 2.18.x:
  Backport QueryParameterTest (#11288)
2024-02-22 13:23:53 +01:00
Alexander M. Turek c9c493b2fe Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Backport QueryParameterTest (#11288)
2024-02-22 13:23:21 +01:00
Alexander M. Turek e6eef1a97d Backport QueryParameterTest (#11288) 2024-02-22 13:22:44 +01:00
Mark Schmale 8d4718f875 provides a test case for github issue 11154
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.
2024-02-22 10:58:50 +01:00
Alexander M. Turek 44fa5d340a Merge pull request #11287 from derrabus/bugfix/parameter-types
Allow (Array)ParameterType in QueryBuilder
2024-02-22 09:31:34 +01:00
Alexander M. Turek 708146bbbc Test different ways of settings query parameters 2024-02-22 09:19:39 +01:00
Vincent Langlet a5bf9bb96a Be less restrictive in DiscriminatorColumnMapping phpdoc (#11226)
* Be less restrictive in params

* Allow null options

* Simplify expression

* Fix ci

* Add support for null
2024-02-22 09:12:39 +01:00
Hanish Singla 3eace16e85 Allow (Array)ParameterType in QueryBuilder 2024-02-22 00:01:05 +01:00
Grégoire Paris e4c27092cd Merge pull request #11276 from greg0ire/no-cast-filelock
Remove implicit casts in FileLock.php
2024-02-21 22:59:47 +01:00
Grégoire Paris adadf1fb90 Do not implicitly cast glob's return type
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.
2024-02-21 22:36:01 +01:00
Grégoire Paris 380b5b62ef Do not cast file_put_contents's return type
If $lock->value was an empty string, this would fix a bug, but it never
is, it is a uniqid-generated string.
2024-02-21 22:35:38 +01:00
Grégoire Paris a0e7a59572 Do not implicitly cast getLockTime()'s return type
This fixes a bug for files last modified on 1970-01-01 00:00:00, so… not
worth backporting IMO.
2024-02-21 22:35:38 +01:00
Grégoire Paris fb6c0c1d8b Do not implicitly cast getLockContent()'s return value
Lock files are supposed to contain uniqid()-generated values, so they
cannot be falsy strings, but if they did, this would fix a bug.
2024-02-21 22:35:36 +01:00
Alexander M. Turek fcf1116e33 Merge branch '3.0.x' into 3.1.x
* 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)
2024-02-21 19:28:15 +01:00
Alexander M. Turek 78dc63df27 Merge branch '2.19.x' into 3.1.x
* 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)
2024-02-21 19:28:06 +01:00
Alexander M. Turek bc5efd4bfe Merge branch '2.18.x' into 3.0.x
* 2.18.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
2024-02-21 18:54:21 +01:00
Alexander M. Turek c0dfba2ef3 Merge branch '2.18.x' into 2.19.x
* 2.18.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
2024-02-21 18:52:54 +01:00
Karoly Gossler 0efac09141 Fix Static Analysis folder reference (#11281) 2024-02-21 18:51:21 +01:00
Ondřej Mirtes b6b4cbcb93 Remove broken assertion from DateAddFunction and DateSubFunction (#11243)
* Remove wrong asserts in DATE_ADD and DATE_SUB query AST function handlers

* Require DBAL 3.8.2
2024-02-20 20:29:56 +01:00
Grégoire Paris b1f553eba3 Merge pull request #11272 from greg0ire/sa-attach-entity-listener
Improve static analysis on AttachEntityListenersListener
2024-02-20 08:14:13 +01:00
Grégoire Paris 0c4aac5a35 Merge pull request #11275 from greg0ire/sa-inversed-by
Account for inversedBy being a non-falsy-string or null
2024-02-20 08:13:34 +01:00
Grégoire Paris e0081b59be Account for inversedBy being a non-falsy-string or null
It is supposed to hold the name of a PHP property, and those cannot be
falsy strings.
2024-02-20 07:54:19 +01:00
Grégoire Paris 4bd574daee Improve static analysis on AttachEntityListenersListener
$listenerCallback is supposed to be a method name, so it is safe to
require it is not a falsy string.
2024-02-19 09:36:41 +01:00
Grégoire Paris efb6cebd41 Merge pull request #11270 from greg0ire/fix-trim-bug
Treat '0' as a legitimate trim char
2024-02-18 23:53:25 +01:00
Grégoire Paris e4769d3191 docs: recommend safer way to disable logging (#11269)
* 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.
2024-02-18 15:51:05 +01:00
Grégoire Paris cf408ad9ae Remove unused baseline entries 2024-02-18 12:26:18 +01:00
Grégoire Paris 7c29078051 Treat '0' as a legitimate trim char
Because of a loose comparison, it was not.
2024-02-18 11:34:10 +01:00
Grégoire Paris d5ba106803 Merge pull request #11268 from greg0ire/3.0.x
Merge 2.18.x up into 3.0.x
2024-02-17 21:37:33 +01:00
Grégoire Paris b59189ab48 Merge pull request #11267 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-17 20:00:05 +01:00
Grégoire Paris f9a4adc8ab Merge remote-tracking branch 'origin/2.18.x' into 3.0.x 2024-02-17 19:59:26 +01:00
Grégoire Paris 401a0c4fe9 Merge pull request #11266 from greg0ire/more-valid-docs
More valid docs
2024-02-17 19:57:34 +01:00
Grégoire Paris dba9d72b2d Add type field mapper documentation to the sidebar 2024-02-17 15:10:28 +01:00
Grégoire Paris fe0647053a Mark document as orphan
It is here for backward compatibilty reasons.
2024-02-17 15:06:46 +01:00
Grégoire Paris 7b3db4a037 Use correction sectionauthor syntax 2024-02-17 14:59:24 +01:00
Grégoire Paris 6672aaf165 Merge pull request #11265 from greg0ire/remove-unneeded-verify-depr
Remove unused trait
2024-02-17 14:55:10 +01:00
Grégoire Paris aa3b331cae Remove unused trait 2024-02-17 11:14:47 +01:00
Dmitry Bannik e5e3166747 #11090 - Fix obtaining an identifier in cases where the hydration has not yet fully completed on eagerLoadCollections 2024-02-16 12:57:23 +03:00
Thomas Landauer 3918dcfb42 [Documentation] Adding link to Postgres upgrade article (#11257)
* [Documentation] Adding link to Postgres upgrade article

* Update UPGRADE.md

* Update UPGRADE.md
2024-02-15 22:12:57 +01:00
Alexander M. Turek 6290747bf9 Validate more variadic parameters (#11261) 2024-02-14 00:33:12 +01:00
Alexander M. Turek b6f4220493 Throw if a variadic parameter contains unexpected named arguments (#11260) 2024-02-13 18:28:17 +01:00
Grégoire Paris bfb033fe3c Merge pull request #11256 from greg0ire/3.0.x
Merge 2.18.x up into 3.0.x
2024-02-13 12:07:29 +01:00
Grégoire Paris afbf293c94 Merge pull request #11255 from doctrine/2.18.x
Merge 2.18.x up into 2.19.x
2024-02-13 12:07:19 +01:00
Grégoire Paris bf86155dc2 Merge remote-tracking branch 'origin/2.18.x' into 3.0.x 2024-02-13 11:29:26 +01:00
Grégoire Paris 1d218bae30 Make docs valid according to guides 0.3.3 (#11252) 2024-02-12 23:46:09 +01:00
Simon Podlipsky 9acc70d5b8 fix: support array-type arg in QB variadic calls (#11242) 2024-02-09 15:23:22 +01:00
Alexander M. Turek b7860c782b Merge branch '2.19.x' into 3.1.x
* 2.19.x:
  Remove references to deprecated constants from Lexer (#11234)
2024-02-07 15:43:24 +01:00
Alexander M. Turek 7baef1e120 Remove references to deprecated constants from Lexer (#11234) 2024-02-07 15:39:20 +01:00
Alexander M. Turek 9a24ce5fad Merge branch '2.19.x' into 3.1.x
* 2.19.x:
  Add TokenType class (#11228)
2024-02-07 14:21:22 +01:00
Alexander M. Turek 9fcb8f1305 Merge branch '3.0.x' into 3.1.x
* 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
2024-02-07 13:48:24 +01:00
Alexander M. Turek 5a40b99e11 Merge branch '2.18.x' into 3.0.x
* 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
2024-02-07 13:44:56 +01:00
Karoly Gossler 5049b615c5 Add TokenType class (#11228)
* 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>
2024-02-07 13:31:08 +01:00
Alexander M. Turek 94144e1227 Revert "Merge pull request #11229 from greg0ire/add-columns"
This reverts commit 599dd58fe1, reversing
changes made to 1854ce2d32.
2024-02-07 09:36:32 +01:00
Grégoire Paris 599dd58fe1 Merge pull request #11229 from greg0ire/add-columns
Add columns for 3.1.x and 4.0x
2024-02-07 07:56:20 +01:00
Grégoire Paris aff543a4ff Add columns for 3.1.x and 4.0x 2024-02-06 23:12:13 +01:00
Andrey Bolonin 1854ce2d32 Update version ORM from 2 to 3 in docs (#11221) 2024-02-06 07:09:30 +01:00
Benjamin Morel b00f0c258e Clean up outdated sentence (#11224)
The static create() method is gone in version 3
2024-02-05 22:58:19 +01:00
Grégoire Paris 13a79b068c Merge pull request #11222 from andreybolonin/patch-3
Update README.md
2024-02-05 08:36:56 +01:00
Andrey Bolonin 27c9e9cab3 Update README.md 2024-02-05 09:31:22 +03:00
Alexander M. Turek 1051817d92 Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  Bump dependencies in the "getting started" docs page (#11219)
  DoctrineSetup was renamed to ORMSetup (#11218)
2024-02-04 17:45:24 +01:00
Grégoire Paris 40fbbf4429 Point link to correct upgrade guide (#11220) 2024-02-04 17:41:45 +01:00
Alexander M. Turek 00ed2ca991 Bump dependencies in the "getting started" docs page (#11219) 2024-02-04 17:40:24 +01:00
Alexander M. Turek 54b7ad2073 DoctrineSetup was renamed to ORMSetup (#11218) 2024-02-04 17:40:04 +01:00
Alexander M. Turek 517d038e5b Merge branch '3.0.x' into 3.1.x
* 3.0.x:
  Switch back to stable dependencies (#11210)
  Update branch metadata
2024-02-04 11:49:25 +01:00
Grégoire Paris 3db79ebbf3 Merge pull request #11214 from greg0ire/followup-array-access
Replace more occurrences of array access
2024-02-04 00:16:01 +01:00
Michael Skvortsov 6f98147d09 Ignore subclasses without discriminatorValue when generating discriminator column condition SQL (#11200)
After commit https://github.com/doctrine/orm/commit/4e8e3ef30b3d214640883aec5a17896afc006116 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>
2024-02-04 00:11:40 +01:00
Grégoire Paris a2faeb9a26 Replace more occurrences of array access
Not sure how I missed those.
2024-02-03 23:56:59 +01:00
Grégoire Paris 3764ebf7a3 Merge pull request #11212 from greg0ire/fix-grammar
Follow up on array access deprecation
2024-02-03 23:50:27 +01:00
Grégoire Paris a7d5adb3ce Migrate more occurrences of array access 2024-02-03 23:07:27 +01:00
Grégoire Paris 6f507c322a Fix grammar issue in upgrade guide 2024-02-03 23:01:56 +01:00
Grégoire Paris 54013671a7 Merge pull request #11211 from greg0ire/deprecate-array-access
Deprecate array access
2024-02-03 22:50:38 +01:00
Grégoire Paris f5dea25b6c Deprecate array access
We now have proper value objects with properties for everything we need.
2024-02-03 22:26:56 +01:00
Alexander M. Turek 7527b788de Switch back to stable dependencies (#11210) 2024-02-03 21:24:40 +01:00
Grégoire Paris cfadb5499d Merge pull request #11207 from derrabus/chore/readme
Update branches in README
2024-02-03 20:06:12 +01:00
Grégoire Paris e52bc846f0 Merge pull request #11209 from greg0ire/update-branch-metdata
Update branch metadata
2024-02-03 20:00:29 +01:00
Alexander M. Turek 9ce9ae2818 Update branches in README 2024-02-03 19:43:49 +01:00
Grégoire Paris f259754b7c Update branch metadata 2024-02-03 19:40:21 +01:00
Alexander M. Turek 3bc2cb6b15 Merge branch '2.19.x' into 3.0.x
* 2.19.x:
  Update branch metadata
2024-02-03 18:45:55 +01:00
Grégoire Paris fdb9d44538 Merge pull request #11206 from greg0ire/update-branch-metdata
Update branch metadata
2024-02-03 18:37:27 +01:00
Grégoire Paris a9fcaf1d18 Update branch metadata 2024-02-03 18:35:43 +01:00
94 changed files with 1780 additions and 763 deletions
+34 -40
View File
@@ -6,22 +6,52 @@
"docsSlug": "doctrine-orm",
"versions": [
{
"name": "3.0",
"branchName": "3.0.x",
"name": "4.0",
"branchName": "4.0.x",
"slug": "latest",
"upcoming": true
},
{
"name": "3.2",
"branchName": "3.2.x",
"slug": "3.2",
"upcoming": true
},
{
"name": "3.1",
"branchName": "3.1.x",
"slug": "3.1",
"current": true
},
{
"name": "3.0",
"branchName": "3.0.x",
"slug": "3.0",
"maintained": false
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"upcoming": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"maintained": true
},
{
"name": "2.18",
"branchName": "2.18.x",
"slug": "2.18",
"upcoming": true
"maintained": false
},
{
"name": "2.17",
"branchName": "2.17.x",
"slug": "2.17",
"current": true
"maintained": false
},
{
"name": "2.16",
@@ -64,42 +94,6 @@
"branchName": "2.10.x",
"slug": "2.10",
"maintained": false
},
{
"name": "2.9",
"branchName": "2.9.x",
"slug": "2.9",
"maintained": false
},
{
"name": "2.8",
"branchName": "2.8.x",
"slug": "2.8",
"maintained": false
},
{
"name": "2.7",
"branchName": "2.7",
"slug": "2.7",
"maintained": false
},
{
"name": "2.6",
"branchName": "2.6",
"slug": "2.6",
"maintained": false
},
{
"name": "2.5",
"branchName": "2.5",
"slug": "2.5",
"maintained": false
},
{
"name": "2.4",
"branchName": "2.4",
"slug": "2.4",
"maintained": false
}
]
}
+7 -7
View File
@@ -91,9 +91,9 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage file"
uses: "actions/upload-artifact@v3"
uses: "actions/upload-artifact@v4"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-coverage"
path: "coverage*.xml"
@@ -164,9 +164,9 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v3"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
@@ -230,7 +230,7 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v3"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
@@ -311,7 +311,7 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v3"
uses: "actions/upload-artifact@v4"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
@@ -332,7 +332,7 @@ jobs:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v3"
uses: "actions/download-artifact@v4"
with:
path: "reports"
+5 -4
View File
@@ -27,7 +27,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
php-version: "8.3"
- name: "Remove existing composer file"
run: "rm composer.json"
@@ -40,9 +40,10 @@ jobs:
with:
dependency-versions: "highest"
- name: "Add dummy title to the sidebar"
- name: "Add orphan metadata where needed"
run: |
printf '%s\n%s\n\n%s\n' "Dummy title" "===========" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/sidebar.rst)" > docs/en/sidebar.rst
printf '%s\n\n%s\n' ":orphan:" "$(cat docs/en/reference/installation.rst)" > docs/en/reference/installation.rst
- name: "Run guides-cli"
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'Unknown directive' | ( ! grep WARNING )"
run: "vendor/bin/guides -vvv --no-progress docs/en 2>&1 | grep -v 'No template found for rendering directive' | ( ! grep WARNING )"
@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@3.0.0"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@4.0.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
+6 -6
View File
@@ -10,7 +10,7 @@ on:
- src/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
- tests/StaticAnalysis/**
push:
branches:
- "*.x"
@@ -20,7 +20,7 @@ on:
- src/**
- phpstan*
- psalm*
- tests/Doctrine/StaticAnalysis/**
- tests/StaticAnalysis/**
jobs:
static-analysis-phpstan:
@@ -32,7 +32,7 @@ jobs:
include:
- dbal-version: default
config: phpstan.neon
- dbal-version: 3.7
- dbal-version: 3.8.2
config: phpstan-dbal3.neon
steps:
@@ -43,7 +43,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: "8.2"
php-version: "8.3"
tools: cs2pr
- name: Require specific DBAL version
@@ -65,7 +65,7 @@ jobs:
matrix:
dbal-version:
- default
- 3.7
- 3.8.2
steps:
- name: "Checkout code"
@@ -75,7 +75,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
coverage: none
php-version: "8.2"
php-version: "8.3"
tools: cs2pr
- name: Require specific DBAL version
+25 -17
View File
@@ -1,11 +1,11 @@
| [3.0.x][3.0] | [2.18.x][2.18] | [2.17.x][2.17] |
|:----------------:|:----------------:|:----------:|
| [![Build status][3.0 image]][3.0] | [![Build status][2.18 image]][2.18] | [![Build status][2.17 image]][2.17] |
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.18 coverage image]][2.18 coverage] | [![Coverage Status][2.17 coverage image]][2.17 coverage] |
| [4.0.x][4.0] | [3.2.x][3.2] | [3.1.x][3.1] | [2.20.x][2.20] | [2.19.x][2.19] |
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
| [![Build status][4.0 image]][4.0] | [![Build status][3.2 image]][3.2] | [![Build status][3.1 image]][3.1] | [![Build status][2.20 image]][2.20] | [![Build status][2.19 image]][2.19] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.2 coverage image]][3.2 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] |
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
Doctrine ORM is an object-relational mapper for PHP 7.1+ that provides transparent persistence
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
@@ -18,15 +18,23 @@ without requiring unnecessary code duplication.
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
[3.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0.x
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x
[2.18 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.18.x
[2.18]: https://github.com/doctrine/orm/tree/2.18.x
[2.18 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.18.x/graph/badge.svg
[2.18 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.18.x
[2.17 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.17.x
[2.17]: https://github.com/doctrine/orm/tree/2.17.x
[2.17 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.17.x/graph/badge.svg
[2.17 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.17.x
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
[3.2 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.2.x
[3.2]: https://github.com/doctrine/orm/tree/3.2.x
[3.2 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.2.x/graph/badge.svg
[3.2 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.2.x
[3.1 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.1.x
[3.1]: https://github.com/doctrine/orm/tree/3.1.x
[3.1 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.1.x/graph/badge.svg
[3.1 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.1.x
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
[2.19 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.19.x
[2.19]: https://github.com/doctrine/orm/tree/2.19.x
[2.19 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.19.x/graph/badge.svg
[2.19 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.19.x
+57 -6
View File
@@ -1,5 +1,39 @@
# Upgrade to 3.1
## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty`
This class is deprecated and will be removed in 4.0.
Instead, use `Doctrine\Persistence\Reflection\EnumReflectionProperty` from
`doctrine/persistence`.
## Deprecate passing null to `ClassMetadata::fullyQualifiedClassName()`
Passing `null` to `Doctrine\ORM\ClassMetadata::fullyQualifiedClassName()` is
deprecated and will no longer be possible in 4.0.
## Deprecate array access
Using array access on instances of the following classes is deprecated:
- `Doctrine\ORM\Mapping\DiscriminatorColumnMapping`
- `Doctrine\ORM\Mapping\EmbedClassMapping`
- `Doctrine\ORM\Mapping\FieldMapping`
- `Doctrine\ORM\Mapping\JoinColumnMapping`
- `Doctrine\ORM\Mapping\JoinTableMapping`
# Upgrade to 3.0
## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception
Previously, calling
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
the owning side of an association returned `null`, which was undocumented, and
wrong according to the phpdoc of the parent method.
If you do not know whether you are on the owning or inverse side of an association,
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
to find out.
## BC BREAK: `Doctrine\ORM\Proxy\Autoloader` no longer extends `Doctrine\Common\Proxy\Autoloader`
Make sure to use the former when writing a type declaration or an `instanceof` check.
@@ -13,9 +47,9 @@ so `$targetEntity` is a first argument now. This change affects only non-named a
When using the `AUTO` strategy to let Doctrine determine the identity generation mechanism for
an entity, and when using `doctrine/dbal` 4, PostgreSQL now uses `IDENTITY`
instead of `SEQUENCE`. When upgrading from ORM 2.x and preference is on keeping
the `SEQUENCE` based identity generation, then configure the ORM this way:
instead of `SEQUENCE` or `SERIAL`.
* If you want to upgrade your existing tables to identity columns, you will need to follow [migration to identity columns on PostgreSQL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/how-to/postgresql-identity-migration.html)
* If you want to keep using SQL sequences, you need to configure the ORM this way:
```php
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\ORM\Configuration;
@@ -495,8 +529,8 @@ The methods have been replaced by PSR-6 compatible counterparts
## BC BREAK: Remove `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
This functionality has been moved to the new `DoctrineSetup` class. Call
`Doctrine\ORM\Tools\DoctrineSetup::createDefaultAnnotationDriver()` to create
This functionality has been moved to the new `ORMSetup` class. Call
`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create
a new annotation driver.
## BC BREAK: Remove `Doctrine\ORM\Tools\Setup`
@@ -504,7 +538,7 @@ a new annotation driver.
In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which
accepted a Doctrine Cache instance in each method has been removed.
The replacement is `Doctrine\ORM\Tools\DoctrineSetup` which accepts a PSR-6
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
cache instead.
## BC BREAK: Removed named queries
@@ -663,6 +697,23 @@ following classes and methods:
Use `toIterable()` instead.
# Upgrade to 2.19
## Deprecate calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association
Calling
`Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField()` with
the owning side of an association returns `null`, which is undocumented, and
wrong according to the phpdoc of the parent method.
If you do not know whether you are on the owning or inverse side of an association,
you can use `Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide()`
to find out.
## Deprecate `Doctrine\ORM\Query\Lexer::T_*` constants
Use `Doctrine\ORM\Query\TokenType::T_*` instead.
# Upgrade to 2.17
## Deprecate annotations classes for named queries
+6 -7
View File
@@ -23,29 +23,28 @@
"php": "^8.1",
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/collections": "^2.1",
"doctrine/dbal": "^3.6 || ^4",
"doctrine/collections": "^2.2",
"doctrine/dbal": "^3.8.2 || ^4",
"doctrine/deprecations": "^0.5.3 || ^1",
"doctrine/event-manager": "^1.2 || ^2",
"doctrine/inflector": "^1.4 || ^2.0",
"doctrine/instantiator": "^1.3 || ^2",
"doctrine/lexer": "^3",
"doctrine/persistence": "^3.1.1",
"doctrine/persistence": "^3.3.1",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/var-exporter": "~6.2.13 || ^6.3.2 || ^7.0"
"symfony/var-exporter": "^6.3.9 || ^7.0"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"phpbench/phpbench": "^1.0",
"phpstan/phpstan": "1.10.35",
"phpstan/phpstan": "1.10.59",
"phpunit/phpunit": "^10.4.0",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^5.4 || ^6.2 || ^7.0",
"vimeo/psalm": "5.16.0"
"vimeo/psalm": "5.22.2"
},
"minimum-stability": "RC",
"suggest": {
"ext-dom": "Provides support for XSD validation for XML mapping files",
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
-4
View File
@@ -1,4 +0,0 @@
en/_exts/configurationblock.pyc
build
en/_build
.idea
-3
View File
@@ -1,3 +0,0 @@
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git
@@ -1,7 +1,7 @@
Implementing ArrayAccess for Domain Objects
===========================================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
This recipe will show you how to implement ArrayAccess for your
domain objects in order to allow more uniform access, for example
+2 -2
View File
@@ -1,4 +1,4 @@
Welcome to Doctrine 2 ORM's documentation!
Welcome to Doctrine ORM's documentation!
==========================================
The Doctrine documentation is comprised of tutorials, a reference section and
@@ -93,7 +93,7 @@ Tutorials
Changelogs
----------
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
* `Upgrade <https://github.com/doctrine/orm/blob/HEAD/UPGRADE.md>`_
Cookbook
--------
+6 -44
View File
@@ -228,50 +228,12 @@ and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
Doctrine Mapping Types
----------------------
The ``type`` option used in the ``@Column`` accepts any of the existing
Doctrine types or even your own custom types. A Doctrine type defines
The ``type`` option used in the ``@Column`` accepts any of the
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
or :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`. A Doctrine type defines
the conversion between PHP and SQL types, independent from the database vendor
you are using. All Mapping Types that ship with Doctrine are fully portable
between the supported database systems.
As an example, the Doctrine Mapping Type ``string`` defines the
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
depending on the RDBMS brand). Here is a quick overview of the
built-in mapping types:
- ``string``: Type that maps a SQL VARCHAR to a PHP string.
- ``integer``: Type that maps a SQL INT to a PHP integer.
- ``smallint``: Type that maps a database SMALLINT to a PHP
integer.
- ``bigint``: Type that maps a database BIGINT to a PHP string.
- ``boolean``: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
- ``decimal``: Type that maps a SQL DECIMAL to a PHP string.
- ``date``: Type that maps a SQL DATETIME to a PHP DateTime
object.
- ``time``: Type that maps a SQL TIME to a PHP DateTime object.
- ``datetime``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object.
- ``datetimetz``: Type that maps a SQL DATETIME/TIMESTAMP to a PHP
DateTime object with timezone.
- ``text``: Type that maps a SQL CLOB to a PHP string.
- ``object``: Type that maps a SQL CLOB to a PHP object using
``serialize()`` and ``unserialize()``
- ``array``: Type that maps a SQL CLOB to a PHP array using
``serialize()`` and ``unserialize()``
- ``simple_array``: Type that maps a SQL CLOB to a PHP array using
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
Only use this type if you are sure that your values cannot contain a ",".
- ``json_array``: Type that maps a SQL CLOB to a PHP array using
``json_encode()`` and ``json_decode()``
- ``float``: Type that maps a SQL Float (Double Precision) to a
PHP double. *IMPORTANT*: Works only with locale settings that use
decimal points as separator.
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
varchar but uses a specific type if the platform supports it.
- ``blob``: Type that maps a SQL BLOB to a PHP resource stream
A cookbook article shows how to define :doc:`your own custom mapping types
<../cookbook/custom-mapping-types>`.
you are using.
.. note::
@@ -377,7 +339,7 @@ Here is the list of possible generation strategies:
a new entity is passed to ``EntityManager#persist``. NONE is the
same as leaving off the ``#[GeneratedValue]`` entirely.
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
It will allow you to pass a :ref:`class of your own to generate the identifiers.<attrref_customidgenerator>`
It will allow you to pass a :ref:`class of your own to generate the identifiers. <attrref_customidgenerator>`
Sequence Generator
^^^^^^^^^^^^^^^^^^
+11 -8
View File
@@ -18,14 +18,20 @@ especially what the strategies presented here provide help with.
.. note::
Having an SQL logger enabled when processing batches can have a serious impact on performance and resource usage.
To avoid that you should remove the corresponding middleware.
To remove all middlewares, you can use this line:
Having an SQL logger enabled when processing batches can have a
serious impact on performance and resource usage.
To avoid that, you should use a PSR logger implementation that can be
disabled at runtime.
For example, with Monolog, you can use ``Logger::pushHandler()``
to push a ``NullHandler`` to the logger instance, and then pop it
when you need to enable logging again.
With DBAL 2, you can disable the SQL logger like below:
.. code-block:: php
<?php
$em->getConnection()->getConfiguration()->setMiddlewares([]); // DBAL 3
$em->getConnection()->getConfiguration()->setSQLLogger(null); // DBAL 2
$em->getConnection()->getConfiguration()->setSQLLogger(null);
Bulk Inserts
------------
@@ -188,6 +194,3 @@ problems using the following approach:
Iterating results is not possible with queries that
fetch-join a collection-valued association. The nature of such SQL
result sets is not suitable for incremental hydration.
+40 -40
View File
@@ -131,47 +131,47 @@ There are two ways to set up an event handler:
* For *all events* you can create a Lifecycle Event Listener or Subscriber class and register
it by calling ``$eventManager->addEventListener()`` or ``eventManager->addEventSubscriber()``,
see
:ref:`Listening and subscribing to Lifecycle Events<listening-and-subscribing-to-lifecycle-events>`
:ref:`Listening and subscribing to Lifecycle Events <listening-and-subscribing-to-lifecycle-events>`
* For *some events* (see table below), you can create a *Lifecycle Callback* method in the
entity, see :ref:`Lifecycle Callbacks<lifecycle-callbacks>`.
entity, see :ref:`Lifecycle Callbacks <lifecycle-callbacks>`.
.. _reference-events-lifecycle-events:
Events Overview
---------------
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| Event | Dispatched by | Lifecycle | Passed |
| | | Callback | Argument |
+=================================================================+=======================+===========+=====================================+
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
| | on *initial* persist | | |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
| | metadata | | |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preFlush<reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onFlush<reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onClear<reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| Event | Dispatched by | Lifecycle | Passed |
| | | Callback | Argument |
+==================================================================+=======================+===========+=====================================+
| :ref:`preRemove <reference-events-pre-remove>` | ``$em->remove()`` | Yes | `PreRemoveEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postRemove <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostRemoveEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`prePersist <reference-events-pre-persist>` | ``$em->persist()`` | Yes | `PrePersistEventArgs`_ |
| | on *initial* persist | | |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postPersist <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostPersistEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preUpdate <reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postUpdate <reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `PostUpdateEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postLoad <reference-events-post-load>` | Loading from database | Yes | `PostLoadEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`loadClassMetadata <reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
| | metadata | | |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`preFlush <reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onFlush <reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`postFlush <reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
| :ref:`onClear <reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
+------------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
.. warning::
@@ -313,7 +313,7 @@ behaviors across different entity classes.
Note that they require much more detailed knowledge about the inner
workings of the ``EntityManager`` and ``UnitOfWork`` classes. Please
read the :ref:`Implementing Event Listeners<reference-events-implementing-listeners>` section
read the :ref:`Implementing Event Listeners <reference-events-implementing-listeners>` section
carefully if you are trying to write your own listener.
For event subscribers, there are no surprises. They declare the
@@ -426,11 +426,11 @@ prePersist
There are two ways for the ``prePersist`` event to be triggered:
- One is when you call ``EntityManager::persist()``. The
event is also called for all :ref:`cascaded associations<transitive-persistence>`.
event is also called for all :ref:`cascaded associations <transitive-persistence>`.
- The other is inside the ``flush()`` method when changes to associations are computed and
this association is marked as :ref:`cascade: persist<transitive-persistence>`. Any new entity found
this association is marked as :ref:`cascade: persist <transitive-persistence>`. Any new entity found
during this operation is also persisted and ``prePersist`` called
on it. This is called :ref:`persistence by reachability<persistence-by-reachability>`.
on it. This is called :ref:`persistence by reachability <persistence-by-reachability>`.
In both cases you get passed a ``PrePersistEventArgs`` instance
which has access to the entity and the entity manager.
@@ -454,7 +454,7 @@ preRemove
The ``preRemove`` event is called on every entity immediately when it is passed
to the ``EntityManager::remove()`` method. It is cascaded for all
associations that are marked as :ref:`cascade: remove<transitive-persistence>`
associations that are marked as :ref:`cascade: remove <transitive-persistence>`
It is not called for a DQL ``DELETE`` statement.
@@ -502,7 +502,7 @@ entities and their associations have been computed. This means, the
- Collections scheduled for removal
To make use of the ``onFlush`` event you have to be familiar with the
internal :ref:`UnitOfWork<unit-of-work>` API, which grants you access to the previously
internal :ref:`UnitOfWork <unit-of-work>` API, which grants you access to the previously
mentioned sets. See this example:
.. code-block:: php
+1 -1
View File
@@ -101,7 +101,7 @@ The many-to-many association is only supporting foreign keys in the table defini
To work with many-to-many tables containing extra columns you have to use the
foreign keys as primary keys feature of Doctrine ORM.
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
See :doc:`the tutorial on composite primary keys for more information <../tutorials/composite-primary-keys>`.
How can i paginate fetch-joined collections?
+1 -1
View File
@@ -342,7 +342,7 @@ It is not supported to use overrides in entity inheritance scenarios.
.. note::
When using traits, make sure not to miss the warnings given in the
:doc:`Limitations and Known Issues</reference/limitations-and-known-issues>` chapter.
:doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
Association Override
+1
View File
@@ -31,6 +31,7 @@
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations
+3 -3
View File
@@ -82,9 +82,9 @@ that directory with the following contents:
{
"require": {
"doctrine/orm": "^2.11.0",
"doctrine/dbal": "^3.2",
"symfony/cache": "^5.4"
"doctrine/orm": "^3",
"doctrine/dbal": "^4",
"symfony/cache": "^7"
},
"autoload": {
"psr-0": {"": "src/"}
-5
View File
@@ -125,11 +125,6 @@ parameters:
count: 1
path: src/EntityRepository.php
-
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata\\:\\:fullyQualifiedClassName\\(\\) should return class\\-string\\|null but returns string\\|null\\.$#"
count: 1
path: src/Mapping/ClassMetadata.php
-
message: "#^If condition is always true\\.$#"
count: 1
+1 -1
View File
@@ -30,7 +30,7 @@ parameters:
message: '~^Result of method Doctrine\\DBAL\\Connection::commit\(\) \(void\) is used\.$~'
path: src/UnitOfWork.php
-
message: '~^Strict comparison using === between void and false will always evaluate to false\.$~'
message: '~^Strict comparison using === between null and false will always evaluate to false\.$~'
path: src/UnitOfWork.php
-
message: '~^Variable \$e on left side of \?\? always exists and is not nullable\.$~'
+306 -330
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -170,6 +170,12 @@
<file name="src/Mapping/ClassMetadataFactory.php"/>
</errorLevel>
</ReferenceConstraintViolation>
<RiskyTruthyFalsyComparison>
<!-- TODO: Enable this new rule on higher branches. -->
<errorLevel type="suppress">
<directory name="src" />
</errorLevel>
</RiskyTruthyFalsyComparison>
<TooManyArguments>
<errorLevel type="suppress">
<!-- Symfony cache supports passing a key prefix to the clear method. -->
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Cache\Persister\Entity;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Cache\CollectionCacheKey;
@@ -201,8 +202,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
/**
* Generates a string of currently query
*
* @param string[]|Criteria $criteria
* @param string[]|null $orderBy
* @param string[]|Criteria $criteria
* @param array<string, Order>|null $orderBy
*/
protected function getHash(
string $query,
@@ -426,7 +427,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadCriteria(Criteria $criteria): array
{
$orderBy = $criteria->getOrderings();
$orderBy = $criteria->orderings();
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->persister->getSelectSQL($criteria);
+5 -7
View File
@@ -67,7 +67,7 @@ class FileLockRegion implements ConcurrentRegion
$time = $this->getLockTime($filename);
$content = $this->getLockContent($filename);
if (! $content || ! $time) {
if ($content === false || $time === false) {
@unlink($filename);
return false;
@@ -156,12 +156,10 @@ class FileLockRegion implements ConcurrentRegion
{
// The check below is necessary because on some platforms glob returns false
// when nothing matched (even though no errors occurred)
$filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION));
$filenames = glob(sprintf('%s/*.%s', $this->directory, self::LOCK_EXTENSION)) ?: [];
if ($filenames) {
foreach ($filenames as $filename) {
@unlink($filename);
}
foreach ($filenames as $filename) {
@unlink($filename);
}
return $this->region->evictAll();
@@ -176,7 +174,7 @@ class FileLockRegion implements ConcurrentRegion
$lock = Lock::createLockRead();
$filename = $this->getLockFileName($key);
if (! @file_put_contents($filename, $lock->value, LOCK_EX)) {
if (@file_put_contents($filename, $lock->value, LOCK_EX) === false) {
return null;
}
+1 -2
View File
@@ -36,8 +36,7 @@ use function method_exists;
* The EntityManager is the central access point to ORM functionality.
*
* It is a facade to all different ORM subsystems such as UnitOfWork,
* Query Language and Repository API. Instantiation is done through
* the static create() method. The quickest way to obtain a fully
* Query Language and Repository API. The quickest way to obtain a fully
* configured EntityManager is:
*
* use Doctrine\ORM\Tools\ORMSetup;
+2 -2
View File
@@ -91,7 +91,7 @@ class ObjectHydrator extends AbstractHydrator
}
// handle fetch-joined owning side bi-directional one-to-one associations
if ($assoc->inversedBy) {
if ($assoc->inversedBy !== null) {
$class = $this->getClassMetadata($className);
$inverseAssoc = $class->associationMappings[$assoc->inversedBy];
@@ -439,7 +439,7 @@ class ObjectHydrator extends AbstractHydrator
if ($relation->isOwningSide()) {
// TODO: Just check hints['fetched'] here?
// If there is an inverse mapping on the target class its bidirectional
if ($relation->inversedBy) {
if ($relation->inversedBy !== null) {
$inverseAssoc = $targetClass->associationMappings[$relation->inversedBy];
if ($inverseAssoc->isToOne()) {
$targetClass->reflFields[$inverseAssoc->fieldName]->setValue($element, $parentObject);
+55
View File
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal;
use BadMethodCallException;
use function array_filter;
use function array_is_list;
use function array_keys;
use function array_values;
use function assert;
use function debug_backtrace;
use function implode;
use function is_string;
use function sprintf;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
/**
* Checks if a variadic parameter contains unexpected named arguments.
*
* @internal
*/
trait NoUnknownNamedArguments
{
/**
* @param TItem[] $parameter
*
* @template TItem
* @psalm-assert list<TItem> $parameter
*/
private static function validateVariadicParameter(array $parameter): void
{
if (array_is_list($parameter)) {
return;
}
[, $trace] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
assert(isset($trace['class']));
$additionalArguments = array_values(array_filter(
array_keys($parameter),
is_string(...),
));
throw new BadMethodCallException(sprintf(
'Invalid call to %s::%s(), unknown named arguments: %s',
$trace['class'],
$trace['function'],
implode(', ', $additionalArguments),
));
}
}
+29
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\Deprecations\Deprecation;
use InvalidArgumentException;
use function property_exists;
@@ -14,12 +15,26 @@ trait ArrayAccessImplementation
/** @param string $offset */
public function offsetExists(mixed $offset): bool
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
return isset($this->$offset);
}
/** @param string $offset */
public function offsetGet(mixed $offset): mixed
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
if (! property_exists($this, $offset)) {
throw new InvalidArgumentException('Undefined property: ' . $offset);
}
@@ -30,12 +45,26 @@ trait ArrayAccessImplementation
/** @param string $offset */
public function offsetSet(mixed $offset, mixed $value): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
$this->$offset = $value;
}
/** @param string $offset */
public function offsetUnset(mixed $offset): void
{
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11211',
'Using ArrayAccess on %s is deprecated and will not be possible in Doctrine ORM 4.0. Use the corresponding property instead.',
static::class,
);
$this->$offset = null;
}
}
+3 -3
View File
@@ -288,7 +288,7 @@ class ClassMetadataBuilder
): ClassMetadataBuilder {
$builder = $this->createManyToOne($name, $targetEntity);
if ($inversedBy) {
if ($inversedBy !== null) {
$builder->inversedBy($inversedBy);
}
@@ -348,7 +348,7 @@ class ClassMetadataBuilder
): ClassMetadataBuilder {
$builder = $this->createOneToOne($name, $targetEntity);
if ($inversedBy) {
if ($inversedBy !== null) {
$builder->inversedBy($inversedBy);
}
@@ -380,7 +380,7 @@ class ClassMetadataBuilder
): ClassMetadataBuilder {
$builder = $this->createManyToMany($name, $targetEntity);
if ($inversedBy) {
if ($inversedBy !== null) {
$builder->inversedBy($inversedBy);
}
+6 -4
View File
@@ -4,18 +4,20 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
use ReflectionProperty;
final class ChainTypedFieldMapper implements TypedFieldMapper
{
/**
* @readonly
* @var TypedFieldMapper[] $typedFieldMappers
*/
use NoUnknownNamedArguments;
/** @var list<TypedFieldMapper> $typedFieldMappers */
private readonly array $typedFieldMappers;
public function __construct(TypedFieldMapper ...$typedFieldMappers)
{
self::validateVariadicParameter($typedFieldMappers);
$this->typedFieldMappers = $typedFieldMappers;
}
+47 -25
View File
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Mapping;
use BackedEnum;
use BadMethodCallException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\Deprecation;
use Doctrine\Instantiator\Instantiator;
use Doctrine\Instantiator\InstantiatorInterface;
use Doctrine\ORM\Cache\Exception\NonCacheableEntityAssociation;
@@ -14,6 +15,7 @@ use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
use Doctrine\Persistence\Mapping\ReflectionService;
use Doctrine\Persistence\Reflection\EnumReflectionProperty;
use InvalidArgumentException;
use LogicException;
use ReflectionClass;
@@ -41,6 +43,7 @@ use function is_subclass_of;
use function ltrim;
use function method_exists;
use function spl_object_id;
use function sprintf;
use function str_contains;
use function str_replace;
use function strtolower;
@@ -820,7 +823,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
assert($childProperty !== null);
if (isset($mapping->enumType)) {
$childProperty = new ReflectionEnumProperty(
$childProperty = new EnumReflectionProperty(
$childProperty,
$mapping->enumType,
);
@@ -839,7 +842,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
: $this->getAccessibleProperty($reflService, $this->name, $field);
if (isset($mapping->enumType) && $this->reflFields[$field] !== null) {
$this->reflFields[$field] = new ReflectionEnumProperty(
$this->reflFields[$field] = new EnumReflectionProperty(
$this->reflFields[$field],
$mapping->enumType,
);
@@ -2003,6 +2006,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
*/
public function setCustomRepositoryClass(string|null $repositoryClassName): void
{
if ($repositoryClassName === null) {
$this->customRepositoryClassName = null;
return;
}
$this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
}
@@ -2108,13 +2117,12 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
* @param DiscriminatorColumnMapping|mixed[]|null $columnDef
* @psalm-param DiscriminatorColumnMapping|array{
* name: string|null,
* fieldName?: string,
* type?: string,
* length?: int,
* fieldName?: string|null,
* type?: string|null,
* length?: int|null,
* columnDefinition?: string|null,
* enumType?: class-string<BackedEnum>|null,
* options?:array<string,
* mixed>|null
* options?: array<string, mixed>|null
* }|null $columnDef
*
* @throws MappingException
@@ -2136,13 +2144,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
}
if (! isset($columnDef['fieldName'])) {
$columnDef['fieldName'] = $columnDef['name'];
}
if (! isset($columnDef['type'])) {
$columnDef['type'] = 'string';
}
$columnDef['fieldName'] ??= $columnDef['name'];
$columnDef['type'] ??= 'string';
$columnDef['options'] ??= [];
if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) {
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
@@ -2462,21 +2466,43 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
public function getAssociationMappedByTargetField(string $assocName): string
{
$assoc = $this->associationMappings[$assocName];
$assoc = $this->getAssociationMapping($assocName);
assert($assoc instanceof InverseSideMapping);
if (! $assoc instanceof InverseSideMapping) {
throw new LogicException(sprintf(
<<<'EXCEPTION'
Context: Calling %s() with "%s", which is the owning side of an association.
Problem: The owning side of an association has no "mappedBy" field.
Solution: Call %s::isAssociationInverseSide() to check first.
EXCEPTION,
__METHOD__,
$assocName,
self::class,
));
}
return $assoc->mappedBy;
}
/**
* @return string|null null if the input value is null
* @psalm-return class-string|null
* @param C $className
*
* @return string|null null if and only if the input value is null
* @psalm-return (C is class-string ? class-string : (C is string ? string : null))
*
* @template C of string|null
*/
public function fullyQualifiedClassName(string|null $className): string|null
{
if (empty($className)) {
return $className;
if ($className === null) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11294',
'Passing null to %s is deprecated and will not be supported in Doctrine ORM 4.0',
__METHOD__,
);
return null;
}
if (! str_contains($className, '\\') && $this->namespace) {
@@ -2519,12 +2545,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
throw MappingException::missingEmbeddedClass($mapping['fieldName']);
}
$fqcn = $this->fullyQualifiedClassName($mapping['class']);
assert($fqcn !== null);
$this->embeddedClasses[$mapping['fieldName']] = EmbeddedClassMapping::fromMappingArray([
'class' => $fqcn,
'class' => $this->fullyQualifiedClassName($mapping['class']),
'columnPrefix' => $mapping['columnPrefix'] ?? null,
'declaredField' => $mapping['declaredField'] ?? null,
'originalField' => $mapping['originalField'] ?? null,
+1 -1
View File
@@ -642,7 +642,7 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
configuration.
We currently recommend "SEQUENCE" for "%s", when using DBAL 3,
and "IDENTITY" when using DBAL 4,
so you should use probably use the following configuration before upgrading to DBAL 4,
so you should probably use the following configuration before upgrading to DBAL 4,
and remove it after deploying that upgrade:
$configuration->setIdentityGenerationPreferences([
+6 -4
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping;
use BackedEnum;
use DateInterval;
use DateTime;
use DateTimeImmutable;
@@ -16,6 +17,7 @@ use ReflectionProperty;
use function array_merge;
use function assert;
use function enum_exists;
use function is_a;
/** @psalm-type ScalarName = 'array'|'bool'|'float'|'int'|'string' */
final class DefaultTypedFieldMapper implements TypedFieldMapper
@@ -52,18 +54,18 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
&& ($type instanceof ReflectionNamedType)
) {
if (! $type->isBuiltin() && enum_exists($type->getName())) {
$mapping['enumType'] = $type->getName();
$reflection = new ReflectionEnum($type->getName());
if (! $reflection->isBacked()) {
throw MappingException::backedEnumTypeRequired(
$field->class,
$mapping['fieldName'],
$mapping['enumType'],
$type->getName(),
);
}
$type = $reflection->getBackingType();
assert(is_a($type->getName(), BackedEnum::class, true));
$mapping['enumType'] = $type->getName();
$type = $reflection->getBackingType();
assert($type instanceof ReflectionNamedType);
}
+5 -5
View File
@@ -39,10 +39,10 @@ final class DiscriminatorColumnMapping implements ArrayAccess
* type: string,
* fieldName: string,
* name: string,
* length?: int,
* columnDefinition?: string,
* enumType?: class-string<BackedEnum>,
* options?: array<string, mixed>,
* length?: int|null,
* columnDefinition?: string|null,
* enumType?: class-string<BackedEnum>|null,
* options?: array<string, mixed>|null,
* } $mappingArray
*/
public static function fromMappingArray(array $mappingArray): self
@@ -58,7 +58,7 @@ final class DiscriminatorColumnMapping implements ArrayAccess
}
if (property_exists($mapping, $key)) {
$mapping->$key = $value;
$mapping->$key = $value ?? $mapping->$key;
} else {
throw new Exception('Unknown property ' . $key . ' on class ' . static::class);
}
+6 -2
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
@@ -20,6 +21,7 @@ use function assert;
use function constant;
use function count;
use function defined;
use function enum_exists;
use function explode;
use function extension_loaded;
use function file_get_contents;
@@ -403,9 +405,10 @@ class XmlDriver extends FileDriver
if (isset($oneToManyElement->{'order-by'})) {
$orderBy = [];
foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
/** @psalm-suppress DeprecatedConstant */
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
? (string) $orderByField['direction']
: Criteria::ASC;
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
}
$mapping['orderBy'] = $orderBy;
@@ -531,9 +534,10 @@ class XmlDriver extends FileDriver
if (isset($manyToManyElement->{'order-by'})) {
$orderBy = [];
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
/** @psalm-suppress DeprecatedConstant */
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
? (string) $orderByField['direction']
: Criteria::ASC;
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
}
$mapping['orderBy'] = $orderBy;
+1 -1
View File
@@ -30,7 +30,7 @@ final class ReflectionEmbeddedProperty extends ReflectionProperty
private readonly ReflectionProperty $childProperty,
private readonly string $embeddedClass,
) {
parent::__construct($childProperty->class, $childProperty->name);
parent::__construct($childProperty->getDeclaringClass()->name, $childProperty->getName());
}
public function getValue(object|null $object = null): mixed
+1
View File
@@ -11,6 +11,7 @@ use ValueError;
use function array_map;
use function is_array;
/** @deprecated use Doctrine\Persistence\Reflection\EnumReflectionProperty instead */
final class ReflectionEnumProperty extends ReflectionProperty
{
/** @param class-string<BackedEnum> $enumType */
+8 -1
View File
@@ -8,6 +8,7 @@ use Doctrine\Common\Collections\AbstractLazyCollection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -23,6 +24,7 @@ use function array_walk;
use function assert;
use function is_object;
use function spl_object_id;
use function strtoupper;
/**
* A PersistentCollection represents a collection of elements that have persistent state.
@@ -585,7 +587,12 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$criteria = clone $criteria;
$criteria->where($expression);
$criteria->orderBy($criteria->getOrderings() ?: $association->orderBy());
$criteria->orderBy(
$criteria->orderings() ?: array_map(
static fn (string $order): Order => Order::from(strtoupper($order)),
$association->orderBy(),
),
);
$persister = $this->getUnitOfWork()->getEntityPersister($association->targetEntity);
@@ -732,7 +732,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
{
$orderings = $criteria->getOrderings();
$orderings = $criteria->orderings();
if ($orderings) {
$orderBy = [];
foreach ($orderings as $name => $direction) {
@@ -741,7 +741,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
$targetClass,
$this->platform,
);
$orderBy[] = $field . ' ' . $direction;
$orderBy[] = $field . ' ' . $direction->value;
}
return ' ORDER BY ' . implode(', ', $orderBy);
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Persisters\Entity;
use BackedEnum;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\Common\Collections\Order;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\LockMode;
@@ -764,7 +765,7 @@ class BasicEntityPersister implements EntityPersister
$targetClass = $this->em->getClassMetadata($assoc->targetEntity);
if ($assoc->isOwningSide()) {
$isInverseSingleValued = $assoc->inversedBy && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy);
$isInverseSingleValued = $assoc->inversedBy !== null && ! $targetClass->isCollectionValuedAssociation($assoc->inversedBy);
// Mark inverse side as fetched in the hints, otherwise the UoW would
// try to load it in a separate query (remember: to-one inverse sides can not be lazy).
@@ -842,7 +843,10 @@ class BasicEntityPersister implements EntityPersister
*/
public function loadCriteria(Criteria $criteria): array
{
$orderBy = $criteria->getOrderings();
$orderBy = array_map(
static fn (Order $order): string => $order->value,
$criteria->orderings(),
);
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
@@ -1151,7 +1155,7 @@ class BasicEntityPersister implements EntityPersister
if (isset($this->class->fieldMappings[$fieldName])) {
$tableAlias = isset($this->class->fieldMappings[$fieldName]->inherited)
? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]['inherited'])
? $this->getSQLTableAlias($this->class->fieldMappings[$fieldName]->inherited)
: $baseTableAlias;
$columnName = $this->quoteStrategy->getColumnName($fieldName, $this->class, $this->platform);
@@ -466,7 +466,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|| isset($this->class->associationMappings[$name]->inherited)
|| ($this->class->isVersioned && $this->class->versionField === $name)
|| isset($this->class->embeddedClasses[$name])
|| isset($this->class->fieldMappings[$name]['notInsertable'])
|| isset($this->class->fieldMappings[$name]->notInsertable)
) {
continue;
}
@@ -519,9 +519,9 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
$class = null;
if ($this->class->isVersioned && $key === $versionedClass->versionField) {
$class = $versionedClass;
} elseif (isset($column['generated'])) {
$class = isset($column['inherited'])
? $this->em->getClassMetadata($column['inherited'])
} elseif (isset($column->generated)) {
$class = isset($column->inherited)
? $this->em->getClassMetadata($column->inherited)
: $this->class;
} else {
continue;
+7 -9
View File
@@ -216,11 +216,11 @@ EOPHP;
*/
private function createLazyInitializer(ClassMetadata $classMetadata, EntityPersister $entityPersister, IdentifierFlattener $identifierFlattener): Closure
{
return static function (InternalProxy $proxy, InternalProxy $original) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$identifier = $classMetadata->getIdentifierValues($original);
$entity = $entityPersister->loadById($identifier, $original);
return static function (InternalProxy $proxy) use ($entityPersister, $classMetadata, $identifierFlattener): void {
$identifier = $classMetadata->getIdentifierValues($proxy);
$original = $entityPersister->loadById($identifier);
if ($entity === null) {
if ($original === null) {
throw EntityNotFoundException::fromClassNameAndIdentifier(
$classMetadata->getName(),
$identifierFlattener->flattenIdentifier($classMetadata, $identifier),
@@ -234,11 +234,11 @@ EOPHP;
$class = $entityPersister->getClassMetadata();
foreach ($class->getReflectionProperties() as $property) {
if (! $property || ! $class->hasField($property->name) && ! $class->hasAssociation($property->name)) {
if (! $property || ! $class->hasField($property->getName()) && ! $class->hasAssociation($property->getName())) {
continue;
}
$property->setValue($proxy, $property->getValue($entity));
$property->setValue($proxy, $property->getValue($original));
}
};
}
@@ -283,9 +283,7 @@ EOPHP;
$identifierFields = array_intersect_key($class->getReflectionProperties(), $identifiers);
$proxyFactory = Closure::bind(static function (array $identifier) use ($initializer, $skippedProperties, $identifierFields, $className): InternalProxy {
$proxy = self::createLazyGhost(static function (InternalProxy $object) use ($initializer, &$proxy): void {
$initializer($object, $proxy);
}, $skippedProperties);
$proxy = self::createLazyGhost($initializer, $skippedProperties);
foreach ($identifierFields as $idField => $reflector) {
if (! isset($identifier[$idField])) {
+2 -11
View File
@@ -11,8 +11,6 @@ use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function assert;
use function is_numeric;
use function strtolower;
/**
@@ -63,17 +61,10 @@ class DateAddFunction extends FunctionNode
};
}
/**
* @return numeric-string
*
* @throws ASTException
*/
/** @throws ASTException */
private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
{
$sql = $this->intervalExpression->dispatch($sqlWalker);
assert(is_numeric($sql));
return $sql;
return $this->intervalExpression->dispatch($sqlWalker);
}
public function parse(Parser $parser): void
+2 -11
View File
@@ -8,8 +8,6 @@ use Doctrine\ORM\Query\AST\ASTException;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use function assert;
use function is_numeric;
use function strtolower;
/**
@@ -56,16 +54,9 @@ class DateSubFunction extends DateAddFunction
};
}
/**
* @return numeric-string
*
* @throws ASTException
*/
/** @throws ASTException */
private function dispatchIntervalExpression(SqlWalker $sqlWalker): string
{
$sql = $this->intervalExpression->dispatch($sqlWalker);
assert(is_numeric($sql));
return $sql;
return $this->intervalExpression->dispatch($sqlWalker);
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ class TrimFunction extends FunctionNode
$this->trimChar = $lexer->token->value;
}
if ($this->leading || $this->trailing || $this->both || $this->trimChar) {
if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) {
$parser->match(TokenType::T_FROM);
}
+11
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query;
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
use Traversable;
use function implode;
@@ -23,6 +24,8 @@ use function str_replace;
*/
class Expr
{
use NoUnknownNamedArguments;
/**
* Creates a conjunction of the given boolean expressions.
*
@@ -38,6 +41,8 @@ class Expr
*/
public function andX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Andx
{
self::validateVariadicParameter($x);
return new Expr\Andx($x);
}
@@ -56,6 +61,8 @@ class Expr
*/
public function orX(Expr\Comparison|Expr\Func|Expr\Andx|Expr\Orx|string ...$x): Expr\Orx
{
self::validateVariadicParameter($x);
return new Expr\Orx($x);
}
@@ -225,6 +232,8 @@ class Expr
*/
public function countDistinct(mixed ...$x): string
{
self::validateVariadicParameter($x);
return 'COUNT(DISTINCT ' . implode(', ', $x) . ')';
}
@@ -470,6 +479,8 @@ class Expr
*/
public function concat(mixed ...$x): Expr\Func
{
self::validateVariadicParameter($x);
return new Expr\Func('CONCAT', $x);
}
+6
View File
@@ -7,10 +7,12 @@ namespace Doctrine\ORM\Query\Expr;
use InvalidArgumentException;
use Stringable;
use function array_key_exists;
use function count;
use function get_debug_type;
use function implode;
use function in_array;
use function is_array;
use function is_string;
use function sprintf;
@@ -33,6 +35,10 @@ abstract class Base implements Stringable
public function __construct(mixed $args = [])
{
if (is_array($args) && array_key_exists(0, $args) && is_array($args[0])) {
$args = $args[0];
}
$this->addMultiple($args);
}
+1 -1
View File
@@ -134,7 +134,7 @@ class ResultSetMappingBuilder extends ResultSetMapping implements Stringable
$this->addFieldResult($alias, $columnAlias, $propertyName);
$enumType = $classMetadata->getFieldMapping($propertyName)['enumType'] ?? null;
$enumType = $classMetadata->getFieldMapping($propertyName)->enumType ?? null;
if (! empty($enumType)) {
$this->addEnumResult($columnAlias, $enumType);
}
+19 -7
View File
@@ -376,6 +376,10 @@ class SqlWalker
continue;
}
$sqlTableAlias = $this->useSqlTableAliases
? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
: '';
$conn = $this->em->getConnection();
$values = [];
@@ -384,14 +388,22 @@ class SqlWalker
}
foreach ($class->subClasses as $subclassName) {
$values[] = $conn->quote((string) $this->em->getClassMetadata($subclassName)->discriminatorValue);
$subclassMetadata = $this->em->getClassMetadata($subclassName);
// Abstract entity classes show up in the list of subClasses, but may be omitted
// from the discriminator map. In that case, they have a null discriminator value.
if ($subclassMetadata->discriminatorValue === null) {
continue;
}
$values[] = $conn->quote((string) $subclassMetadata->discriminatorValue);
}
$sqlTableAlias = $this->useSqlTableAliases
? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
: '';
$sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')';
if ($values !== []) {
$sqlParts[] = $sqlTableAlias . $class->getDiscriminatorColumn()->name . ' IN (' . implode(', ', $values) . ')';
} else {
$sqlParts[] = '1=0'; // impossible condition
}
}
$sql = implode(' AND ', $sqlParts);
@@ -786,7 +798,7 @@ class SqlWalker
$class = $this->getMetadataForDqlAlias($alias);
if (isset($class->associationMappings[$fieldName]->inherited)) {
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
$class = $this->em->getClassMetadata($class->associationMappings[$fieldName]->inherited);
}
$association = $class->associationMappings[$fieldName];
+40 -17
View File
@@ -6,6 +6,9 @@ namespace Doctrine\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
use Doctrine\ORM\Internal\QueryType;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Parameter;
@@ -38,6 +41,8 @@ use function substr;
*/
class QueryBuilder implements Stringable
{
use NoUnknownNamedArguments;
/**
* The array of DQL parts collected.
*
@@ -428,12 +433,12 @@ class QueryBuilder implements Stringable
* ->setParameter('user_id', 1);
* </code>
*
* @param string|int $key The parameter position or name.
* @param string|int|null $type ParameterType::* or \Doctrine\DBAL\Types\Type::* constant
* @param string|int $key The parameter position or name.
* @param ParameterType|ArrayParameterType|string|int|null $type ParameterType::*, ArrayParameterType::* or \Doctrine\DBAL\Types\Type::* constant
*
* @return $this
*/
public function setParameter(string|int $key, mixed $value, string|int|null $type = null): static
public function setParameter(string|int $key, mixed $value, ParameterType|ArrayParameterType|string|int|null $type = null): static
{
$existingParameter = $this->getParameter($key);
@@ -611,6 +616,8 @@ class QueryBuilder implements Stringable
*/
public function select(mixed ...$select): static
{
self::validateVariadicParameter($select);
$this->type = QueryType::Select;
if ($select === []) {
@@ -657,6 +664,8 @@ class QueryBuilder implements Stringable
*/
public function addSelect(mixed ...$select): static
{
self::validateVariadicParameter($select);
$this->type = QueryType::Select;
if ($select === []) {
@@ -951,6 +960,8 @@ class QueryBuilder implements Stringable
*/
public function where(mixed ...$predicates): static
{
self::validateVariadicParameter($predicates);
if (! (count($predicates) === 1 && $predicates[0] instanceof Expr\Composite)) {
$predicates = new Expr\Andx($predicates);
}
@@ -976,6 +987,8 @@ class QueryBuilder implements Stringable
*/
public function andWhere(mixed ...$where): static
{
self::validateVariadicParameter($where);
$dql = $this->getDQLPart('where');
if ($dql instanceof Expr\Andx) {
@@ -1006,6 +1019,8 @@ class QueryBuilder implements Stringable
*/
public function orWhere(mixed ...$where): static
{
self::validateVariadicParameter($where);
$dql = $this->getDQLPart('where');
if ($dql instanceof Expr\Orx) {
@@ -1033,6 +1048,8 @@ class QueryBuilder implements Stringable
*/
public function groupBy(string ...$groupBy): static
{
self::validateVariadicParameter($groupBy);
return $this->add('groupBy', new Expr\GroupBy($groupBy));
}
@@ -1051,6 +1068,8 @@ class QueryBuilder implements Stringable
*/
public function addGroupBy(string ...$groupBy): static
{
self::validateVariadicParameter($groupBy);
return $this->add('groupBy', new Expr\GroupBy($groupBy), true);
}
@@ -1062,6 +1081,8 @@ class QueryBuilder implements Stringable
*/
public function having(mixed ...$having): static
{
self::validateVariadicParameter($having);
if (! (count($having) === 1 && ($having[0] instanceof Expr\Andx || $having[0] instanceof Expr\Orx))) {
$having = new Expr\Andx($having);
}
@@ -1077,6 +1098,8 @@ class QueryBuilder implements Stringable
*/
public function andHaving(mixed ...$having): static
{
self::validateVariadicParameter($having);
$dql = $this->getDQLPart('having');
if ($dql instanceof Expr\Andx) {
@@ -1097,6 +1120,8 @@ class QueryBuilder implements Stringable
*/
public function orHaving(mixed ...$having): static
{
self::validateVariadicParameter($having);
$dql = $this->getDQLPart('having');
if ($dql instanceof Expr\Orx) {
@@ -1162,22 +1187,20 @@ class QueryBuilder implements Stringable
}
}
if ($criteria->getOrderings()) {
foreach ($criteria->getOrderings() as $sort => $order) {
$hasValidAlias = false;
foreach ($allAliases as $alias) {
if (str_starts_with($sort . '.', $alias . '.')) {
$hasValidAlias = true;
break;
}
foreach ($criteria->orderings() as $sort => $order) {
$hasValidAlias = false;
foreach ($allAliases as $alias) {
if (str_starts_with($sort . '.', $alias . '.')) {
$hasValidAlias = true;
break;
}
if (! $hasValidAlias) {
$sort = $allAliases[0] . '.' . $sort;
}
$this->addOrderBy($sort, $order);
}
if (! $hasValidAlias) {
$sort = $allAliases[0] . '.' . $sort;
}
$this->addOrderBy($sort, $order->value);
}
// Overwrite limits only if they was set in criteria
+15 -6
View File
@@ -5,8 +5,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use function assert;
use function ltrim;
/**
@@ -14,16 +16,22 @@ use function ltrim;
*/
class AttachEntityListenersListener
{
/** @var mixed[][] */
/**
* @var array<class-string, list<array{
* event: Events::*|null,
* class: class-string,
* method: string|null,
* }>>
*/
private array $entityListeners = [];
/**
* Adds an entity listener for a specific entity.
*
* @param string $entityClass The entity to attach the listener.
* @param string $listenerClass The listener class.
* @param string|null $eventName The entity lifecycle event.
* @param string|null $listenerCallback The listener callback method or NULL to use $eventName.
* @param class-string $entityClass The entity to attach the listener.
* @param class-string $listenerClass The listener class.
* @param Events::*|null $eventName The entity lifecycle event.
* @param non-falsy-string|null $listenerCallback The listener callback method or NULL to use $eventName.
*/
public function addEntityListener(
string $entityClass,
@@ -34,7 +42,7 @@ class AttachEntityListenersListener
$this->entityListeners[ltrim($entityClass, '\\')][] = [
'event' => $eventName,
'class' => $listenerClass,
'method' => $listenerCallback ?: $eventName,
'method' => $listenerCallback ?? $eventName,
];
}
@@ -53,6 +61,7 @@ class AttachEntityListenersListener
if ($listener['event'] === null) {
EntityListenerBuilder::bindEntityListener($metadata, $listener['class']);
} else {
assert($listener['method'] !== null);
$metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']);
}
}
+1 -1
View File
@@ -337,7 +337,7 @@ class SchemaTool
if (isset($class->table['uniqueConstraints'])) {
foreach ($class->table['uniqueConstraints'] as $indexName => $indexData) {
$uniqIndex = new Index($indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
$uniqIndex = new Index('tmp__' . $indexName, $this->getIndexColumns($class, $indexData), true, false, [], $indexData['options'] ?? []);
foreach ($table->getIndexes() as $tableIndexName => $tableIndex) {
if ($tableIndex->isFulfilledBy($uniqIndex)) {
+11 -11
View File
@@ -162,7 +162,7 @@ class SchemaValidator
}
}
if ($assoc->isOwningSide() && $assoc->inversedBy) {
if ($assoc->isOwningSide() && $assoc->inversedBy !== null) {
if ($targetMetadata->hasField($assoc->inversedBy)) {
$ce[] = 'The association ' . $class->name . '#' . $fieldName . ' refers to the inverse side ' .
'field ' . $assoc->targetEntity . '#' . $assoc->inversedBy . ' which is not defined as association.';
@@ -343,7 +343,7 @@ class SchemaValidator
return null;
}
$metadataFieldType = $this->findBuiltInType(Type::getType($fieldMapping['type']));
$metadataFieldType = $this->findBuiltInType(Type::getType($fieldMapping->type));
//If the metadata field type is not a mapped built-in type, we cannot check it
if ($metadataFieldType === null) {
@@ -371,7 +371,7 @@ class SchemaValidator
);
}
if (! isset($fieldMapping['enumType']) || $propertyType === $fieldMapping['enumType']) {
if (! isset($fieldMapping->enumType) || $propertyType === $fieldMapping->enumType) {
return null;
}
@@ -380,17 +380,17 @@ class SchemaValidator
$class->name,
$fieldName,
$propertyType,
$fieldMapping['enumType'],
$fieldMapping->enumType,
);
}
if (
isset($fieldMapping['enumType'])
&& $propertyType !== $fieldMapping['enumType']
isset($fieldMapping->enumType)
&& $propertyType !== $fieldMapping->enumType
&& interface_exists($propertyType)
&& is_a($fieldMapping['enumType'], $propertyType, true)
&& is_a($fieldMapping->enumType, $propertyType, true)
) {
$backingType = (string) (new ReflectionEnum($fieldMapping['enumType']))->getBackingType();
$backingType = (string) (new ReflectionEnum($fieldMapping->enumType))->getBackingType();
if ($metadataFieldType === $backingType) {
return null;
@@ -400,14 +400,14 @@ class SchemaValidator
"The field '%s#%s' has the metadata enumType '%s' with a backing type of '%s' that differs from the metadata field type '%s'.",
$class->name,
$fieldName,
$fieldMapping['enumType'],
$fieldMapping->enumType,
$backingType,
$metadataFieldType,
);
}
if (
$fieldMapping['type'] === 'json'
$fieldMapping->type === 'json'
&& in_array($propertyType, ['string', 'int', 'float', 'bool', 'true', 'false', 'null'], true)
) {
return null;
@@ -419,7 +419,7 @@ class SchemaValidator
$fieldName,
$propertyType,
$metadataFieldType,
$fieldMapping['type'],
$fieldMapping->type,
);
},
$class->fieldMappings,
+28 -19
View File
@@ -997,7 +997,7 @@ class UnitOfWork implements PropertyChangedListener
foreach ($actualData as $propName => $actualValue) {
$orgValue = $originalData[$propName] ?? null;
if (isset($class->fieldMappings[$propName]['enumType'])) {
if (isset($class->fieldMappings[$propName]->enumType)) {
if (is_array($orgValue)) {
foreach ($orgValue as $id => $val) {
if ($val instanceof BackedEnum) {
@@ -1267,16 +1267,16 @@ class UnitOfWork implements PropertyChangedListener
}
$joinColumns = reset($assoc->joinColumns);
if (! isset($joinColumns['onDelete'])) {
if (! isset($joinColumns->onDelete)) {
continue;
}
$onDeleteOption = strtolower($joinColumns['onDelete']);
$onDeleteOption = strtolower($joinColumns->onDelete);
if ($onDeleteOption !== 'cascade') {
continue;
}
$targetEntity = $class->getFieldValue($entity, $assoc['fieldName']);
$targetEntity = $class->getFieldValue($entity, $assoc->fieldName);
// If the association does not refer to another entity or that entity
// is not to be deleted, there is no ordering problem and we can
@@ -1557,18 +1557,15 @@ class UnitOfWork implements PropertyChangedListener
*/
final public static function getIdHashByIdentifier(array $identifier): string
{
foreach ($identifier as $k => $value) {
if ($value instanceof BackedEnum) {
$identifier[$k] = $value->value;
}
}
return implode(
' ',
array_map(
static function ($value) {
if ($value instanceof BackedEnum) {
return $value->value;
}
return $value;
},
$identifier,
),
$identifier,
);
}
@@ -2550,7 +2547,7 @@ class UnitOfWork implements PropertyChangedListener
$this->originalEntityData[$oid][$field] = $newValue;
$class->reflFields[$field]->setValue($entity, $newValue);
if ($assoc->inversedBy && $assoc->isOneToOne() && $newValue !== null) {
if ($assoc->inversedBy !== null && $assoc->isOneToOne() && $newValue !== null) {
$inverseAssoc = $targetClass->associationMappings[$assoc->inversedBy];
$targetClass->reflFields[$inverseAssoc->fieldName]->setValue($newValue, $entity);
}
@@ -2584,9 +2581,9 @@ class UnitOfWork implements PropertyChangedListener
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
if (! $isIteration && $assoc->isOneToMany()) {
if (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} elseif (($isIteration && $assoc->isOneToMany()) || $assoc->isManyToMany()) {
} else {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
}
@@ -2665,7 +2662,19 @@ class UnitOfWork implements PropertyChangedListener
foreach ($found as $targetValue) {
$sourceEntity = $targetProperty->getValue($targetValue);
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
if ($sourceEntity === null && isset($targetClass->associationMappings[$mappedBy]->joinColumns)) {
// case where the hydration $targetValue itself has not yet fully completed, for example
// in case a bi-directional association is being hydrated and deferring eager loading is
// not possible due to subclassing.
$data = $this->getOriginalEntityData($targetValue);
$id = [];
foreach ($targetClass->associationMappings[$mappedBy]->joinColumns as $joinColumn) {
$id[] = $data[$joinColumn->name];
}
} else {
$id = $this->identifierFlattener->flattenIdentifier($class, $class->getIdentifierValues($sourceEntity));
}
$idHash = implode(' ', $id);
if ($mapping->indexBy !== null) {
@@ -2713,7 +2722,7 @@ class UnitOfWork implements PropertyChangedListener
private function scheduleCollectionForBatchLoading(PersistentCollection $collection, ClassMetadata $sourceClass): void
{
$mapping = $collection->getMapping();
$name = $mapping['sourceEntity'] . '#' . $mapping['fieldName'];
$name = $mapping->sourceEntity . '#' . $mapping->fieldName;
if (! isset($this->eagerLoadingCollections[$name])) {
$this->eagerLoadingCollections[$name] = [
+8 -17
View File
@@ -4,8 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Performance\Mock;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
/**
@@ -13,22 +12,14 @@ use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
*/
class NonLoadingPersister extends BasicEntityPersister
{
public function __construct()
{
public function __construct(
ClassMetadata $class,
) {
$this->class = $class;
}
/**
* {@inheritDoc}
*/
public function load(
array $criteria,
object|null $entity = null,
AssociationMapping|null $assoc = null,
array $hints = [],
LockMode|int|null $lockMode = null,
int|null $limit = null,
array|null $orderBy = null,
): object|null {
return $entity;
public function loadById(array $identifier, object|null $entity = null): object|null
{
return $entity ?? new ($this->class->name)();
}
}
@@ -57,7 +57,7 @@ class NonProxyLoadingEntityManager implements EntityManagerInterface
public function getUnitOfWork(): UnitOfWork
{
return new NonProxyLoadingUnitOfWork();
return new NonProxyLoadingUnitOfWork($this);
}
public function getCache(): Cache|null
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\Performance\Mock;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\UnitOfWork;
/**
@@ -11,15 +12,17 @@ use Doctrine\ORM\UnitOfWork;
*/
class NonProxyLoadingUnitOfWork extends UnitOfWork
{
private NonLoadingPersister $entityPersister;
/** @var array<class-string, NonLoadingPersister> */
private array $entityPersisters = [];
public function __construct()
{
$this->entityPersister = new NonLoadingPersister();
public function __construct(
private EntityManagerInterface $entityManager,
) {
}
public function getEntityPersister(string $entityName): NonLoadingPersister
{
return $this->entityPersister;
return $this->entityPersisters[$entityName]
??= new NonLoadingPersister($this->entityManager->getClassMetadata($entityName));
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'abstract_fetch_eager_remote_control')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(['mobile' => 'MobileRemoteControl'])]
abstract class AbstractRemoteControl
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\Column(type: 'string')]
public string $name;
/** @var Collection<User> */
#[ORM\OneToMany(targetEntity: User::class, mappedBy: 'remoteControl', fetch: 'EAGER')]
public Collection $users;
public function __construct(string $name)
{
$this->name = $name;
$this->users = new ArrayCollection();
}
}
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class MobileRemoteControl extends AbstractRemoteControl
{
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'abstract_fetch_eager_user')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
public int $id;
#[ORM\ManyToOne(targetEntity: AbstractRemoteControl::class, inversedBy: 'users')]
#[ORM\JoinColumn(nullable: false)]
public AbstractRemoteControl $remoteControl;
public function __construct(AbstractRemoteControl $control)
{
$this->remoteControl = $control;
}
}
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'eager_composite_join_root')]
class RootEntity
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
private int|null $id = null;
#[ORM\Id]
#[ORM\Column(type: 'string', nullable: false, name: 'other_key', length: 42)]
private string $otherKey;
/** @var Collection<int, SecondLevel> */
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevel::class, fetch: 'EAGER')]
private Collection $secondLevel;
public function __construct(int $id, string $other)
{
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->id = $id;
}
public function getId(): int|null
{
return $this->id;
}
public function getOtherKey(): string
{
return $this->otherKey;
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'eager_composite_join_second_level')]
#[ORM\Index(name: 'root_other_key_idx', columns: ['root_other_key', 'root_id'])]
class SecondLevel
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
private int|null $upperId;
#[ORM\Id]
#[ORM\Column(type: 'string', nullable: false, name: 'other_key')]
private string $otherKey;
#[ORM\ManyToOne(targetEntity: RootEntity::class, inversedBy: 'secondLevel')]
#[ORM\JoinColumn(name: 'root_id', referencedColumnName: 'id')]
#[ORM\JoinColumn(name: 'root_other_key', referencedColumnName: 'other_key')]
private RootEntity $root;
public function __construct(RootEntity $upper)
{
$this->upperId = $upper->getId();
$this->otherKey = $upper->getOtherKey();
$this->root = $upper;
}
public function getId(): int|null
{
return $this->id;
}
}
-3
View File
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Cache\CacheConfiguration;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityRepository;
@@ -26,8 +25,6 @@ use Psr\Cache\CacheItemPoolInterface;
*/
class ConfigurationTest extends TestCase
{
use VerifyDeprecations;
private Configuration $configuration;
protected function setUp(): void
-3
View File
@@ -6,7 +6,6 @@ namespace Doctrine\Tests\ORM;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Exception\EntityManagerClosed;
@@ -27,8 +26,6 @@ use TypeError;
class EntityManagerTest extends OrmTestCase
{
use VerifyDeprecations;
private EntityManagerMock $entityManager;
protected function setUp(): void
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\AbstractFetchEager\AbstractRemoteControl;
use Doctrine\Tests\Models\AbstractFetchEager\MobileRemoteControl;
use Doctrine\Tests\Models\AbstractFetchEager\User;
use Doctrine\Tests\OrmFunctionalTestCase;
final class AbstractFetchEagerTest extends OrmFunctionalTestCase
{
public function testWithAbstractFetchEager(): void
{
$this->createSchemaForModels(
AbstractRemoteControl::class,
User::class,
);
$control = new MobileRemoteControl('smart');
$user = new User($control);
$entityManage = $this->getEntityManager();
$entityManage->persist($control);
$entityManage->persist($user);
$entityManage->flush();
$entityManage->clear();
$user = $entityManage->find(User::class, $user->id);
self::assertNotNull($user);
self::assertEquals('smart', $user->remoteControl->name);
self::assertTrue($user->remoteControl->users->contains($user));
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\RootEntity;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevel;
use Doctrine\Tests\OrmFunctionalTestCase;
final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCase
{
/** @ticket 11154 */
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
{
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);
$a1 = new RootEntity(1, 'A');
$this->_em->persist($a1);
$this->_em->flush();
$this->_em->clear();
self::assertCount(1, $this->_em->getRepository(RootEntity::class)->findAll());
}
}
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\UnitOfWork;
@@ -16,6 +17,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function assert;
use function class_exists;
/**
* Basic many-to-many association tests.
@@ -435,7 +437,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
$user = $this->_em->find($user::class, $user->id);
$criteria = Criteria::create()
->orderBy(['name' => Criteria::ASC]);
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
self::assertEquals(
['A', 'B', 'C', 'Developers_0'],
@@ -475,7 +477,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
$user = $this->_em->find($user::class, $user->id);
$criteria = Criteria::create()
->orderBy(['name' => Criteria::ASC]);
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
self::assertEquals(
['A', 'B', 'C'],
@@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Internal\Hydration\HydrationException;
use Doctrine\ORM\Internal\SQLResultCasing;
use Doctrine\ORM\PersistentCollection;
@@ -33,7 +32,6 @@ use PHPUnit\Framework\Attributes\Group;
class NativeQueryTest extends OrmFunctionalTestCase
{
use SQLResultCasing;
use VerifyDeprecations;
private AbstractPlatform|null $platform = null;
@@ -7,6 +7,7 @@ namespace Doctrine\Tests\ORM\Functional;
use DateTimeImmutable;
use Doctrine\DBAL\Platforms\SQLitePlatform;
use Doctrine\ORM\AbstractQuery;
use Doctrine\Tests\Models\Company\CompanyEmployee;
use Doctrine\Tests\Models\Company\CompanyManager;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
@@ -487,4 +488,34 @@ SQL;
$this->_em->flush();
$this->_em->clear();
}
#[Group('GH-11240')]
public function testDateAddWithColumnInterval(): void
{
$query = sprintf(
'SELECT DATE_ADD(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m',
CompanyEmployee::class,
);
$result = $this->_em->createQuery($query)
->setMaxResults(1)
->getSingleResult(AbstractQuery::HYDRATE_ARRAY);
self::assertArrayHasKey('add', $result);
}
#[Group('GH-11240')]
public function testDateSubWithColumnInterval(): void
{
$query = sprintf(
'SELECT DATE_SUB(CURRENT_TIMESTAMP(), m.salary, \'day\') AS add FROM %s m',
CompanyEmployee::class,
);
$result = $this->_em->createQuery($query)
->setMaxResults(1)
->getSingleResult(AbstractQuery::HYDRATE_ARRAY);
self::assertArrayHasKey('add', $result);
}
}
@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Types;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
#[Group('GH-11278')]
final class QueryParameterTest extends OrmFunctionalTestCase
{
private int $userId;
protected function setUp(): void
{
$this->useModelSet('cms');
parent::setUp();
$user = new CmsUser();
$user->name = 'John Doe';
$user->username = 'john';
$user2 = new CmsUser();
$user2->name = 'Jane Doe';
$user2->username = 'jane';
$user3 = new CmsUser();
$user3->name = 'Just Bill';
$user3->username = 'bill';
$this->_em->persist($user);
$this->_em->persist($user2);
$this->_em->persist($user3);
$this->_em->flush();
$this->userId = $user->id;
$this->_em->clear();
}
public function testParameterTypeInBuilder(): void
{
$result = $this->_em->createQueryBuilder()
->from(CmsUser::class, 'u')
->select('u.name')
->where('u.id = :id')
->setParameter('id', $this->userId, ParameterType::INTEGER)
->getQuery()
->getArrayResult();
self::assertSame([['name' => 'John Doe']], $result);
}
public function testParameterTypeInQuery(): void
{
$result = $this->_em->createQueryBuilder()
->from(CmsUser::class, 'u')
->select('u.name')
->where('u.id = :id')
->getQuery()
->setParameter('id', $this->userId, ParameterType::INTEGER)
->getArrayResult();
self::assertSame([['name' => 'John Doe']], $result);
}
public function testDbalTypeStringInBuilder(): void
{
$result = $this->_em->createQueryBuilder()
->from(CmsUser::class, 'u')
->select('u.name')
->where('u.id = :id')
->setParameter('id', $this->userId, Types::INTEGER)
->getQuery()
->getArrayResult();
self::assertSame([['name' => 'John Doe']], $result);
}
public function testDbalTypeStringInQuery(): void
{
$result = $this->_em->createQueryBuilder()
->from(CmsUser::class, 'u')
->select('u.name')
->where('u.id = :id')
->getQuery()
->setParameter('id', $this->userId, Types::INTEGER)
->getArrayResult();
self::assertSame([['name' => 'John Doe']], $result);
}
public function testArrayParameterTypeInBuilder(): void
{
$result = $this->_em->createQueryBuilder()
->from(CmsUser::class, 'u')
->select('u.name')
->where('u.username IN (:usernames)')
->orderBy('u.username')
->setParameter('usernames', ['john', 'jane'], ArrayParameterType::STRING)
->getQuery()
->getArrayResult();
self::assertSame([['name' => 'Jane Doe'], ['name' => 'John Doe']], $result);
}
public function testArrayParameterTypeInQuery(): void
{
$result = $this->_em->createQueryBuilder()
->from(CmsUser::class, 'u')
->select('u.name')
->where('u.username IN (:usernames)')
->orderBy('u.username')
->getQuery()
->setParameter('usernames', ['john', 'jane'], ArrayParameterType::STRING)
->getArrayResult();
self::assertSame([['name' => 'Jane Doe'], ['name' => 'John Doe']], $result);
}
}
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Models\DDC117\DDC117ApproveChanges;
use Doctrine\Tests\Models\DDC117\DDC117Article;
@@ -23,8 +22,6 @@ use function count;
#[Group('DDC-117')]
class DDC117Test extends OrmFunctionalTestCase
{
use VerifyDeprecations;
private DDC117Article|null $article1;
private DDC117Article|null $article2;
@@ -25,8 +25,8 @@ class GH11135Test extends OrmFunctionalTestCase
$cm1 = $this->_em->getClassMetadata(GH11135EntityWithOverride::class);
$cm2 = $this->_em->getClassMetadata(GH11135EntityWithoutOverride::class);
self::assertSame($cm1->getFieldMapping('id')['declared'], $cm2->getFieldMapping('id')['declared']);
self::assertSame($cm1->getAssociationMapping('ref')['declared'], $cm2->getAssociationMapping('ref')['declared']);
self::assertSame($cm1->getFieldMapping('id')->declared, $cm2->getFieldMapping('id')->declared);
self::assertSame($cm1->getAssociationMapping('ref')->declared, $cm2->getAssociationMapping('ref')->declared);
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table('gh11149_eager_product')]
class EagerProduct
{
#[ORM\Id]
#[ORM\Column]
public int $id;
/** @var Collection<string, EagerProductTranslation> */
#[ORM\OneToMany(
targetEntity: EagerProductTranslation::class,
mappedBy: 'product',
fetch: 'EAGER',
indexBy: 'locale_code',
)]
public Collection $translations;
public function __construct(int $id)
{
$this->id = $id;
$this->translations = new ArrayCollection();
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table('gh11149_eager_product_translation')]
class EagerProductTranslation
{
#[ORM\Id]
#[ORM\Column]
private int $id;
#[ORM\ManyToOne(inversedBy: 'translations')]
#[ORM\JoinColumn(nullable: false)]
public EagerProduct $product;
#[ORM\ManyToOne]
#[ORM\JoinColumn(name: 'locale_code', referencedColumnName: 'code', nullable: false)]
public Locale $locale;
public function __construct(int $id, EagerProduct $product, Locale $locale)
{
$this->id = $id;
$this->product = $product;
$this->locale = $locale;
}
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Persistence\Proxy;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH11149Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
Locale::class,
EagerProduct::class,
EagerProductTranslation::class,
]);
}
public function testFetchEagerModeWithIndexBy(): void
{
// Load entities into database
$this->_em->persist($product = new EagerProduct(11149));
$this->_em->persist($locale = new Locale('fr_FR'));
$this->_em->persist(new EagerProductTranslation(11149, $product, $locale));
$this->_em->flush();
$this->_em->clear();
// Fetch entity from database
$product = $this->_em->find(EagerProduct::class, 11149);
// Assert associated entity is loaded eagerly
static::assertInstanceOf(EagerProduct::class, $product);
static::assertInstanceOf(PersistentCollection::class, $product->translations);
static::assertTrue($product->translations->isInitialized());
static::assertCount(1, $product->translations);
// Assert associated entity is indexed by given property
$translation = $product->translations->get('fr_FR');
static::assertInstanceOf(EagerProductTranslation::class, $translation);
static::assertNotInstanceOf(Proxy::class, $translation);
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11149;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table('gh11149_locale')]
class Locale
{
#[ORM\Id]
#[ORM\Column(length: 5)]
public string $code;
public function __construct(string $code)
{
$this->code = $code;
}
}
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
use Generator;
use PHPUnit\Framework\Attributes\DataProvider;
class GH11199Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH11199Root::class,
GH11199Parent::class,
GH11199Foo::class,
GH11199Baz::class,
GH11199AbstractLeaf::class,
]);
}
public static function dqlStatements(): Generator
{
yield ['SELECT e FROM ' . GH11199Root::class . ' e', "/WHERE g0_.asset_type IN \('root', 'foo', 'baz'\)$/"];
yield ['SELECT e FROM ' . GH11199Parent::class . ' e', "/WHERE g0_.asset_type IN \('foo'\)$/"];
yield ['SELECT e FROM ' . GH11199Foo::class . ' e', "/WHERE g0_.asset_type IN \('foo'\)$/"];
yield ['SELECT e FROM ' . GH11199Baz::class . ' e', "/WHERE g0_.asset_type IN \('baz'\)$/"];
yield ['SELECT e FROM ' . GH11199AbstractLeaf::class . ' e', '/WHERE 1=0/'];
}
#[DataProvider('dqlStatements')]
public function testGH11199(string $dql, string $expectedDiscriminatorValues): void
{
$query = $this->_em->createQuery($dql);
$sql = $query->getSQL();
self::assertMatchesRegularExpression($expectedDiscriminatorValues, $sql);
}
}
#[ORM\Entity]
#[ORM\Table(name: 'gh11199')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'asset_type', type: 'string')]
#[ORM\DiscriminatorMap([
'root' => GH11199Root::class,
'foo' => GH11199Foo::class,
'baz' => GH11199Baz::class,
])]
class GH11199Root
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
#[ORM\Column(type: 'integer')]
private int|null $id = null;
}
#[ORM\Entity]
abstract class GH11199Parent extends GH11199Root
{
}
#[ORM\Entity]
class GH11199Foo extends GH11199Parent
{
}
#[ORM\Entity]
class GH11199Baz extends GH11199Root
{
}
#[ORM\Entity]
abstract class GH11199AbstractLeaf extends GH11199Root
{
}
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToOne;
#[Entity]
class GH11386EntityCart
{
#[Id]
#[GeneratedValue]
#[Column]
private int|null $id = null;
#[Column]
private int|null $amount = null;
#[OneToOne(inversedBy: 'cart', cascade: ['persist', 'remove'], orphanRemoval: true)]
private GH11386EntityCustomer|null $customer = null;
public function getId(): int|null
{
return $this->id;
}
public function getAmount(): int|null
{
return $this->amount;
}
public function setAmount(int $amount): static
{
$this->amount = $amount;
return $this;
}
public function getCustomer(): GH11386EntityCustomer|null
{
return $this->customer;
}
public function setCustomer(GH11386EntityCustomer|null $customer): self
{
$this->customer = $customer;
return $this;
}
}
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToOne;
#[Entity]
class GH11386EntityCustomer
{
#[Id]
#[GeneratedValue]
#[Column]
private int|null $id = null;
#[Column]
private string|null $name = null;
#[Column(type: 'smallint', nullable: true, enumType: GH11386EnumType::class, options: ['unsigned' => true])]
private GH11386EnumType|null $type = null;
#[OneToOne(mappedBy: 'customer')]
private GH11386EntityCart|null $cart = null;
public function getId(): int|null
{
return $this->id;
}
public function getName(): string|null
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getType(): GH11386EnumType|null
{
return $this->type;
}
public function setType(GH11386EnumType $type): static
{
$this->type = $type;
return $this;
}
public function getCart(): GH11386EntityCart|null
{
return $this->cart;
}
public function setCart(GH11386EntityCart|null $cart): self
{
// unset the owning side of the relation if necessary
if ($cart === null && $this->cart !== null) {
$this->cart->setCustomer(null);
}
// set the owning side of the relation if necessary
if ($cart !== null && $cart->getCustomer() !== $this) {
$cart->setCustomer($this);
}
$this->cart = $cart;
return $this;
}
}
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
enum GH11386EnumType: int
{
case MALE = 1;
case FEMALE = 2;
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH11386;
use Doctrine\Tests\OrmFunctionalTestCase;
final class GH11386Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->createSchemaForModels(
GH11386EntityCart::class,
GH11386EntityCustomer::class,
);
}
public function testInitializeClonedProxy(): void
{
$cart = new GH11386EntityCart();
$cart->setAmount(1000);
$customer = new GH11386EntityCustomer();
$customer->setName('John Doe')
->setType(GH11386EnumType::MALE)
->setCart($cart);
$this->_em->persist($cart);
$this->_em->flush();
$this->_em->clear();
$cart = $this->_em->find(GH11386EntityCart::class, 1);
$customer = clone $cart->getCustomer();
self::assertEquals('John Doe', $customer->getName());
}
}
@@ -6,6 +6,8 @@ namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
@@ -17,6 +19,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function assert;
use function class_exists;
#[Group('GH7767')]
class GH7767Test extends OrmFunctionalTestCase
@@ -54,7 +57,9 @@ class GH7767Test extends OrmFunctionalTestCase
$parent = $this->_em->find(GH7767ParentEntity::class, 1);
assert($parent instanceof GH7767ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC']));
$children = $parent->getChildren()->matching(
Criteria::create()->orderBy(['position' => class_exists(Order::class) ? Order::Descending : 'DESC']),
);
self::assertEquals(300, $children[0]->position);
self::assertEquals(200, $children[1]->position);
@@ -70,7 +75,7 @@ class GH7767ParentEntity
#[GeneratedValue]
private int $id;
/** @psalm-var Collection<int, GH7767ChildEntity> */
/** @psalm-var Collection<int, GH7767ChildEntity>&Selectable<int, GH7767ChildEntity> */
#[OneToMany(targetEntity: GH7767ChildEntity::class, mappedBy: 'parent', fetch: 'EXTRA_LAZY', cascade: ['persist'])]
#[OrderBy(['position' => 'ASC'])]
private $children;
@@ -80,7 +85,7 @@ class GH7767ParentEntity
$this->children[] = new GH7767ChildEntity($this, $position);
}
/** @psalm-return Collection<int, GH7767ChildEntity> */
/** @psalm-return Collection<int, GH7767ChildEntity>&Selectable<int, GH7767ChildEntity> */
public function getChildren(): Collection
{
return $this->children;
@@ -6,6 +6,8 @@ namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
@@ -17,6 +19,7 @@ use Doctrine\Tests\OrmFunctionalTestCase;
use PHPUnit\Framework\Attributes\Group;
use function assert;
use function class_exists;
#[Group('GH7836')]
class GH7836Test extends OrmFunctionalTestCase
@@ -57,7 +60,13 @@ class GH7836Test extends OrmFunctionalTestCase
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
assert($parent instanceof GH7836ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['position' => 'DESC', 'name' => 'ASC']));
$children = $parent->getChildren()->matching(
Criteria::create()->orderBy(
class_exists(Order::class)
? ['position' => Order::Descending, 'name' => Order::Ascending]
: ['position' => 'DESC', 'name' => 'ASC'],
),
);
self::assertSame(200, $children[0]->position);
self::assertSame('baz', $children[0]->name);
@@ -72,7 +81,13 @@ class GH7836Test extends OrmFunctionalTestCase
$parent = $this->_em->find(GH7836ParentEntity::class, 1);
assert($parent instanceof GH7836ParentEntity);
$children = $parent->getChildren()->matching(Criteria::create()->orderBy(['name' => 'ASC', 'position' => 'ASC']));
$children = $parent->getChildren()->matching(
Criteria::create()->orderBy(
class_exists(Order::class)
? ['name' => Order::Ascending, 'position' => Order::Ascending]
: ['name' => 'ASC', 'position' => 'ASC'],
),
);
self::assertSame(100, $children[0]->position);
self::assertSame('bar', $children[0]->name);
@@ -91,7 +106,7 @@ class GH7836ParentEntity
#[GeneratedValue]
private int $id;
/** @var Collection<int, GH7836ChildEntity> */
/** @var Collection<int, GH7836ChildEntity>&Selectable<int, GH7836ChildEntity> */
#[OneToMany(targetEntity: GH7836ChildEntity::class, mappedBy: 'parent', fetch: 'EXTRA_LAZY', cascade: ['persist'])]
#[OrderBy(['position' => 'ASC', 'name' => 'ASC'])]
private $children;
@@ -101,7 +116,7 @@ class GH7836ParentEntity
$this->children[] = new GH7836ChildEntity($this, $position, $name);
}
/** @psalm-return Collection<int, GH7836ChildEntity> */
/** @psalm-return Collection<int, GH7836ChildEntity>&Selectable<int, GH7836ChildEntity> */
public function getChildren(): Collection
{
return $this->children;
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Id\SequenceGenerator as IdSequenceGenerator;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -42,8 +41,6 @@ use function unserialize;
class BasicInheritanceMappingTest extends OrmTestCase
{
use VerifyDeprecations;
private ClassMetadataFactory $cmf;
protected function setUp(): void
@@ -9,7 +9,6 @@ use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs;
@@ -55,8 +54,6 @@ use function sprintf;
class ClassMetadataFactoryTest extends OrmTestCase
{
use VerifyDeprecations;
public function testGetMetadataForSingleClass(): void
{
$platform = $this->createMock(AbstractPlatform::class);
+30 -4
View File
@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Mapping;
use ArrayObject;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -47,6 +48,7 @@ use Doctrine\Tests\Models\TypedProperties\UserTypedWithCustomTypedField;
use Doctrine\Tests\ORM\Mapping\TypedFieldMapper\CustomIntAsStringTypedFieldMapper;
use Doctrine\Tests\OrmTestCase;
use DoctrineGlobalArticle;
use LogicException;
use PHPUnit\Framework\Attributes\Group as TestGroup;
use ReflectionClass;
use stdClass;
@@ -66,6 +68,8 @@ require_once __DIR__ . '/../../Models/Global/GlobalNamespaceModel.php';
class ClassMetadataTest extends OrmTestCase
{
use VerifyDeprecations;
public function testClassMetadataInstanceSerialization(): void
{
$cm = new ClassMetadata(CmsUser::class);
@@ -859,8 +863,7 @@ class ClassMetadataTest extends OrmTestCase
$mapping = $cm->getFieldMapping('id');
self::assertArrayHasKey('declared', $mapping);
self::assertSame(AbstractContentItem::class, $mapping['declared']);
self::assertSame(AbstractContentItem::class, $mapping->declared);
}
public function testAssociationOverrideKeepsDeclaringClass(): void
@@ -871,8 +874,7 @@ class ClassMetadataTest extends OrmTestCase
$mapping = $cm->getAssociationMapping('parentDirectory');
self::assertArrayHasKey('declared', $mapping);
self::assertSame(Directory::class, $mapping['declared']);
self::assertSame(Directory::class, $mapping->declared);
}
#[TestGroup('DDC-1955')]
@@ -1054,6 +1056,30 @@ class ClassMetadataTest extends OrmTestCase
$metadata->addLifecycleCallback('foo', 'bar');
}
public function testGettingAnFQCNForNullIsDeprecated(): void
{
$metadata = new ClassMetadata(self::class);
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11294');
$metadata->fullyQualifiedClassName(null);
}
public function testItThrowsOnInvalidCallToGetAssociationMappedByTargetField(): void
{
$metadata = new ClassMetadata(self::class);
$metadata->mapOneToOne(['fieldName' => 'foo', 'targetEntity' => 'bar']);
$this->expectException(LogicException::class);
$this->expectExceptionMessage(<<<'EXCEPTION'
Context: Calling Doctrine\ORM\Mapping\ClassMetadata::getAssociationMappedByTargetField() with "foo", which is the owning side of an association.
Problem: The owning side of an association has no "mappedBy" field.
Solution: Call Doctrine\ORM\Mapping\ClassMetadata::isAssociationInverseSide() to check first.
EXCEPTION);
$metadata->getAssociationMappedByTargetField('foo');
}
}
#[MappedSuperclass]
@@ -297,8 +297,8 @@ class XmlMappingDriverTest extends MappingDriverTestCase
/** @var array{type: string} $name */
$name = $class->getFieldMapping('name');
self::assertEquals(ProjectId::class, $id['type']);
self::assertEquals(ProjectName::class, $name['type']);
self::assertEquals(ProjectId::class, $id->type);
self::assertEquals(ProjectName::class, $name->type);
}
public function testDisablingXmlValidationIsPossible(): void
+3 -4
View File
@@ -62,9 +62,8 @@ class ProxyFactoryTest extends OrmTestCase
public function testReferenceProxyDelegatesLoadingToThePersister(): void
{
$identifier = ['id' => 42];
$proxyClass = 'Proxies\__CG__\Doctrine\Tests\Models\ECommerce\ECommerceFeature';
$persister = $this->getMockBuilder(BasicEntityPersister::class)
->onlyMethods(['load'])
->onlyMethods(['loadById'])
->disableOriginalConstructor()
->getMock();
@@ -74,8 +73,8 @@ class ProxyFactoryTest extends OrmTestCase
$persister
->expects(self::atLeastOnce())
->method('load')
->with(self::equalTo($identifier), self::isInstanceOf($proxyClass))
->method('loadById')
->with(self::equalTo($identifier))
->will(self::returnValue($proxy));
$proxy->getDescription();
@@ -162,6 +162,11 @@ class LanguageRecognitionTest extends OrmTestCase
$this->assertValidDQL("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM(u.name) = 'someone'");
}
public function testTrimFalsyString(): void
{
$this->assertValidDQL("SELECT u.name FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE TRIM('0' FROM u.name) = 'someone'");
}
public function testArithmeticExpressionsSupportedInWherePart(): void
{
$this->assertValidDQL('SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000');
-3
View File
@@ -15,7 +15,6 @@ use Doctrine\DBAL\Driver\Result;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\UnitOfWork;
@@ -40,8 +39,6 @@ use function array_map;
class QueryTest extends OrmTestCase
{
use VerifyDeprecations;
/** @var EntityManagerMock */
protected $entityManager;
+26 -2
View File
@@ -4,8 +4,10 @@ declare(strict_types=1);
namespace Doctrine\Tests\ORM;
use BadMethodCallException;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Expr\Join;
@@ -22,6 +24,7 @@ use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Group;
use function array_filter;
use function class_exists;
/**
* Test case for the QueryBuilder class used to build DQL query string in a
@@ -80,6 +83,15 @@ class QueryBuilderTest extends OrmTestCase
$this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u');
}
public function testSimpleSelectArray(): void
{
$qb = $this->entityManager->createQueryBuilder()
->from(CmsUser::class, 'u')
->select(['u.id', 'u.username']);
$this->assertValidQueryBuilder($qb, 'SELECT u.id, u.username FROM Doctrine\Tests\Models\CMS\CmsUser u');
}
public function testSimpleDelete(): void
{
$qb = $this->entityManager->createQueryBuilder()
@@ -275,6 +287,18 @@ class QueryBuilderTest extends OrmTestCase
$this->assertValidQueryBuilder($qb, 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id = :uid');
}
public function testWhereWithUnexpectedNamedArguments(): void
{
$qb = $this->entityManager->createQueryBuilder()
->select('u')
->from(CmsUser::class, 'u');
$this->expectException(BadMethodCallException::class);
$this->expectExceptionMessage('Invalid call to Doctrine\ORM\QueryBuilder::where(), unknown named arguments: foo, bar');
$qb->where(foo: 'u.id = :uid', bar: 'u.name = :name');
}
public function testComplexAndWhere(): void
{
$qb = $this->entityManager->createQueryBuilder()
@@ -563,7 +587,7 @@ class QueryBuilderTest extends OrmTestCase
->from(CmsUser::class, 'u');
$criteria = new Criteria();
$criteria->orderBy(['field' => Criteria::DESC]);
$criteria->orderBy(['field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
$qb->addCriteria($criteria);
@@ -580,7 +604,7 @@ class QueryBuilderTest extends OrmTestCase
->join('u.article', 'a');
$criteria = new Criteria();
$criteria->orderBy(['a.field' => Criteria::DESC]);
$criteria->orderBy(['a.field' => class_exists(Order::class) ? Order::Descending : Criteria::DESC]);
$qb->addCriteria($criteria);
+36
View File
@@ -370,6 +370,27 @@ class SchemaToolTest extends OrmTestCase
self::assertTrue($schema->hasTable('first_entity'), 'Table first_entity should exist.');
self::assertFalse($schema->hasTable('second_entity'), 'Table second_entity should not exist.');
}
#[Group('GH-11314')]
public function testLoadUniqueConstraintWithoutName(): void
{
$em = $this->getTestEntityManager();
$entity = $em->getClassMetadata(GH11314Entity::class);
$schemaTool = new SchemaTool($em);
$schema = $schemaTool->getSchemaFromMetadata([$entity]);
self::assertTrue($schema->hasTable('GH11314Entity'));
$tableEntity = $schema->getTable('GH11314Entity');
self::assertTrue($tableEntity->hasIndex('uniq_2d81a3ed5bf54558875f7fd5'));
$tableIndex = $tableEntity->getIndex('uniq_2d81a3ed5bf54558875f7fd5');
self::assertTrue($tableIndex->isUnique());
self::assertSame(['field', 'anotherField'], $tableIndex->getColumns());
}
}
#[Table(options: ['foo' => 'bar', 'baz' => ['key' => 'val']])]
@@ -500,6 +521,21 @@ class IndexByFieldEntity
public $fieldName;
}
#[Entity]
#[UniqueConstraint(columns: ['field', 'anotherField'])]
class GH11314Entity
{
#[Column]
#[Id]
private int $id;
#[Column(name: 'field')]
private string $field;
#[Column(name: 'anotherField')]
private string $anotherField;
}
class IncorrectIndexByFieldEntity
{
/** @var int */
-3
View File
@@ -11,7 +11,6 @@ use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\EntityNotFoundException;
use Doctrine\ORM\Exception\EntityIdentityCollisionException;
use Doctrine\ORM\Mapping\ClassMetadata;
@@ -46,8 +45,6 @@ use function uniqid;
*/
class UnitOfWorkTest extends OrmTestCase
{
use VerifyDeprecations;
/**
* SUT
*/