Compare commits

...

67 Commits
3.0.2 ... 3.1.0

Author SHA1 Message Date
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
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
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
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
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
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
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
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
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
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
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
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
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
c9c493b2fe Merge branch '2.18.x' into 2.19.x
* 2.18.x:
  Backport QueryParameterTest (#11288)
2024-02-22 13:23:21 +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
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
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
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
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
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
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
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
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
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
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
37 changed files with 380 additions and 191 deletions

View File

@@ -11,29 +11,41 @@
"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",
"upcoming": true
"current": true
},
{
"name": "3.0",
"branchName": "3.0.x",
"slug": "3.0",
"current": true
"maintained": false
},
{
"name": "2.20",
"branchName": "2.20.x",
"slug": "2.20",
"upcoming": true
},
{
"name": "2.19",
"branchName": "2.19.x",
"slug": "2.19",
"upcoming": true
"maintained": true
},
{
"name": "2.18",
"branchName": "2.18.x",
"slug": "2.18",
"maintained": true
"maintained": false
},
{
"name": "2.17",

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"

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"

View File

@@ -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 }}

View File

@@ -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
@@ -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

View File

@@ -1,7 +1,7 @@
| [4.0.x][4.0] | [3.1.x][3.1] | [3.0.x][3.0] | [2.19.x][2.19] | [2.18.x][2.18] |
|:------------------------------------------------------:|:------------------------------------------------------:|:-------------------------------------------------------:|:--------------------------------------------------------:|:---------------------------------------------------------:|
| [![Build status][4.0 image]][4.0] | [![Build status][3.1 image]][3.1] | [![Build status][3.0 image]][3.0] | [![Build status][2.19 image]][2.19] | [![Build status][2.18 image]][2.18] |
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.1 coverage image]][3.1 coverage] | [![Coverage Status][3.0 coverage image]][3.0 coverage] | [![Coverage Status][2.19 coverage image]][2.19 coverage] | [![Coverage Status][2.18 coverage image]][2.18 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)
@@ -22,19 +22,19 @@ without requiring unnecessary code duplication.
[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
[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.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
[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

View File

@@ -1,3 +1,26 @@
# 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
@@ -674,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

View File

@@ -23,14 +23,14 @@
"php": "^8.1",
"composer-runtime-api": "^2",
"ext-ctype": "*",
"doctrine/collections": "^2.1",
"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"

View File

@@ -290,11 +290,6 @@
<code><![CDATA[$repositoryClassName]]></code>
</ArgumentTypeCoercion>
</file>
<file src="src/Mapping/Builder/EntityListenerBuilder.php">
<PossiblyNullArgument>
<code><![CDATA[$class]]></code>
</PossiblyNullArgument>
</file>
<file src="src/Mapping/ClassMetadata.php">
<DeprecatedProperty>
<code><![CDATA[$this->columnNames]]></code>
@@ -322,8 +317,6 @@
<code><![CDATA[$entity]]></code>
</ParamNameMismatch>
<PossiblyNullArgument>
<code><![CDATA[$class]]></code>
<code><![CDATA[$className]]></code>
<code><![CDATA[$mapping['targetEntity']]]></code>
<code><![CDATA[$mapping['targetEntity']]]></code>
<code><![CDATA[$parentReflFields[$embeddedClass->declaredField]]]></code>
@@ -1034,10 +1027,6 @@
</file>
<file src="src/QueryBuilder.php">
<ArgumentTypeCoercion>
<code><![CDATA[$having]]></code>
<code><![CDATA[$having]]></code>
<code><![CDATA[$where]]></code>
<code><![CDATA[$where]]></code>
<code><![CDATA[[$rootAlias => $join]]]></code>
<code><![CDATA[[$rootAlias => $join]]]></code>
</ArgumentTypeCoercion>

View File

@@ -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;
@@ -17,7 +18,6 @@ use Doctrine\ORM\Cache\Region;
use Doctrine\ORM\Cache\TimestampCacheKey;
use Doctrine\ORM\Cache\TimestampRegion;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
@@ -34,8 +34,6 @@ use function sha1;
abstract class AbstractEntityPersister implements CachedEntityPersister
{
use CriteriaOrderings;
protected UnitOfWork $uow;
protected ClassMetadataFactory $metadataFactory;
@@ -204,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,
@@ -429,7 +427,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadCriteria(Criteria $criteria): array
{
$orderBy = self::getCriteriaOrderings($criteria);
$orderBy = $criteria->orderings();
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->persister->getSelectSQL($criteria);

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;
}

View File

@@ -1,50 +0,0 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Internal;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use function array_map;
use function enum_exists;
use function method_exists;
use function strtoupper;
trait CriteriaOrderings
{
/**
* @return array<string, string>
*
* @psalm-suppress DeprecatedMethod We need to call the deprecated API if the new one does not exist yet.
*/
private static function getCriteriaOrderings(Criteria $criteria): array
{
if (! method_exists(Criteria::class, 'orderings')) {
return $criteria->getOrderings();
}
return array_map(
static fn (Order $order): string => $order->value,
$criteria->orderings(),
);
}
/**
* @param array<string, string> $orderings
*
* @return array<string, string>|array<string, Order>
*/
private static function mapToOrderEnumIfAvailable(array $orderings): array
{
if (! enum_exists(Order::class)) {
return $orderings;
}
return array_map(
static fn (string $order): Order => Order::from(strtoupper($order)),
$orderings,
);
}
}

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);

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),
));
}
}

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;
}
}

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);
}

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;
}

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;
@@ -821,7 +823,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
assert($childProperty !== null);
if (isset($mapping->enumType)) {
$childProperty = new ReflectionEnumProperty(
$childProperty = new EnumReflectionProperty(
$childProperty,
$mapping->enumType,
);
@@ -840,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,
);
@@ -2004,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);
}
@@ -2476,11 +2484,25 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
return $assoc->mappedBy;
}
/** @return string|null null if the input value is 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) {
@@ -2523,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,

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

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 */

View File

@@ -8,8 +8,8 @@ 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\Internal\CriteriaOrderings;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ToManyAssociationMapping;
@@ -24,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.
@@ -41,8 +42,6 @@ use function spl_object_id;
*/
final class PersistentCollection extends AbstractLazyCollection implements Selectable
{
use CriteriaOrderings;
/**
* A snapshot of the collection at the moment it was fetched from the database.
* This is used to create a diff of the collection at commit time.
@@ -588,9 +587,12 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$criteria = clone $criteria;
$criteria->where($expression);
$criteria->orderBy(self::mapToOrderEnumIfAvailable(
self::getCriteriaOrderings($criteria) ?: $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);

View File

@@ -9,7 +9,6 @@ use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\InverseSideMapping;
@@ -33,8 +32,6 @@ use function sprintf;
*/
class ManyToManyPersister extends AbstractCollectionPersister
{
use CriteriaOrderings;
public function delete(PersistentCollection $collection): void
{
$mapping = $this->getMapping($collection);
@@ -735,7 +732,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
{
$orderings = self::getCriteriaOrderings($criteria);
$orderings = $criteria->orderings();
if ($orderings) {
$orderBy = [];
foreach ($orderings as $name => $direction) {
@@ -744,7 +741,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
$targetClass,
$this->platform,
);
$orderBy[] = $field . ' ' . $direction;
$orderBy[] = $field . ' ' . $direction->value;
}
return ' ORDER BY ' . implode(', ', $orderBy);

View File

@@ -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;
@@ -16,7 +17,6 @@ use Doctrine\DBAL\Result;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Mapping\AssociationMapping;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\JoinColumnMapping;
@@ -98,7 +98,6 @@ use function trim;
*/
class BasicEntityPersister implements EntityPersister
{
use CriteriaOrderings;
use LockSqlHelper;
/** @var array<string,string> */
@@ -766,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).
@@ -844,7 +843,10 @@ class BasicEntityPersister implements EntityPersister
*/
public function loadCriteria(Criteria $criteria): array
{
$orderBy = self::getCriteriaOrderings($criteria);
$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);
@@ -1153,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);

View File

@@ -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;

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);
}

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);
}

View File

@@ -798,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];

View File

@@ -8,7 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\ParameterType;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
use Doctrine\ORM\Internal\QueryType;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Parameter;
@@ -41,7 +41,7 @@ use function substr;
*/
class QueryBuilder implements Stringable
{
use CriteriaOrderings;
use NoUnknownNamedArguments;
/**
* The array of DQL parts collected.
@@ -616,6 +616,8 @@ class QueryBuilder implements Stringable
*/
public function select(mixed ...$select): static
{
self::validateVariadicParameter($select);
$this->type = QueryType::Select;
if ($select === []) {
@@ -662,6 +664,8 @@ class QueryBuilder implements Stringable
*/
public function addSelect(mixed ...$select): static
{
self::validateVariadicParameter($select);
$this->type = QueryType::Select;
if ($select === []) {
@@ -956,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);
}
@@ -981,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) {
@@ -1011,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) {
@@ -1038,6 +1048,8 @@ class QueryBuilder implements Stringable
*/
public function groupBy(string ...$groupBy): static
{
self::validateVariadicParameter($groupBy);
return $this->add('groupBy', new Expr\GroupBy($groupBy));
}
@@ -1056,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);
}
@@ -1067,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);
}
@@ -1082,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) {
@@ -1102,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) {
@@ -1167,7 +1187,7 @@ class QueryBuilder implements Stringable
}
}
foreach (self::getCriteriaOrderings($criteria) as $sort => $order) {
foreach ($criteria->orderings() as $sort => $order) {
$hasValidAlias = false;
foreach ($allAliases as $alias) {
if (str_starts_with($sort . '.', $alias . '.')) {
@@ -1180,7 +1200,7 @@ class QueryBuilder implements Stringable
$sort = $allAliases[0] . '.' . $sort;
}
$this->addOrderBy($sort, $order);
$this->addOrderBy($sort, $order->value);
}
// Overwrite limits only if they was set in criteria

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']);
}
}

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)) {

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,

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);
}
@@ -2713,7 +2710,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] = [

View File

@@ -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);
}
}

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;
@@ -67,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);
@@ -860,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
@@ -872,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')]
@@ -1056,6 +1057,15 @@ 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);

View File

@@ -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

View File

@@ -4,6 +4,7 @@ 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;
@@ -286,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()

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 */