Compare commits

...

61 Commits

Author SHA1 Message Date
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
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
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
a809a71aa6 Prepare releases 2.19 and 3.1 (#11335) 2024-03-03 18:43:41 +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
Benjamin Eberlei
e62571c8f4 Refator array_map into simple loop for performance. (#11332) 2024-03-02 23:11:11 +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
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
Alexander M. Turek
feb27f00c1 Address deprecations from Collection 2.2 (#11315) 2024-02-27 17:37:52 +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
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
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
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
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
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
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
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
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
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
1d218bae30 Make docs valid according to guides 0.3.3 (#11252) 2024-02-12 23:46:09 +01:00
Alexander M. Turek
7baef1e120 Remove references to deprecated constants from Lexer (#11234) 2024-02-07 15:39:20 +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
Grégoire Paris
40fbbf4429 Point link to correct upgrade guide (#11220) 2024-02-04 17:41:45 +01:00
Michael Skvortsov
6f98147d09 Ignore subclasses without discriminatorValue when generating discriminator column condition SQL (#11200)
After commit 4e8e3ef30b when `\Doctrine\ORM\Query\SqlWalker` generates dicsriminator column condition SQL (method `\Doctrine\ORM\Query\SqlWalker::generateDiscriminatorColumnConditionSQL`) it adds an empty string to the list of possible values if the inheritance hierarchy contains a non-root abstract class. 

When the discriminator column is implemented with a custom type in PostgreSQL (equivalent of Enum) the query fails because the type cannot have a value of an empty string. It boils down to the fact that `\Doctrine\ORM\Mapping\ClassMetadataInfo::$subClasses` contains an abstract class and in its Metadata the value of `\Doctrine\ORM\Mapping\ClassMetadataInfo::$discriminatorValue` is `null`.

#### Previous behavior

In version 2.14.1 `\Doctrine\ORM\Mapping\ClassMetadataInfo::$subClasses` does not contain an abstract class.

Fixes #11199, fixes #11177, fixes #10846.
---------

Co-authored-by: Michael Skvortsov <michael.skvortsov@eleving.com>
Co-authored-by: Matthias Pigulla <mp@webfactory.de>
2024-02-04 00:11: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
Alexander M. Turek
9ce9ae2818 Update branches in README 2024-02-03 19:43:49 +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
82 changed files with 2692 additions and 1595 deletions

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",

View File

@@ -97,9 +97,9 @@ jobs:
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
- 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.proxy }}-coverage"
path: "coverage*.xml"
@@ -170,9 +170,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"
@@ -240,7 +240,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"
@@ -317,7 +317,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"
@@ -372,7 +372,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"
@@ -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 )"

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

@@ -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:
@@ -48,7 +48,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
php-version: "8.3"
- name: "Require specific DBAL version"
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
@@ -89,7 +89,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
coverage: "none"
php-version: "8.2"
php-version: "8.3"
- name: "Require specific persistence version"
run: "composer require doctrine/persistence ^3.1 --no-update"

View File

@@ -1,7 +1,7 @@
| [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)
@@ -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

View File

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

@@ -42,14 +42,14 @@
"doctrine/annotations": "^1.13 || ^2",
"doctrine/coding-standard": "^9.0.2 || ^12.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/phpstan": "~1.4.10 || 1.10.35",
"phpstan/phpstan": "~1.4.10 || 1.10.59",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"squizlabs/php_codesniffer": "3.7.2",
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
"vimeo/psalm": "4.30.0 || 5.16.0"
"vimeo/psalm": "4.30.0 || 5.22.2"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"

4
docs/.gitignore vendored
View File

@@ -1,4 +0,0 @@
en/_exts/configurationblock.pyc
build
en/_build
.idea

3
docs/.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git

View File

@@ -99,12 +99,12 @@ discuss it step by step:
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER); // (2)
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
$parser->match(TokenType::T_IDENTIFIER); // (2)
$parser->match(TokenType::T_OPEN_PARENTHESIS); // (3)
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
$parser->match(Lexer::T_COMMA); // (5)
$parser->match(TokenType::T_COMMA); // (5)
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
$parser->match(TokenType::T_CLOSE_PARENTHESIS); // (3)
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
@@ -183,23 +183,23 @@ I'll skip the blah and show the code for this function:
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(TokenType::T_COMMA);
$parser->match(TokenType::T_IDENTIFIER);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(TokenType::T_IDENTIFIER);
/** @var Lexer $lexer */
$lexer = $parser->getLexer();
$this->unit = $lexer->token['value'];
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)

View File

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

View File

@@ -1,7 +1,7 @@
Implementing the Notify ChangeTracking Policy
=============================================
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
The NOTIFY change-tracking policy is the most effective
change-tracking policy provided by Doctrine but it requires some

View File

@@ -96,7 +96,7 @@ Tutorials
Changelogs
----------
* `Upgrade <https://github.com/doctrine/doctrine2/blob/master/UPGRADE.md>`_
* `Upgrade <https://github.com/doctrine/orm/blob/HEAD/UPGRADE.md>`_
Cookbook
--------

View File

@@ -462,7 +462,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.<annref_customidgenerator>`
It will allow you to pass a :ref:`class of your own to generate the identifiers. <annref_customidgenerator>`
Sequence Generator
^^^^^^^^^^^^^^^^^^

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.

View File

@@ -812,7 +812,7 @@ classes have to implement the base class :
namespace MyProject\Query\AST;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\TokenType;
class MysqlFloor extends FunctionNode
{
@@ -827,12 +827,12 @@ classes have to implement the base class :
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

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::
@@ -358,7 +358,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
@@ -471,11 +471,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.
@@ -499,7 +499,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.
@@ -547,7 +547,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

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?

View File

@@ -380,7 +380,7 @@ It is not supported to use overrides in entity inheritance scenarios.
.. note::
When using traits, make sure not to miss the warnings given in the
:doc:`Limitations and Known Issues</reference/limitations-and-known-issues>` chapter.
:doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
Association Override

View File

@@ -31,6 +31,7 @@
reference/inheritance-mapping
reference/working-with-objects
reference/working-with-associations
reference/typedfieldmapper
reference/events
reference/unitofwork
reference/unitofwork-associations

View File

@@ -279,4 +279,9 @@
<!-- https://github.com/doctrine/orm/issues/8537 -->
<exclude-pattern>src/QueryBuilder.php</exclude-pattern>
</rule>
<rule ref="SlevomatCodingStandard.PHP.UselessParentheses">
<!-- We need those parentheses to make enum access seem like valid syntax on PHP 7 -->
<exclude-pattern>src/Mapping/Driver/XmlDriver.php</exclude-pattern>
</rule>
</ruleset>

View File

@@ -170,11 +170,6 @@ parameters:
count: 2
path: src/Mapping/ClassMetadataFactory.php
-
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadataInfo\\:\\:fullyQualifiedClassName\\(\\) should return class\\-string\\|null but returns string\\|null\\.$#"
count: 1
path: src/Mapping/ClassMetadataInfo.php
-
message: "#^Method Doctrine\\\\ORM\\\\Mapping\\\\NamingStrategy\\:\\:joinColumnName\\(\\) invoked with 2 parameters, 1 required\\.$#"
count: 2
@@ -236,7 +231,7 @@ parameters:
path: src/Mapping/Driver/XmlDriver.php
-
message: "#^Offset 'version' on \\*NEVER\\* in isset\\(\\) always exists and is always null\\.$#"
message: "#^Offset 'version' on \\*NEVER\\* in isset\\(\\) always exists and is not nullable\\.$#"
count: 1
path: src/Mapping/Driver/XmlDriver.php
@@ -331,22 +326,7 @@ parameters:
path: src/Query/AST/Functions/DateSubFunction.php
-
message: "#^Parameter \\#1 \\$simpleArithmeticExpr of method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkSimpleArithmeticExpression\\(\\) expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\SimpleArithmeticExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
count: 1
path: src/Query/AST/Functions/LengthFunction.php
-
message: "#^Parameter \\#1 \\$simpleArithmeticExpr of method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkSimpleArithmeticExpression\\(\\) expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\SimpleArithmeticExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
count: 1
path: src/Query/AST/Functions/LowerFunction.php
-
message: "#^Parameter \\#1 \\$simpleArithmeticExpr of method Doctrine\\\\ORM\\\\Query\\\\SqlWalker\\:\\:walkSimpleArithmeticExpression\\(\\) expects Doctrine\\\\ORM\\\\Query\\\\AST\\\\SimpleArithmeticExpression, Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node given\\.$#"
count: 1
path: src/Query/AST/Functions/UpperFunction.php
-
message: "#^Method Doctrine\\\\ORM\\\\Query\\\\AST\\\\IndexBy\\:\\:dispatch\\(\\) should return string but returns void\\.$#"
message: "#^Method Doctrine\\\\ORM\\\\Query\\\\AST\\\\IndexBy\\:\\:dispatch\\(\\) should return string but returns null\\.$#"
count: 1
path: src/Query/AST/IndexBy.php
@@ -395,11 +375,6 @@ parameters:
count: 1
path: src/Query/Expr/Select.php
-
message: "#^Comparison operation \"\\<\" between null and 102 is always true\\.$#"
count: 1
path: src/Query/Parser.php
-
message: "#^Method Doctrine\\\\ORM\\\\Query\\\\Parser\\:\\:ArithmeticFactor\\(\\) should return Doctrine\\\\ORM\\\\Query\\\\AST\\\\ArithmeticFactor but returns Doctrine\\\\ORM\\\\Query\\\\AST\\\\Node\\|string\\.$#"
count: 1
@@ -415,14 +390,9 @@ parameters:
count: 1
path: src/Query/Parser.php
-
message: "#^Result of && is always true\\.$#"
count: 1
path: src/Query/Parser.php
-
message: "#^Unreachable statement \\- code above always terminates\\.$#"
count: 4
count: 3
path: src/Query/Parser.php
-

View File

@@ -31,6 +31,31 @@ parameters:
message: '/^Instanceof between Doctrine\\DBAL\\Platforms\\AbstractPlatform and Doctrine\\DBAL\\Platforms\\MySQLPlatform will always evaluate to false\.$/'
path: src/Utility/LockSqlHelper.php
# Forward compatibility with Collections 3
-
message: '#^Parameter \$order of anonymous function has invalid type Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Anonymous function has invalid return type Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Access to property \$value on an unknown class Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Call to static method from\(\) on an unknown class Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Call to an undefined method Doctrine\\Common\\Collections\\Criteria\:\:orderings\(\)\.$#'
path: src/Internal/CriteriaOrderings.php
-
message: '#^Method .+\:\:mapToOrderEnumIfAvailable\(\) has invalid return type Doctrine\\Common\\Collections\\Order\.$#'
path: src/Internal/CriteriaOrderings.php
# False positive
-
message: '/^Call to an undefined method Doctrine\\Common\\Cache\\Cache::deleteAll\(\)\.$/'

File diff suppressed because it is too large Load Diff

View File

@@ -243,6 +243,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. -->

View File

@@ -16,6 +16,7 @@ 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\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataFactory;
use Doctrine\ORM\PersistentCollection;
@@ -30,6 +31,8 @@ use function sha1;
abstract class AbstractEntityPersister implements CachedEntityPersister
{
use CriteriaOrderings;
/** @var UnitOfWork */
protected $uow;
@@ -475,7 +478,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
*/
public function loadCriteria(Criteria $criteria)
{
$orderBy = $criteria->getOrderings();
$orderBy = self::getCriteriaOrderings($criteria);
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->persister->getSelectSQL($criteria);

View File

@@ -0,0 +1,54 @@
<?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 class_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 function (Order $order): string {
return $order->value;
},
$criteria->orderings()
);
}
/**
* @param array<string, string> $orderings
*
* @return array<string, string>|array<string, Order>
*/
private static function mapToOrderEnumIfAvailable(array $orderings): array
{
if (! class_exists(Order::class)) {
return $orderings;
}
return array_map(
static function (string $order): Order {
return Order::from(strtoupper($order));
},
$orderings
);
}
}

View File

@@ -3681,6 +3681,17 @@ class ClassMetadataInfo implements ClassMetadata
*/
public function getAssociationMappedByTargetField($fieldName)
{
if (! $this->isAssociationInverseSide($fieldName)) {
Deprecation::trigger(
'doctrine/orm',
'https://github.com/doctrine/orm/pull/11309',
'Calling %s with owning side field %s is deprecated and will no longer be supported in Doctrine ORM 3.0. Call %s::isAssociationInverseSide() to check first.',
__METHOD__,
$fieldName,
self::class
);
}
return $this->associationMappings[$fieldName]['mappedBy'];
}
@@ -3707,7 +3718,6 @@ class ClassMetadataInfo implements ClassMetadata
* @param string|null $className
*
* @return string|null null if the input value is null
* @psalm-return class-string|null
*/
public function fullyQualifiedClassName($className)
{

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;
use const PHP_VERSION_ID;
@@ -54,18 +56,18 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
&& ($type instanceof ReflectionNamedType)
) {
if (PHP_VERSION_ID >= 80100 && ! $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);
}

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;
@@ -16,6 +17,7 @@ use LogicException;
use SimpleXMLElement;
use function assert;
use function class_exists;
use function constant;
use function count;
use function defined;
@@ -481,9 +483,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;
: (class_exists(Order::class) ? (Order::Ascending)->value : Criteria::ASC);
}
$mapping['orderBy'] = $orderBy;
@@ -609,9 +612,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;
: (class_exists(Order::class) ? (Order::Ascending)->value : Criteria::ASC);
}
$mapping['orderBy'] = $orderBy;

View File

@@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Mapping\ClassMetadata;
use ReturnTypeWillChange;
use RuntimeException;
@@ -41,6 +42,8 @@ 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.
@@ -671,7 +674,9 @@ final class PersistentCollection extends AbstractLazyCollection implements Selec
$criteria = clone $criteria;
$criteria->where($expression);
$criteria->orderBy($criteria->getOrderings() ?: $association['orderBy'] ?? []);
$criteria->orderBy(self::mapToOrderEnumIfAvailable(
self::getCriteriaOrderings($criteria) ?: $association['orderBy'] ?? []
));
$persister = $this->getUnitOfWork()->getEntityPersister($association['targetEntity']);

View File

@@ -8,6 +8,7 @@ use BadMethodCallException;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Expr\Comparison;
use Doctrine\DBAL\Exception as DBALException;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Persisters\SqlValueVisitor;
@@ -30,6 +31,8 @@ use function sprintf;
*/
class ManyToManyPersister extends AbstractCollectionPersister
{
use CriteriaOrderings;
/**
* {@inheritDoc}
*/
@@ -745,7 +748,7 @@ class ManyToManyPersister extends AbstractCollectionPersister
private function getOrderingSql(Criteria $criteria, ClassMetadata $targetClass): string
{
$orderings = $criteria->getOrderings();
$orderings = self::getCriteriaOrderings($criteria);
if ($orderings) {
$orderBy = [];
foreach ($orderings as $name => $direction) {

View File

@@ -15,6 +15,7 @@ use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Mapping\QuoteStrategy;
@@ -93,6 +94,7 @@ use function trim;
*/
class BasicEntityPersister implements EntityPersister
{
use CriteriaOrderings;
use LockSqlHelper;
/** @var array<string,string> */
@@ -884,7 +886,7 @@ class BasicEntityPersister implements EntityPersister
*/
public function loadCriteria(Criteria $criteria)
{
$orderBy = $criteria->getOrderings();
$orderBy = self::getCriteriaOrderings($criteria);
$limit = $criteria->getMaxResults();
$offset = $criteria->getFirstResult();
$query = $this->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "ABS" "(" SimpleArithmeticExpression ")"
@@ -30,11 +30,11 @@ class AbsFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
@@ -36,13 +36,13 @@ class BitAndFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
@@ -36,13 +36,13 @@ class BitOrFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondArithmetic = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary }* ")"
@@ -42,22 +42,22 @@ class ConcatFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstStringPrimary = $parser->StringPrimary();
$this->concatExpressions[] = $this->firstStringPrimary;
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondStringPrimary = $parser->StringPrimary();
$this->concatExpressions[] = $this->secondStringPrimary;
while ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
while ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$this->concatExpressions[] = $parser->StringPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CURRENT_DATE"
@@ -24,8 +24,8 @@ class CurrentDateFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CURRENT_TIME"
@@ -24,8 +24,8 @@ class CurrentTimeFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "CURRENT_TIMESTAMP"
@@ -24,8 +24,8 @@ class CurrentTimestampFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,10 +5,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function strtolower;
@@ -84,15 +84,15 @@ class DateAddFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstDateExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->intervalExpression = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->unit = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
@@ -34,13 +34,13 @@ class DateDiffFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->date1 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->date2 = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,10 +5,10 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function assert;
use function reset;
@@ -77,20 +77,20 @@ class IdentityFunction extends FunctionNode
*/
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->pathExpression = $parser->SingleValuedAssociationPathExpression();
if ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
$parser->match(Lexer::T_STRING);
if ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$parser->match(TokenType::T_STRING);
$token = $parser->getLexer()->token;
assert($token !== null);
$this->fieldMapping = $token->value;
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -8,9 +8,9 @@ use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\TypedExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "LENGTH" "(" StringPrimary ")"
@@ -33,12 +33,12 @@ class LengthFunction extends FunctionNode implements TypedExpression
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getReturnType(): Type

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")"
@@ -48,22 +48,22 @@ class LocateFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstStringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondStringPrimary = $parser->StringPrimary();
$lexer = $parser->getLexer();
if ($lexer->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
if ($lexer->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function sprintf;
@@ -33,11 +33,11 @@ class LowerFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
@@ -34,15 +34,15 @@ class ModFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function assert;
@@ -105,11 +105,11 @@ class SizeFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->collectionPathExpression = $parser->CollectionValuedPathExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function sprintf;
@@ -33,11 +33,11 @@ class SqrtFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
/**
* "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")"
@@ -44,22 +44,22 @@ class SubstringFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->firstSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$lexer = $parser->getLexer();
if ($lexer->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);
if ($lexer->isNextToken(TokenType::T_COMMA)) {
$parser->match(TokenType::T_COMMA);
$this->secondSimpleArithmeticExpression = $parser->SimpleArithmeticExpression();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\DBAL\Platforms\TrimMode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function assert;
use function strcasecmp;
@@ -62,25 +62,25 @@ class TrimFunction extends FunctionNode
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->parseTrimMode($parser);
if ($lexer->isNextToken(Lexer::T_STRING)) {
$parser->match(Lexer::T_STRING);
if ($lexer->isNextToken(TokenType::T_STRING)) {
$parser->match(TokenType::T_STRING);
assert($lexer->token !== null);
$this->trimChar = $lexer->token->value;
}
if ($this->leading || $this->trailing || $this->both || $this->trimChar) {
$parser->match(Lexer::T_FROM);
if ($this->leading || $this->trailing || $this->both || ($this->trimChar !== false)) {
$parser->match(TokenType::T_FROM);
}
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
/** @psalm-return TrimMode::* */
@@ -108,7 +108,7 @@ class TrimFunction extends FunctionNode
$value = $lexer->lookahead->value;
if (strcasecmp('leading', $value) === 0) {
$parser->match(Lexer::T_LEADING);
$parser->match(TokenType::T_LEADING);
$this->leading = true;
@@ -116,7 +116,7 @@ class TrimFunction extends FunctionNode
}
if (strcasecmp('trailing', $value) === 0) {
$parser->match(Lexer::T_TRAILING);
$parser->match(TokenType::T_TRAILING);
$this->trailing = true;
@@ -124,7 +124,7 @@ class TrimFunction extends FunctionNode
}
if (strcasecmp('both', $value) === 0) {
$parser->match(Lexer::T_BOTH);
$parser->match(TokenType::T_BOTH);
$this->both = true;

View File

@@ -5,9 +5,9 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\AST\Functions;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use function sprintf;
@@ -33,11 +33,11 @@ class UpperFunction extends FunctionNode
/** @inheritDoc */
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->stringPrimary = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}

View File

@@ -21,95 +21,249 @@ use function substr;
/**
* Scans a DQL query for tokens.
*
* @extends AbstractLexer<Lexer::T_*, string>
* @extends AbstractLexer<TokenType::T_*, string>
*/
class Lexer extends AbstractLexer
{
// All tokens that are not valid identifiers must be < 100
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_INPUT_PARAMETER = 4;
public const T_FLOAT = 5;
public const T_CLOSE_PARENTHESIS = 6;
public const T_OPEN_PARENTHESIS = 7;
public const T_COMMA = 8;
public const T_DIVIDE = 9;
public const T_DOT = 10;
public const T_EQUALS = 11;
public const T_GREATER_THAN = 12;
public const T_LOWER_THAN = 13;
public const T_MINUS = 14;
public const T_MULTIPLY = 15;
public const T_NEGATE = 16;
public const T_PLUS = 17;
public const T_OPEN_CURLY_BRACE = 18;
public const T_CLOSE_CURLY_BRACE = 19;
/** @deprecated use {@see TokenType::T_NONE} */
public const T_NONE = TokenType::T_NONE;
/** @deprecated use {@see TokenType::T_INTEGER} */
public const T_INTEGER = TokenType::T_INTEGER;
/** @deprecated use {@see TokenType::T_STRING} */
public const T_STRING = TokenType::T_STRING;
/** @deprecated use {@see TokenType::T_INPUT_PARAMETER} */
public const T_INPUT_PARAMETER = TokenType::T_INPUT_PARAMETER;
/** @deprecated use {@see TokenType::T_FLOAT} */
public const T_FLOAT = TokenType::T_FLOAT;
/** @deprecated use {@see TokenType::T_CLOSE_PARENTHESIS} */
public const T_CLOSE_PARENTHESIS = TokenType::T_CLOSE_PARENTHESIS;
/** @deprecated use {@see TokenType::T_OPEN_PARENTHESIS} */
public const T_OPEN_PARENTHESIS = TokenType::T_OPEN_PARENTHESIS;
/** @deprecated use {@see TokenType::T_COMMA} */
public const T_COMMA = TokenType::T_COMMA;
/** @deprecated use {@see TokenType::T_DIVIDE} */
public const T_DIVIDE = TokenType::T_DIVIDE;
/** @deprecated use {@see TokenType::T_DOT} */
public const T_DOT = TokenType::T_DOT;
/** @deprecated use {@see TokenType::T_EQUALS} */
public const T_EQUALS = TokenType::T_EQUALS;
/** @deprecated use {@see TokenType::T_GREATER_THAN} */
public const T_GREATER_THAN = TokenType::T_GREATER_THAN;
/** @deprecated use {@see TokenType::T_LOWER_THAN} */
public const T_LOWER_THAN = TokenType::T_LOWER_THAN;
/** @deprecated use {@see TokenType::T_MINUS} */
public const T_MINUS = TokenType::T_MINUS;
/** @deprecated use {@see TokenType::T_MULTIPLY} */
public const T_MULTIPLY = TokenType::T_MULTIPLY;
/** @deprecated use {@see TokenType::T_NEGATE} */
public const T_NEGATE = TokenType::T_NEGATE;
/** @deprecated use {@see TokenType::T_PLUS} */
public const T_PLUS = TokenType::T_PLUS;
/** @deprecated use {@see TokenType::T_OPEN_CURLY_BRACE} */
public const T_OPEN_CURLY_BRACE = TokenType::T_OPEN_CURLY_BRACE;
/** @deprecated use {@see TokenType::T_CLOSE_CURLY_BRACE} */
public const T_CLOSE_CURLY_BRACE = TokenType::T_CLOSE_CURLY_BRACE;
// All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
/** @deprecated No Replacement planned. */
public const T_ALIASED_NAME = 100;
public const T_FULLY_QUALIFIED_NAME = 101;
public const T_IDENTIFIER = 102;
public const T_ALIASED_NAME = TokenType::T_ALIASED_NAME;
/** @deprecated use {@see TokenType::T_FULLY_QUALIFIED_NAME} */
public const T_FULLY_QUALIFIED_NAME = TokenType::T_FULLY_QUALIFIED_NAME;
/** @deprecated use {@see TokenType::T_IDENTIFIER} */
public const T_IDENTIFIER = TokenType::T_IDENTIFIER;
// All keyword tokens should be >= 200
public const T_ALL = 200;
public const T_AND = 201;
public const T_ANY = 202;
public const T_AS = 203;
public const T_ASC = 204;
public const T_AVG = 205;
public const T_BETWEEN = 206;
public const T_BOTH = 207;
public const T_BY = 208;
public const T_CASE = 209;
public const T_COALESCE = 210;
public const T_COUNT = 211;
public const T_DELETE = 212;
public const T_DESC = 213;
public const T_DISTINCT = 214;
public const T_ELSE = 215;
public const T_EMPTY = 216;
public const T_END = 217;
public const T_ESCAPE = 218;
public const T_EXISTS = 219;
public const T_FALSE = 220;
public const T_FROM = 221;
public const T_GROUP = 222;
public const T_HAVING = 223;
public const T_HIDDEN = 224;
public const T_IN = 225;
public const T_INDEX = 226;
public const T_INNER = 227;
public const T_INSTANCE = 228;
public const T_IS = 229;
public const T_JOIN = 230;
public const T_LEADING = 231;
public const T_LEFT = 232;
public const T_LIKE = 233;
public const T_MAX = 234;
public const T_MEMBER = 235;
public const T_MIN = 236;
public const T_NEW = 237;
public const T_NOT = 238;
public const T_NULL = 239;
public const T_NULLIF = 240;
public const T_OF = 241;
public const T_OR = 242;
public const T_ORDER = 243;
public const T_OUTER = 244;
public const T_PARTIAL = 245;
public const T_SELECT = 246;
public const T_SET = 247;
public const T_SOME = 248;
public const T_SUM = 249;
public const T_THEN = 250;
public const T_TRAILING = 251;
public const T_TRUE = 252;
public const T_UPDATE = 253;
public const T_WHEN = 254;
public const T_WHERE = 255;
public const T_WITH = 256;
/** @deprecated use {@see TokenType::T_ALL} */
public const T_ALL = TokenType::T_ALL;
/** @deprecated use {@see TokenType::T_AND} */
public const T_AND = TokenType::T_AND;
/** @deprecated use {@see TokenType::T_ANY} */
public const T_ANY = TokenType::T_ANY;
/** @deprecated use {@see TokenType::T_AS} */
public const T_AS = TokenType::T_AS;
/** @deprecated use {@see TokenType::T_ASC} */
public const T_ASC = TokenType::T_ASC;
/** @deprecated use {@see TokenType::T_AVG} */
public const T_AVG = TokenType::T_AVG;
/** @deprecated use {@see TokenType::T_BETWEEN} */
public const T_BETWEEN = TokenType::T_BETWEEN;
/** @deprecated use {@see TokenType::T_BOTH} */
public const T_BOTH = TokenType::T_BOTH;
/** @deprecated use {@see TokenType::T_BY} */
public const T_BY = TokenType::T_BY;
/** @deprecated use {@see TokenType::T_CASE} */
public const T_CASE = TokenType::T_CASE;
/** @deprecated use {@see TokenType::T_COALESCE} */
public const T_COALESCE = TokenType::T_COALESCE;
/** @deprecated use {@see TokenType::T_COUNT} */
public const T_COUNT = TokenType::T_COUNT;
/** @deprecated use {@see TokenType::T_DELETE} */
public const T_DELETE = TokenType::T_DELETE;
/** @deprecated use {@see TokenType::T_DESC} */
public const T_DESC = TokenType::T_DESC;
/** @deprecated use {@see TokenType::T_DISTINCT} */
public const T_DISTINCT = TokenType::T_DISTINCT;
/** @deprecated use {@see TokenType::T_ELSE} */
public const T_ELSE = TokenType::T_ELSE;
/** @deprecated use {@see TokenType::T_EMPTY} */
public const T_EMPTY = TokenType::T_EMPTY;
/** @deprecated use {@see TokenType::T_END} */
public const T_END = TokenType::T_END;
/** @deprecated use {@see TokenType::T_ESCAPE} */
public const T_ESCAPE = TokenType::T_ESCAPE;
/** @deprecated use {@see TokenType::T_EXISTS} */
public const T_EXISTS = TokenType::T_EXISTS;
/** @deprecated use {@see TokenType::T_FALSE} */
public const T_FALSE = TokenType::T_FALSE;
/** @deprecated use {@see TokenType::T_FROM} */
public const T_FROM = TokenType::T_FROM;
/** @deprecated use {@see TokenType::T_GROUP} */
public const T_GROUP = TokenType::T_GROUP;
/** @deprecated use {@see TokenType::T_HAVING} */
public const T_HAVING = TokenType::T_HAVING;
/** @deprecated use {@see TokenType::T_HIDDEN} */
public const T_HIDDEN = TokenType::T_HIDDEN;
/** @deprecated use {@see TokenType::T_IN} */
public const T_IN = TokenType::T_IN;
/** @deprecated use {@see TokenType::T_INDEX} */
public const T_INDEX = TokenType::T_INDEX;
/** @deprecated use {@see TokenType::T_INNER} */
public const T_INNER = TokenType::T_INNER;
/** @deprecated use {@see TokenType::T_INSTANCE} */
public const T_INSTANCE = TokenType::T_INSTANCE;
/** @deprecated use {@see TokenType::T_IS} */
public const T_IS = TokenType::T_IS;
/** @deprecated use {@see TokenType::T_JOIN} */
public const T_JOIN = TokenType::T_JOIN;
/** @deprecated use {@see TokenType::T_LEADING} */
public const T_LEADING = TokenType::T_LEADING;
/** @deprecated use {@see TokenType::T_LEFT} */
public const T_LEFT = TokenType::T_LEFT;
/** @deprecated use {@see TokenType::T_LIKE} */
public const T_LIKE = TokenType::T_LIKE;
/** @deprecated use {@see TokenType::T_MAX} */
public const T_MAX = TokenType::T_MAX;
/** @deprecated use {@see TokenType::T_MEMBER} */
public const T_MEMBER = TokenType::T_MEMBER;
/** @deprecated use {@see TokenType::T_MIN} */
public const T_MIN = TokenType::T_MIN;
/** @deprecated use {@see TokenType::T_NEW} */
public const T_NEW = TokenType::T_NEW;
/** @deprecated use {@see TokenType::T_NOT} */
public const T_NOT = TokenType::T_NOT;
/** @deprecated use {@see TokenType::T_NULL} */
public const T_NULL = TokenType::T_NULL;
/** @deprecated use {@see TokenType::T_NULLIF} */
public const T_NULLIF = TokenType::T_NULLIF;
/** @deprecated use {@see TokenType::T_OF} */
public const T_OF = TokenType::T_OF;
/** @deprecated use {@see TokenType::T_OR} */
public const T_OR = TokenType::T_OR;
/** @deprecated use {@see TokenType::T_ORDER} */
public const T_ORDER = TokenType::T_ORDER;
/** @deprecated use {@see TokenType::T_OUTER} */
public const T_OUTER = TokenType::T_OUTER;
/** @deprecated use {@see TokenType::T_PARTIAL} */
public const T_PARTIAL = TokenType::T_PARTIAL;
/** @deprecated use {@see TokenType::T_SELECT} */
public const T_SELECT = TokenType::T_SELECT;
/** @deprecated use {@see TokenType::T_SET} */
public const T_SET = TokenType::T_SET;
/** @deprecated use {@see TokenType::T_SOME} */
public const T_SOME = TokenType::T_SOME;
/** @deprecated use {@see TokenType::T_SUM} */
public const T_SUM = TokenType::T_SUM;
/** @deprecated use {@see TokenType::T_THEN} */
public const T_THEN = TokenType::T_THEN;
/** @deprecated use {@see TokenType::T_TRAILING} */
public const T_TRAILING = TokenType::T_TRAILING;
/** @deprecated use {@see TokenType::T_TRUE} */
public const T_TRUE = TokenType::T_TRUE;
/** @deprecated use {@see TokenType::T_UPDATE} */
public const T_UPDATE = TokenType::T_UPDATE;
/** @deprecated use {@see TokenType::T_WHEN} */
public const T_WHEN = TokenType::T_WHEN;
/** @deprecated use {@see TokenType::T_WHERE} */
public const T_WHERE = TokenType::T_WHERE;
/** @deprecated use {@see TokenType::T_WITH} */
public const T_WITH = TokenType::T_WITH;
/**
* Creates a new query scanner object.
@@ -150,26 +304,26 @@ class Lexer extends AbstractLexer
*/
protected function getType(&$value)
{
$type = self::T_NONE;
$type = TokenType::T_NONE;
switch (true) {
// Recognize numeric values
case is_numeric($value):
if (str_contains($value, '.') || stripos($value, 'e') !== false) {
return self::T_FLOAT;
return TokenType::T_FLOAT;
}
return self::T_INTEGER;
return TokenType::T_INTEGER;
// Recognize quoted strings
case $value[0] === "'":
$value = str_replace("''", "'", substr($value, 1, strlen($value) - 2));
return self::T_STRING;
return TokenType::T_STRING;
// Recognize identifiers, aliased or qualified names
case ctype_alpha($value[0]) || $value[0] === '_' || $value[0] === '\\':
$name = 'Doctrine\ORM\Query\Lexer::T_' . strtoupper($value);
$name = 'Doctrine\ORM\Query\TokenType::T_' . strtoupper($value);
if (defined($name)) {
$type = constant($name);
@@ -187,61 +341,61 @@ class Lexer extends AbstractLexer
$value
);
return self::T_ALIASED_NAME;
return TokenType::T_ALIASED_NAME;
}
if (str_contains($value, '\\')) {
return self::T_FULLY_QUALIFIED_NAME;
return TokenType::T_FULLY_QUALIFIED_NAME;
}
return self::T_IDENTIFIER;
return TokenType::T_IDENTIFIER;
// Recognize input parameters
case $value[0] === '?' || $value[0] === ':':
return self::T_INPUT_PARAMETER;
return TokenType::T_INPUT_PARAMETER;
// Recognize symbols
case $value === '.':
return self::T_DOT;
return TokenType::T_DOT;
case $value === ',':
return self::T_COMMA;
return TokenType::T_COMMA;
case $value === '(':
return self::T_OPEN_PARENTHESIS;
return TokenType::T_OPEN_PARENTHESIS;
case $value === ')':
return self::T_CLOSE_PARENTHESIS;
return TokenType::T_CLOSE_PARENTHESIS;
case $value === '=':
return self::T_EQUALS;
return TokenType::T_EQUALS;
case $value === '>':
return self::T_GREATER_THAN;
return TokenType::T_GREATER_THAN;
case $value === '<':
return self::T_LOWER_THAN;
return TokenType::T_LOWER_THAN;
case $value === '+':
return self::T_PLUS;
return TokenType::T_PLUS;
case $value === '-':
return self::T_MINUS;
return TokenType::T_MINUS;
case $value === '*':
return self::T_MULTIPLY;
return TokenType::T_MULTIPLY;
case $value === '/':
return self::T_DIVIDE;
return TokenType::T_DIVIDE;
case $value === '!':
return self::T_NEGATE;
return TokenType::T_NEGATE;
case $value === '{':
return self::T_OPEN_CURLY_BRACE;
return TokenType::T_OPEN_CURLY_BRACE;
case $value === '}':
return self::T_CLOSE_CURLY_BRACE;
return TokenType::T_CLOSE_CURLY_BRACE;
// Default
default:

File diff suppressed because it is too large Load Diff

View File

@@ -471,6 +471,10 @@ class SqlWalker implements TreeWalker
continue;
}
$sqlTableAlias = $this->useSqlTableAliases
? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
: '';
$conn = $this->em->getConnection();
$values = [];
@@ -479,14 +483,22 @@ class SqlWalker implements TreeWalker
}
foreach ($class->subClasses as $subclassName) {
$values[] = $conn->quote($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($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);
@@ -2576,7 +2588,7 @@ class SqlWalker implements TreeWalker
/**
* Walks down an SimpleArithmeticExpression AST node, thereby generating the appropriate SQL.
*
* @param AST\SimpleArithmeticExpression $simpleArithmeticExpr
* @param AST\Node|string $simpleArithmeticExpr
*
* @return string
*

99
src/Query/TokenType.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Query;
final class TokenType
{
// All tokens that are not valid identifiers must be < 100
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_INPUT_PARAMETER = 4;
public const T_FLOAT = 5;
public const T_CLOSE_PARENTHESIS = 6;
public const T_OPEN_PARENTHESIS = 7;
public const T_COMMA = 8;
public const T_DIVIDE = 9;
public const T_DOT = 10;
public const T_EQUALS = 11;
public const T_GREATER_THAN = 12;
public const T_LOWER_THAN = 13;
public const T_MINUS = 14;
public const T_MULTIPLY = 15;
public const T_NEGATE = 16;
public const T_PLUS = 17;
public const T_OPEN_CURLY_BRACE = 18;
public const T_CLOSE_CURLY_BRACE = 19;
// All tokens that are identifiers or keywords that could be considered as identifiers should be >= 100
/** @deprecated No Replacement planned. */
public const T_ALIASED_NAME = 100;
public const T_FULLY_QUALIFIED_NAME = 101;
public const T_IDENTIFIER = 102;
// All keyword tokens should be >= 200
public const T_ALL = 200;
public const T_AND = 201;
public const T_ANY = 202;
public const T_AS = 203;
public const T_ASC = 204;
public const T_AVG = 205;
public const T_BETWEEN = 206;
public const T_BOTH = 207;
public const T_BY = 208;
public const T_CASE = 209;
public const T_COALESCE = 210;
public const T_COUNT = 211;
public const T_DELETE = 212;
public const T_DESC = 213;
public const T_DISTINCT = 214;
public const T_ELSE = 215;
public const T_EMPTY = 216;
public const T_END = 217;
public const T_ESCAPE = 218;
public const T_EXISTS = 219;
public const T_FALSE = 220;
public const T_FROM = 221;
public const T_GROUP = 222;
public const T_HAVING = 223;
public const T_HIDDEN = 224;
public const T_IN = 225;
public const T_INDEX = 226;
public const T_INNER = 227;
public const T_INSTANCE = 228;
public const T_IS = 229;
public const T_JOIN = 230;
public const T_LEADING = 231;
public const T_LEFT = 232;
public const T_LIKE = 233;
public const T_MAX = 234;
public const T_MEMBER = 235;
public const T_MIN = 236;
public const T_NEW = 237;
public const T_NOT = 238;
public const T_NULL = 239;
public const T_NULLIF = 240;
public const T_OF = 241;
public const T_OR = 242;
public const T_ORDER = 243;
public const T_OUTER = 244;
public const T_PARTIAL = 245;
public const T_SELECT = 246;
public const T_SET = 247;
public const T_SOME = 248;
public const T_SUM = 249;
public const T_THEN = 250;
public const T_TRAILING = 251;
public const T_TRUE = 252;
public const T_UPDATE = 253;
public const T_WHEN = 254;
public const T_WHERE = 255;
public const T_WITH = 256;
/** @internal */
private function __construct()
{
}
}

View File

@@ -7,6 +7,7 @@ namespace Doctrine\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Deprecations\Deprecation;
use Doctrine\ORM\Internal\CriteriaOrderings;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Parameter;
use Doctrine\ORM\Query\QueryExpressionVisitor;
@@ -39,6 +40,8 @@ use function substr;
*/
class QueryBuilder
{
use CriteriaOrderings;
/** @deprecated */
public const SELECT = 0;
@@ -1375,22 +1378,20 @@ class QueryBuilder
}
}
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 (self::getCriteriaOrderings($criteria) 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);
}
// Overwrite limits only if they was set in criteria

View File

@@ -365,7 +365,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) {
$method = method_exists($tableIndex, 'isFulfilledBy') ? 'isFulfilledBy' : 'isFullfilledBy';

View File

@@ -1781,18 +1781,15 @@ EXCEPTION
*/
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
);
}
@@ -3169,9 +3166,9 @@ EXCEPTION
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
if (! $isIteration && $assoc['type'] === ClassMetadata::ONE_TO_MANY) {
if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && ! $isIteration && ! $targetClass->isIdentifierComposite) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} elseif (($isIteration && $assoc['type'] === ClassMetadata::ONE_TO_MANY) || $assoc['type'] === ClassMetadata::MANY_TO_MANY) {
} else {
$this->loadCollection($pColl);
$pColl->takeSnapshot();
}
@@ -3252,7 +3249,19 @@ EXCEPTION
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 (isset($mapping['indexBy'])) {

View File

@@ -0,0 +1,48 @@
<?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")
*
* @var int
*/
public $id;
/**
* @ORM\Column(type="string")
*
* @var string
*/
public $name;
/**
* @ORM\OneToMany(targetEntity="User", mappedBy="remoteControl", fetch="EAGER")
*
* @var Collection<User>
*/
public $users;
public function __construct(string $name)
{
$this->name = $name;
$this->users = new ArrayCollection();
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\AbstractFetchEager;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class MobileRemoteControl extends AbstractRemoteControl
{
}

View File

@@ -0,0 +1,36 @@
<?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")
*
* @var int
*/
public $id;
/**
* @ORM\ManyToOne(targetEntity="AbstractRemoteControl", inversedBy="users")
* @ORM\JoinColumn(nullable=false)
*
* @var AbstractRemoteControl
*/
public $remoteControl;
public function __construct(AbstractRemoteControl $control)
{
$this->remoteControl = $control;
}
}

View File

@@ -0,0 +1,56 @@
<?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)
*
* @var int|null
*/
private $id = null;
/**
* @ORM\Id
* @ORM\Column(type="string", nullable=false, name="other_key")
*
* @var string
*/
private $otherKey;
/**
* @ORM\OneToMany(mappedBy="root", targetEntity=SecondLevel::class, fetch="EAGER")
*
* @var Collection<int, SecondLevel>
*/
private $secondLevel;
public function __construct(int $id, string $other)
{
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->id = $id;
}
public function getId(): ?int
{
return $this->id;
}
public function getOtherKey(): string
{
return $this->otherKey;
}
}

View File

@@ -0,0 +1,55 @@
<?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", indexes={
* @ORM\Index(name="root_other_key_idx", columns={"root_other_key", "root_id"})
* })
*/
class SecondLevel
{
/**
* @ORM\Id
* @ORM\Column(type="integer", nullable=false)
*
* @var int|null
*/
private $upperId;
/**
* @ORM\Id
* @ORM\Column(type="string", nullable=false, name="other_key")
*
* @var string
*/
private $otherKey;
/**
* @ORM\ManyToOne(targetEntity=RootEntity::class, inversedBy="secondLevel")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="root_id", referencedColumnName="id"),
* @ORM\JoinColumn(name="root_other_key", referencedColumnName="other_key")
* })
*
* @var RootEntity
*/
private $root;
public function __construct(RootEntity $upper)
{
$this->upperId = $upper->getId();
$this->otherKey = $upper->getOtherKey();
$this->root = $upper;
}
public function getId(): ?int
{
return $this->id;
}
}

View File

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

View File

@@ -7,9 +7,9 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmFunctionalTestCase;
@@ -75,10 +75,10 @@ class NoOp extends FunctionNode
public function parse(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->field = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $sqlWalker): string

View File

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

View File

@@ -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\PersistentCollection;
use Doctrine\ORM\UnitOfWork;
use Doctrine\Tests\Models\CMS\CmsGroup;
@@ -14,6 +15,7 @@ use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmFunctionalTestCase;
use function assert;
use function class_exists;
use function get_class;
/**
@@ -436,7 +438,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
$user = $this->_em->find(get_class($user), $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'],
@@ -478,7 +480,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
$user = $this->_em->find(get_class($user), $user->id);
$criteria = Criteria::create()
->orderBy(['name' => Criteria::ASC]);
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
self::assertEquals(
['A', 'B', 'C'],

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Types\Types;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmFunctionalTestCase;
use function class_exists;
/** @group GH-11278 */
final class QueryParameterTest extends OrmFunctionalTestCase
{
/** @var int */
private $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'], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY)
->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'], class_exists(ArrayParameterType::class) ? ArrayParameterType::STRING : Connection::PARAM_STR_ARRAY)
->getArrayResult();
self::assertSame([['name' => 'Jane Doe'], ['name' => 'John Doe']], $result);
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Tests\OrmFunctionalTestCase;
use Generator;
class GH11199Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH11199Root::class,
GH11199Parent::class,
GH11199Foo::class,
GH11199Baz::class,
GH11199AbstractLeaf::class,
]);
}
public 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" = "\Doctrine\Tests\ORM\Functional\Ticket\GH11199Root",
* "foo" = "\Doctrine\Tests\ORM\Functional\Ticket\GH11199Foo",
* "baz" = "\Doctrine\Tests\ORM\Functional\Ticket\GH11199Baz",
* })
*/
class GH11199Root
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
* @ORM\Column(type="integer")
*
* @var int|null
*/
private $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
{
}

View File

@@ -10,9 +10,9 @@ use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\OrmFunctionalTestCase;
final class GH7286Test extends OrmFunctionalTestCase
@@ -114,14 +114,14 @@ class GH7286CustomConcat extends FunctionNode
public function parse(Parser $parser): void
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->first = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$parser->match(TokenType::T_COMMA);
$this->second = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $walker): string

View File

@@ -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;
@@ -16,6 +18,7 @@ use Doctrine\ORM\Mapping\OrderBy;
use Doctrine\Tests\OrmFunctionalTestCase;
use function assert;
use function class_exists;
/** @group GH7767 */
class GH7767Test extends OrmFunctionalTestCase
@@ -53,7 +56,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);
@@ -73,7 +78,7 @@ class GH7767ParentEntity
private $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"})
*/
@@ -84,7 +89,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;

View File

@@ -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;
@@ -16,6 +18,7 @@ use Doctrine\ORM\Mapping\OrderBy;
use Doctrine\Tests\OrmFunctionalTestCase;
use function assert;
use function class_exists;
/** @group GH7836 */
class GH7836Test extends OrmFunctionalTestCase
@@ -56,7 +59,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);
@@ -71,7 +80,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);
@@ -94,7 +109,7 @@ class GH7836ParentEntity
private $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"})
*/
@@ -105,7 +120,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;

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;
@@ -54,6 +55,8 @@ require_once __DIR__ . '/../../Models/Global/GlobalNamespaceModel.php';
class ClassMetadataTest extends OrmTestCase
{
use VerifyDeprecations;
public function testClassMetadataInstanceSerialization(): void
{
$cm = new ClassMetadata(CMS\CmsUser::class);
@@ -1379,6 +1382,16 @@ class ClassMetadataTest extends OrmTestCase
'columnPrefix' => false,
]);
}
public function testInvalidCallToGetAssociationMappedByTargetFieldIsDeprecated(): void
{
$metadata = new ClassMetadata(self::class);
$metadata->mapOneToOne(['fieldName' => 'foo', 'targetEntity' => 'bar']);
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/11309');
$metadata->getAssociationMappedByTargetField('foo');
}
}
/** @MappedSuperclass */

View File

@@ -172,6 +172,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');

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Query;
use Doctrine\Common\Lexer\Token;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\OrmTestCase;
class LexerTest extends OrmTestCase
@@ -35,7 +36,7 @@ class LexerTest extends OrmTestCase
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_ALL, $token->type);
self::assertEquals(TokenType::T_ALL, $token->type);
}
public function testScannerRecognizesDecimalInteger(): void
@@ -43,7 +44,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('1234');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INTEGER, $token->type);
self::assertEquals(TokenType::T_INTEGER, $token->type);
self::assertEquals(1234, $token->value);
}
@@ -52,7 +53,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('1.234');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(1.234, $token->value);
}
@@ -61,7 +62,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('1.2e3');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(1.2e3, $token->value);
}
@@ -70,7 +71,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('0.2e3');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(.2e3, $token->value);
}
@@ -79,7 +80,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('7E-10');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(7E-10, $token->value);
}
@@ -88,7 +89,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('123456789.01');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertEquals(1.2345678901e8, $token->value);
}
@@ -97,12 +98,12 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('- 1.234e2');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_MINUS, $token->type);
self::assertEquals(TokenType::T_MINUS, $token->type);
self::assertEquals('-', $token->value);
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_FLOAT, $token->type);
self::assertEquals(TokenType::T_FLOAT, $token->type);
self::assertNotEquals(-1.234e2, $token->value);
self::assertEquals(1.234e2, $token->value);
}
@@ -112,7 +113,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer("'This is a string.'");
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_STRING, $token->type);
self::assertEquals(TokenType::T_STRING, $token->type);
self::assertEquals('This is a string.', $token->value);
}
@@ -121,7 +122,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer("'abc''defg'''");
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_STRING, $token->type);
self::assertEquals(TokenType::T_STRING, $token->type);
self::assertEquals("abc'defg'", $token->value);
}
@@ -130,7 +131,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer('?1');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INPUT_PARAMETER, $token->type);
self::assertEquals(TokenType::T_INPUT_PARAMETER, $token->type);
self::assertEquals('?1', $token->value);
}
@@ -139,7 +140,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer(':name');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INPUT_PARAMETER, $token->type);
self::assertEquals(TokenType::T_INPUT_PARAMETER, $token->type);
self::assertEquals(':name', $token->value);
}
@@ -148,7 +149,7 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer(':_name');
$lexer->moveNext();
$token = $lexer->lookahead;
self::assertEquals(Lexer::T_INPUT_PARAMETER, $token->type);
self::assertEquals(TokenType::T_INPUT_PARAMETER, $token->type);
self::assertEquals(':_name', $token->value);
}
@@ -158,17 +159,17 @@ class LexerTest extends OrmTestCase
$lexer = new Lexer($dql);
$tokens = [
new Token('SELECT', Lexer::T_SELECT, 0),
new Token('u', Lexer::T_IDENTIFIER, 7),
new Token('FROM', Lexer::T_FROM, 9),
new Token('My\Namespace\User', Lexer::T_FULLY_QUALIFIED_NAME, 14),
new Token('u', Lexer::T_IDENTIFIER, 32),
new Token('WHERE', Lexer::T_WHERE, 34),
new Token('u', Lexer::T_IDENTIFIER, 40),
new Token('.', Lexer::T_DOT, 41),
new Token('name', Lexer::T_IDENTIFIER, 42),
new Token('=', Lexer::T_EQUALS, 47),
new Token("Jack O'Neil", Lexer::T_STRING, 49),
new Token('SELECT', TokenType::T_SELECT, 0),
new Token('u', TokenType::T_IDENTIFIER, 7),
new Token('FROM', TokenType::T_FROM, 9),
new Token('My\Namespace\User', TokenType::T_FULLY_QUALIFIED_NAME, 14),
new Token('u', TokenType::T_IDENTIFIER, 32),
new Token('WHERE', TokenType::T_WHERE, 34),
new Token('u', TokenType::T_IDENTIFIER, 40),
new Token('.', TokenType::T_DOT, 41),
new Token('name', TokenType::T_IDENTIFIER, 42),
new Token('=', TokenType::T_EQUALS, 47),
new Token("Jack O'Neil", TokenType::T_STRING, 49),
];
foreach ($tokens as $expected) {
@@ -186,15 +187,15 @@ class LexerTest extends OrmTestCase
public static function provideTokens(): array
{
return [
[Lexer::T_IDENTIFIER, 'u'], // one char
[Lexer::T_IDENTIFIER, 'someIdentifier'],
[Lexer::T_IDENTIFIER, 's0m31d3nt1f13r'], // including digits
[Lexer::T_IDENTIFIER, 'some_identifier'], // including underscore
[Lexer::T_IDENTIFIER, '_some_identifier'], // starts with underscore
[Lexer::T_IDENTIFIER, 'comma'], // name of a token class with value < 100 (whitebox test)
[Lexer::T_FULLY_QUALIFIED_NAME, 'Some\Class'], // DQL class reference
[Lexer::T_ALIASED_NAME, 'Some:Name'],
[Lexer::T_ALIASED_NAME, 'Some:Subclassed\Name'],
[TokenType::T_IDENTIFIER, 'u'], // one char
[TokenType::T_IDENTIFIER, 'someIdentifier'],
[TokenType::T_IDENTIFIER, 's0m31d3nt1f13r'], // including digits
[TokenType::T_IDENTIFIER, 'some_identifier'], // including underscore
[TokenType::T_IDENTIFIER, '_some_identifier'], // starts with underscore
[TokenType::T_IDENTIFIER, 'comma'], // name of a token class with value < 100 (whitebox test)
[TokenType::T_FULLY_QUALIFIED_NAME, 'Some\Class'], // DQL class reference
[TokenType::T_ALIASED_NAME, 'Some:Name'],
[TokenType::T_ALIASED_NAME, 'Some:Subclassed\Name'],
];
}
}

View File

@@ -6,9 +6,9 @@ namespace Doctrine\Tests\ORM\Query;
use Doctrine\Common\Persistence\PersistentObject;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\Models\CMS\CmsUser;
use Doctrine\Tests\OrmTestCase;
use stdClass;
@@ -124,11 +124,11 @@ class ParserTest extends OrmTestCase
* but in LexerTest.
*/
return [
[Lexer::T_WHERE, 'where'], // keyword
[Lexer::T_DOT, '.'], // token that cannot be an identifier
[Lexer::T_IDENTIFIER, 'someIdentifier'],
[Lexer::T_IDENTIFIER, 'from'], // also a terminal string (the "FROM" keyword) as in DDC-505
[Lexer::T_IDENTIFIER, 'comma'],
[TokenType::T_WHERE, 'where'], // keyword
[TokenType::T_DOT, '.'], // token that cannot be an identifier
[TokenType::T_IDENTIFIER, 'someIdentifier'],
[TokenType::T_IDENTIFIER, 'from'], // also a terminal string (the "FROM" keyword) as in DDC-505
[TokenType::T_IDENTIFIER, 'comma'],
// not even a terminal string, but the name of a constant in the Lexer (whitebox test)
];
}
@@ -137,15 +137,15 @@ class ParserTest extends OrmTestCase
public static function invalidMatches(): array
{
return [
[Lexer::T_DOT, 'ALL'], // ALL is a terminal string (reserved keyword) and also possibly an identifier
[Lexer::T_DOT, ','], // "," is a token on its own, but cannot be used as identifier
[Lexer::T_WHERE, 'WITH'], // as in DDC-3697
[Lexer::T_WHERE, '.'],
[TokenType::T_DOT, 'ALL'], // ALL is a terminal string (reserved keyword) and also possibly an identifier
[TokenType::T_DOT, ','], // "," is a token on its own, but cannot be used as identifier
[TokenType::T_WHERE, 'WITH'], // as in DDC-3697
[TokenType::T_WHERE, '.'],
// The following are qualified or aliased names and must not be accepted where only an Identifier is expected
[Lexer::T_IDENTIFIER, '\\Some\\Class'],
[Lexer::T_IDENTIFIER, 'Some\\Class'],
[Lexer::T_IDENTIFIER, 'Some:Name'],
[TokenType::T_IDENTIFIER, '\\Some\\Class'],
[TokenType::T_IDENTIFIER, 'Some\\Class'],
[TokenType::T_IDENTIFIER, 'Some:Name'],
];
}
@@ -164,7 +164,7 @@ class ParserTest extends OrmTestCase
$parser = new Parser($query);
$this->expectException(QueryException::class);
$parser->match(Lexer::T_SELECT);
$parser->match(TokenType::T_SELECT);
}
private function createParser(string $dql): Parser

View File

@@ -18,10 +18,10 @@ use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Query as ORMQuery;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\QueryException;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\TokenType;
use Doctrine\Tests\DbalTypes\NegativeToPositiveType;
use Doctrine\Tests\Models\CMS\CmsGroup;
use Doctrine\Tests\Models\CMS\CmsPhonenumber;
@@ -2186,12 +2186,12 @@ class MyAbsFunction extends FunctionNode
{
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(TokenType::T_IDENTIFIER);
$parser->match(TokenType::T_OPEN_PARENTHESIS);
$this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
}
}
/** @Entity */

View File

@@ -7,8 +7,8 @@ namespace Doctrine\Tests\ORM\Query;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\ParserResult;
use Doctrine\ORM\Query\TokenType;
use Doctrine\ORM\Query\TreeWalkerAdapter;
use PHPUnit\Framework\TestCase;
use stdClass;
@@ -33,7 +33,7 @@ class TreeWalkerAdapterTest extends TestCase
'relation' => null,
'map' => null,
'nestingLevel' => 0,
'token' => ['value' => '', 'type' => Lexer::T_NONE, 'position' => 0],
'token' => ['value' => '', 'type' => TokenType::T_NONE, 'position' => 0],
]);
}
@@ -52,7 +52,7 @@ class TreeWalkerAdapterTest extends TestCase
'relation' => null,
'map' => null,
'nestingLevel' => 0,
'token' => ['value' => '', 'type' => Lexer::T_NONE, 'position' => 0],
'token' => ['value' => '', 'type' => TokenType::T_NONE, 'position' => 0],
]);
}
};

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Order;
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
use Doctrine\ORM\Cache;
use Doctrine\ORM\Query;
@@ -22,6 +23,7 @@ use Doctrine\Tests\OrmTestCase;
use InvalidArgumentException;
use function array_filter;
use function class_exists;
use function get_class;
/**
@@ -576,7 +578,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);
@@ -593,7 +595,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);

View File

@@ -374,6 +374,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());
}
}
/**
@@ -559,6 +580,32 @@ class IndexByFieldEntity
public $fieldName;
}
/**
* @Entity
* @Table(uniqueConstraints={@UniqueConstraint(columns={"field", "anotherField"})})
*/
class GH11314Entity
{
/**
* @Column(type="integer")
* @Id
* @var int
*/
private $id;
/**
* @Column(name="field", type="string")
* @var string
*/
private $field;
/**
* @Column(name="anotherField", type="string")
* @var string
*/
private $anotherField;
}
class IncorrectIndexByFieldEntity
{
/** @var int */