Compare commits

..

66 Commits

Author SHA1 Message Date
Tom Roar Furunes
9fe8ce4bf7 Merge pull request #12373 from tomme87/12225-fix-hydration-issue
12225 Fix hydration issue when using indexBy, SQL filter, and inheritance mapping
2026-04-02 08:18:54 +02:00
Grégoire Paris
a46ff16339 Merge pull request #12414 from ahmed-bhs/docs/enum-type-mapping
Add documentation for enumType mapping with PHP backed enums
2026-04-01 00:35:46 +02:00
Grégoire Paris
94e60e4318 Merge pull request #12419 from greg0ire/backport-12222
Backport #12222
2026-04-01 00:35:01 +02:00
Grégoire Paris
f59cd4019a Add ORDER BY clause to SELECT query
The order of results is not guaranteed unless we do so, and the test can
fail in some cases:

	There was 1 failure:

	1) Doctrine\Tests\ORM\Functional\QueryTest::testToIterableWithMixedResultArbitraryJoinsScalars
	Failed asserting that two strings are equal.
	--- Expected
	+++ Actual
	@@ @@
	-'Doctrine 2'
	+'lala 2'

	/home/runner/work/orm/orm/tests/Tests/ORM/Functional/QueryTest.php:481
2026-03-29 10:33:06 +02:00
Grégoire Paris
81558a8b2a Merge pull request #12418 from doctrine/dependabot/github_actions/2.20.x/codecov/codecov-action-6
Bump codecov/codecov-action from 5 to 6
2026-03-29 10:10:19 +02:00
dependabot[bot]
e431ee113d Bump codecov/codecov-action from 5 to 6
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-29 06:12:22 +00:00
Ahmed EBEN HASSINE
de4ec208fd Mention PostgreSQL jsonb as a reason to favor json over simple_array 2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE 脳の流れ
df014a74c9 Update docs/en/reference/basic-mapping.rst
Co-authored-by: Grégoire Paris <postmaster@greg0ire.fr>
2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE
2f46d95028 Add nullable enums, default values, query usage and XML mapping to enum docs
Cover additional enumType behaviors: nullable columns, database default
values with enum cases, usage in DQL/QueryBuilder/findBy, and XML
mapping syntax with enum-type attribute.
2026-03-26 08:52:24 +01:00
Ahmed EBEN HASSINE
3fda5629f6 Add documentation for enumType mapping with PHP backed enums
The enumType option on #[Column] was barely mentioned in the docs and
had no dedicated section. This adds a complete reference covering
single-value columns, collection types (json, simple_array), automatic
type inference, validation behavior, and platform compatibility.
2026-03-26 08:52:00 +01:00
Grégoire Paris
6b273234d6 Merge pull request #12407 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/composer-lint.yml-14.0.0
Bump doctrine/.github/.github/workflows/composer-lint.yml from 13.1.0 to 14.0.0
2026-03-26 08:08:34 +01:00
Grégoire Paris
d2266c7d0c Merge pull request #12408 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/documentation.yml-14.0.0
Bump doctrine/.github/.github/workflows/documentation.yml from 13.1.0 to 14.0.0
2026-03-22 13:53:32 +00:00
Grégoire Paris
eb0485869a Merge pull request #12406 from doctrine/dependabot/github_actions/2.20.x/ramsey/composer-install-4
Bump ramsey/composer-install from 3 to 4
2026-03-22 13:52:17 +00:00
Grégoire Paris
8372d600c6 Merge pull request #12409 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/coding-standards.yml-14.0.0
Bump doctrine/.github/.github/workflows/coding-standards.yml from 13.1.0 to 14.0.0
2026-03-22 08:41:51 +00:00
Grégoire Paris
dde1d71b34 Merge pull request #12410 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github/dot-github/workflows/release-on-milestone-closed.yml-14.0.0
Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml from 13.1.0 to 14.0.0
2026-03-22 08:38:30 +00:00
dependabot[bot]
0d03255061 Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/release-on-milestone-closed.yml
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:28 +00:00
dependabot[bot]
75a18090d9 Bump doctrine/.github/.github/workflows/coding-standards.yml
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/coding-standards.yml
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:27 +00:00
dependabot[bot]
77ffd2ab68 Bump doctrine/.github/.github/workflows/documentation.yml
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/documentation.yml
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:25 +00:00
dependabot[bot]
401cc06d71 Bump doctrine/.github/.github/workflows/composer-lint.yml
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 13.1.0 to 14.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.1.0...14.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/composer-lint.yml
  dependency-version: 14.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:22 +00:00
dependabot[bot]
5029b193ee Bump ramsey/composer-install from 3 to 4
Bumps [ramsey/composer-install](https://github.com/ramsey/composer-install) from 3 to 4.
- [Release notes](https://github.com/ramsey/composer-install/releases)
- [Commits](https://github.com/ramsey/composer-install/compare/v3...v4)

---
updated-dependencies:
- dependency-name: ramsey/composer-install
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-22 06:12:19 +00:00
Alexander M. Turek
6068b61a0d Make the data provider static 2026-03-12 09:24:03 +01:00
Alexander M. Turek
00024f7d88 Raise proper exception for invalid arguments in Base::add() (#12394) 2026-03-12 09:05:27 +01:00
Alexander M. Turek
b2faba62b7 Fix code style (#12395) 2026-03-11 16:51:15 +01:00
dependabot[bot]
da426a0036 Bump actions/upload-artifact from 6 to 7 (#12387)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 00:34:23 +01:00
dependabot[bot]
1891a76f13 Bump actions/download-artifact from 7 to 8 (#12386)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 00:33:50 +01:00
Grégoire Paris
026f5bfe1b Merge pull request #12350 from greg0ire/missing-order-by
Add missing ORDER BY clause
2026-01-18 10:41:46 +01:00
Grégoire Paris
0b0f2f4d86 Add missing ORDER BY clause
This causes transient failures with PostgreSQL. Order is not guaranteed.
2026-01-17 13:39:44 +01:00
Grégoire Paris
d2418ab074 Merge pull request #12344 from greg0ire/update-baseline
Update PHPStan baseline
2026-01-15 23:38:15 +01:00
Grégoire Paris
39a05e31c9 Update PHPStan baseline
This is caused by the release of doctrine/collections 2.7.0. The error
message is a bit shorter now.
2026-01-15 20:13:05 +01:00
Grégoire Paris
3b8c23c51d Merge pull request #12336 from greg0ire/dmwydo
Stop mocking EventManager
2026-01-08 07:19:16 +01:00
Grégoire Paris
60d4ea694a Stop mocking EventManager
It is defined outside this repository, so let us not mock what we do not
own.
2026-01-07 21:52:47 +01:00
Grégoire Paris
0f8730a6e5 Merge pull request #12331 from greg0ire/a-the
Fix grammatical errors
2025-12-24 11:39:26 +01:00
Grégoire Paris
0aeddd0592 Fix grammatical errors 2025-12-20 15:34:16 +01:00
Sadetdin EYILI
492745d710 docs: add xml example for Single Table Inheritance mapping (#12169) 2025-12-17 10:40:06 +01:00
dependabot[bot]
67419cf951 Bump actions/download-artifact from 6 to 7 (#12321) 2025-12-15 07:56:29 +01:00
dependabot[bot]
1237f5c909 Bump actions/upload-artifact from 5 to 6 (#12322) 2025-12-15 07:55:38 +01:00
dependabot[bot]
dcdd46251e Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12315)
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/release-on-milestone-closed.yml
  dependency-version: 13.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:23:27 +01:00
dependabot[bot]
3d98b43561 Bump doctrine/.github/.github/workflows/composer-lint.yml (#12317)
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/composer-lint.yml
  dependency-version: 13.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:22:42 +01:00
dependabot[bot]
9f3f70944a Bump doctrine/.github/.github/workflows/coding-standards.yml (#12316)
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/coding-standards.yml
  dependency-version: 13.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:22:15 +01:00
dependabot[bot]
05e07c0ae0 Bump doctrine/.github/.github/workflows/documentation.yml (#12318)
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 13.0.0 to 13.1.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/13.0.0...13.1.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/documentation.yml
  dependency-version: 13.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 10:21:12 +01:00
Grégoire Paris
fea42ab984 Merge pull request #12299 from alexislefebvre/chore-show-parameters-in-name-of-CI-jobs
chore: show parameters in name of CI jobs
2025-11-30 11:14:14 +01:00
Alexis Lefebvre
01fd55e9ea chore: show parameters in name of CI jobs 2025-11-29 23:12:27 +01:00
Alexander M. Turek
87f1ba74e0 Support Symfony Console 8 (#12300) 2025-11-29 15:03:56 +01:00
dependabot[bot]
ab148d3d9d Bump doctrine/.github/.github/workflows/composer-lint.yml (#12288)
Bumps [doctrine/.github/.github/workflows/composer-lint.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/composer-lint.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:25:08 +01:00
dependabot[bot]
3924c38fab Bump doctrine/.github/.github/workflows/documentation.yml (#12289)
Bumps [doctrine/.github/.github/workflows/documentation.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/documentation.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:24:42 +01:00
dependabot[bot]
9814078a2c Bump doctrine/.github/.github/workflows/coding-standards.yml (#12290)
Bumps [doctrine/.github/.github/workflows/coding-standards.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/coding-standards.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:24:14 +01:00
dependabot[bot]
6de5684fd9 Bump doctrine/.github/.github/workflows/release-on-milestone-closed.yml (#12291)
Bumps [doctrine/.github/.github/workflows/release-on-milestone-closed.yml](https://github.com/doctrine/.github) from 12.2.0 to 13.0.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/v12.2.0...13.0.0)

---
updated-dependencies:
- dependency-name: doctrine/.github/.github/workflows/release-on-milestone-closed.yml
  dependency-version: 13.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 09:23:46 +01:00
dependabot[bot]
c142503a52 Bump actions/checkout from 5 to 6 (#12292) 2025-11-24 07:27:27 +01:00
Grégoire Paris
15537bc218 Merge pull request #12285 from HypeMC/fix-is-foreign-key-composite
Fix check for composite foreign key
2025-11-21 19:29:05 +01:00
HypeMC
bc95c7c08d Fix check for composite foreign key 2025-11-21 07:27:53 +01:00
Grégoire Paris
c1becd54e6 Merge pull request #12281 from greg0ire/document-default-expressions
Fix documentation about default values
2025-11-20 17:58:48 +01:00
Grégoire Paris
e4d7df29c2 Fix documentation about default values
Saying it is not possible to get Doctrine to use the `DEFAULT` SQL
keyword is wrong.
2025-11-19 23:17:53 +01:00
Adrian Brajkovic
e38278bfca Fix eager fetch composite foreign key (#11397)
I think #11289 did not completely fix problem for eager fetch.
Change in that PR checked if primary key of target class is composite but that does not matter when loading collection by foreign key.
It should check if foreign key on target class is composite.

Fix from that PR did not work for me because i had entity with regular autogenerated id (single column), but foreign key referenced entity with composite primary key, like SecondLevelWithoutCompositePrimaryKey in this PR.

Checking if foreign key is composite fixed the problem for me.
2025-11-18 21:40:50 +01:00
Grégoire Paris
5bff0919a7 Merge pull request #12254 from elliotbruneel/fix/empty-array-query
fix: handling of empty array in SQL condition generation
2025-11-10 14:35:45 +01:00
Elliot Bruneel
9ef0f5301b fix: update SQL condition for empty array to 1=0 instead of IN (NULL) 2025-11-10 10:44:48 +01:00
Elliot Bruneel
4989ca6f15 test: add test for finding by nullable field with empty array 2025-11-05 10:03:09 +01:00
Elliot Bruneel
32d1e97ce7 chore: improve empty array check in SQL condition generation 2025-11-05 09:51:33 +01:00
Grégoire Paris
ca8147b148 Merge pull request #12257 from doctrine/dependabot/github_actions/2.20.x/doctrine/dot-github-12.2.0
Bump doctrine/.github from 12.1.0 to 12.2.0
2025-11-03 09:32:58 +01:00
dependabot[bot]
c8ebea77f0 Bump doctrine/.github from 12.1.0 to 12.2.0
Bumps [doctrine/.github](https://github.com/doctrine/.github) from 12.1.0 to 12.2.0.
- [Release notes](https://github.com/doctrine/.github/releases)
- [Commits](https://github.com/doctrine/.github/compare/12.1.0...v12.2.0)

---
updated-dependencies:
- dependency-name: doctrine/.github
  dependency-version: 12.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-03 06:11:36 +00:00
Elliot Bruneel
23f22860f1 chore: update phpstan version and regenerate baseline 2025-10-31 09:04:03 +01:00
Elliot Bruneel
b24586b1b5 fix: handling of empty array in SQL condition generation 2025-10-30 17:31:04 +01:00
Grégoire Paris
fe5ee705db Merge pull request #12247 from greg0ire/composer-lint
Setup composer lint workflow
2025-10-29 07:28:39 +01:00
Grégoire Paris
0511a9f790 Merge pull request #12248 from greg0ire/remove-mailing-list
Drop link to mailing list
2025-10-28 22:06:18 +01:00
Grégoire Paris
0e3d5e8c82 Drop link to mailing list
Who still uses this? Not me, that's for sure!
2025-10-28 21:20:15 +01:00
Grégoire Paris
72ffb3bfbf Remove archive exclude list
It is not up-to-date, and we use .gitattributes for this purpose.
2025-10-28 21:04:43 +01:00
Grégoire Paris
2e9a1adc23 Setup composer lint workflow 2025-10-28 21:03:24 +01:00
72 changed files with 913 additions and 271 deletions

View File

@@ -24,4 +24,4 @@ on:
jobs:
coding-standards:
uses: "doctrine/.github/.github/workflows/coding-standards.yml@12.1.0"
uses: "doctrine/.github/.github/workflows/coding-standards.yml@14.0.0"

20
.github/workflows/composer-lint.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: "Composer Lint"
on:
pull_request:
branches:
- "*.x"
paths:
- ".github/workflows/composer-lint.yml"
- "composer.json"
push:
branches:
- "*.x"
paths:
- ".github/workflows/composer-lint.yml"
- "composer.json"
jobs:
composer-lint:
name: "Composer Lint"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@14.0.0"

View File

@@ -1,4 +1,4 @@
name: "CI"
name: "CI: PHPUnit"
on:
pull_request:
@@ -27,7 +27,14 @@ env:
jobs:
phpunit-smoke-check:
name: "PHPUnit with SQLite"
name: >
SQLite -
${{ format('PHP {0} - DBAL {1} - ext. {2} - proxy {3}',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø',
matrix.proxy || 'Ø'
) }}
runs-on: "ubuntu-22.04"
strategy:
@@ -65,7 +72,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -82,7 +89,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -99,14 +106,20 @@ jobs:
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
with:
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.proxy }}-coverage"
path: "coverage*.xml"
phpunit-postgres:
name: "PHPUnit with PostgreSQL"
name: >
${{ format('PostgreSQL {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.postgres-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
@@ -149,7 +162,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -166,7 +179,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -174,14 +187,20 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
with:
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
path: "coverage.xml"
phpunit-mariadb:
name: "PHPUnit with MariaDB"
name: >
${{ format('MariaDB {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.mariadb-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
@@ -221,7 +240,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -238,7 +257,7 @@ jobs:
extensions: "${{ matrix.extension }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -246,14 +265,20 @@ jobs:
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
- name: "Upload coverage file"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
with:
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage.xml"
phpunit-mysql:
name: "PHPUnit with MySQL"
name: >
${{ format('MySQL {0} - PHP {1} - DBAL {2} - ext. {3}',
matrix.mysql-version || 'Ø',
matrix.php-version || 'Ø',
matrix.dbal-version || 'Ø',
matrix.extension || 'Ø'
) }}
runs-on: "ubuntu-22.04"
needs: "phpunit-smoke-check"
@@ -293,7 +318,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -310,7 +335,7 @@ jobs:
if: "${{ matrix.dbal-version != 'default' }}"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
composer-options: "--ignore-platform-req=php+"
@@ -325,14 +350,19 @@ jobs:
ENABLE_SECOND_LEVEL_CACHE: 1
- name: "Upload coverage files"
uses: "actions/upload-artifact@v5"
uses: "actions/upload-artifact@v7"
with:
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
path: "coverage*.xml"
phpunit-lower-php-versions:
name: "PHPUnit with SQLite"
name: >
SQLite -
${{ format('PHP {0} - deps {1}',
matrix.php-version || 'Ø',
matrix.deps || 'Ø'
) }}
runs-on: "ubuntu-22.04"
strategy:
@@ -345,7 +375,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -356,7 +386,7 @@ jobs:
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
dependency-versions: "${{ matrix.deps }}"
@@ -377,17 +407,17 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
- name: "Download coverage files"
uses: "actions/download-artifact@v6"
uses: "actions/download-artifact@v8"
with:
path: "reports"
- name: "Upload to Codecov"
uses: "codecov/codecov-action@v5"
uses: "codecov/codecov-action@v6"
with:
directory: reports
env:

View File

@@ -17,4 +17,4 @@ on:
jobs:
documentation:
name: "Documentation"
uses: "doctrine/.github/.github/workflows/documentation.yml@12.1.0"
uses: "doctrine/.github/.github/workflows/documentation.yml@14.0.0"

View File

@@ -36,7 +36,7 @@ jobs:
steps:
- name: "Checkout"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
with:
fetch-depth: 2
@@ -48,7 +48,7 @@ jobs:
ini-values: "zend.assertions=1, apc.enable_cli=1"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
- name: "Run PHPBench"
run: "vendor/bin/phpbench run --report=default"

View File

@@ -7,7 +7,7 @@ on:
jobs:
release:
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@12.1.0"
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@14.0.0"
secrets:
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}

View File

@@ -40,7 +40,7 @@ jobs:
steps:
- name: "Checkout code"
uses: "actions/checkout@v5"
uses: "actions/checkout@v6"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -56,7 +56,7 @@ jobs:
run: "composer require doctrine/persistence ^$([ ${{ matrix.persistence-version }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
- name: "Install dependencies with Composer"
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@v4"
with:
dependency-versions: "highest"

View File

@@ -1248,7 +1248,7 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio
## Scalar mappings can now be omitted from DQL result
You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore.
You are now allowed to mark scalar SELECT expressions as HIDDEN and they are not hydrated anymore.
Example:
SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10

View File

@@ -1,32 +1,39 @@
{
"name": "doctrine/orm",
"type": "library",
"description": "Object-Relational-Mapper for PHP",
"keywords": ["orm", "database"],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
"type": "library",
"keywords": [
"orm",
"database"
],
"scripts": {
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
"sort-packages": true
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"homepage": "https://www.doctrine-project.org/projects/orm.html",
"require": {
"php": "^7.1 || ^8.0",
"composer-runtime-api": "^2",
"ext-ctype": "*",
"composer-runtime-api": "^2",
"doctrine/cache": "^1.12.1 || ^2.1.1",
"doctrine/collections": "^1.5 || ^2.1",
"doctrine/common": "^3.0.3",
@@ -38,7 +45,7 @@
"doctrine/lexer": "^2 || ^3",
"doctrine/persistence": "^2.4 || ^3",
"psr/cache": "^1 || ^2 || ^3",
"symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0",
"symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
"symfony/polyfill-php72": "^1.23",
"symfony/polyfill-php80": "^1.16"
},
@@ -47,13 +54,13 @@
"doctrine/coding-standard": "^9.0.2 || ^14.0",
"phpbench/phpbench": "^0.16.10 || ^1.0",
"phpstan/extension-installer": "~1.1.0 || ^1.4",
"phpstan/phpstan": "~1.4.10 || 2.1.22",
"phpstan/phpstan": "~1.4.10 || 2.1.23",
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
"psr/log": "^1 || ^2 || ^3",
"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"
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"conflict": {
"doctrine/annotations": "<1.13 || >= 3.0"
@@ -64,17 +71,29 @@
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
},
"autoload": {
"psr-4": { "Doctrine\\ORM\\": "src" }
"psr-4": {
"Doctrine\\ORM\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Tests\\": "tests/Tests",
"Doctrine\\Performance\\": "tests/Performance",
"Doctrine\\StaticAnalysis\\": "tests/StaticAnalysis",
"Doctrine\\Performance\\": "tests/Performance"
"Doctrine\\Tests\\": "tests/Tests"
}
},
"bin": ["bin/doctrine"],
"archive": {
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
"bin": [
"bin/doctrine"
],
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"phpstan/extension-installer": true
},
"sort-packages": true
},
"scripts": {
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
}
}

View File

@@ -13,7 +13,6 @@ If this documentation is not helping to answer questions you have about
Doctrine ORM don't panic. You can get help from different sources:
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_

View File

@@ -240,7 +240,7 @@ Here is a complete list of ``Column``s attributes (all optional):
- ``insertable`` (default: ``true``): Whether the column should be inserted.
- ``updatable`` (default: ``true``): Whether the column should be updated.
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into. See :ref:`reference-enum-mapping`.
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
(applies only for decimal column),
which is the maximum number of digits that are stored for the values.
@@ -254,6 +254,21 @@ Here is a complete list of ``Column``s attributes (all optional):
- ``options``: Key-value pairs of options that get passed
to the underlying database platform when generating DDL statements.
Specifying default values
~~~~~~~~~~~~~~~~~~~~~~~~~
While it is possible to specify default values for properties in your
PHP class, Doctrine also allows you to specify default values for
database columns using the ``default`` key in the ``options`` array of
the ``Column`` attribute.
.. configuration-block::
.. literalinclude:: basic-mapping/DefaultValues.php
:language: attribute
.. literalinclude:: basic-mapping/default-values.xml
:language: xml
.. _reference-php-mapping-types:
PHP Types Mapping
@@ -296,6 +311,160 @@ and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
:doc:`Read more about TypedFieldMapper <typedfieldmapper>`.
.. _reference-enum-mapping:
Mapping PHP Enums
-----------------
.. versionadded:: 2.11
Doctrine natively supports mapping PHP backed enums to database columns.
A backed enum is a PHP enum that the same scalar type (``string`` or ``int``)
assigned to each case. Doctrine stores the scalar value in the database and
converts it back to the enum instance when hydrating the entity.
Using ``enumType`` provides three main benefits:
- **Automatic conversion**: Doctrine handles the conversion in both directions
transparently. When loading an entity, scalar values from the database are
converted into enum instances. When persisting, enum instances are reduced
to their scalar ``->value`` before being sent to the database.
- **Type-safety**: Entity properties contain enum instances directly. Your
getters return ``Suit`` instead of ``string``, removing the need to call
``Suit::from()`` manually.
- **Validation**: When a database value does not match any enum case, Doctrine
throws a ``MappingException`` during hydration instead of silently returning
an invalid value.
This feature works with all database platforms supported by Doctrine (MySQL,
PostgreSQL, SQLite, etc.) as it relies on standard column types (``string``,
``integer``, ``json``, ``simple_array``) rather than any vendor-specific enum
type.
.. note::
This is unrelated to the MySQL-specific ``ENUM`` column type covered in
:doc:`the MySQL Enums cookbook entry </cookbook/mysql-enums>`.
Defining an Enum
~~~~~~~~~~~~~~~~
.. literalinclude:: basic-mapping/Suit.php
:language: php
Only backed enums (``string`` or ``int``) are supported. Unit enums (without
a scalar value) cannot be mapped.
Single-Value Columns
~~~~~~~~~~~~~~~~~~~~
Use the ``enumType`` option on ``#[Column]`` to map a property to a backed enum.
The underlying database column stores the enum's scalar value (``string`` or ``int``).
.. literalinclude:: basic-mapping/EnumMapping.php
:language: php
When the PHP property is typed with the enum class, Doctrine automatically
infers the appropriate column type (``string`` for string-backed enums,
``integer`` for int-backed enums) and sets ``enumType``. You can also specify
the column ``type`` explicitly.
Storing Collections of Enums
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can store multiple enum values in a single column by combining ``enumType``
with a collection column type: ``json`` or ``simple_array``.
.. note::
Automatic type inference does not apply to collection columns. When the
PHP property is typed as ``array``, Doctrine cannot detect the enum class.
You must specify both ``type`` and ``enumType`` explicitly.
.. literalinclude:: basic-mapping/EnumCollectionMapping.php
:language: php
With ``json``, the values are stored as a JSON array (e.g. ``["hearts","spades"]``).
With ``simple_array``, the values are stored as a comma-separated string
(e.g. ``hearts,spades``).
In both cases, Doctrine converts each element to and from the enum
automatically during hydration and persistence.
.. tip::
Use ``json`` when enum values may contain commas, when you need to store
int-backed enums (as it preserves value types), when the column also
stores complex/nested data structures, or when you want to query individual
values using database-native JSON operators (e.g. PostgreSQL ``jsonb``).
Prefer ``simple_array`` for a compact, human-readable storage of
string-backed enums whose values do not contain commas.
+-------------------+-----------------------------+-------------------------------+
| Column type | Database storage | PHP type |
+===================+=============================+===============================+
| ``string`` | ``hearts`` | ``Suit`` |
+-------------------+-----------------------------+-------------------------------+
| ``integer`` | ``1`` | ``Priority`` |
+-------------------+-----------------------------+-------------------------------+
| ``json`` | ``["hearts","spades"]`` | ``array<Suit>`` |
+-------------------+-----------------------------+-------------------------------+
| ``simple_array`` | ``hearts,spades`` | ``array<Suit>`` |
+-------------------+-----------------------------+-------------------------------+
Nullable Enums
~~~~~~~~~~~~~~
Enum columns can be nullable. When the database value is ``NULL``, Doctrine
preserves it as ``null`` without triggering any validation error.
.. code-block:: php
<?php
#[ORM\Column(type: 'string', nullable: true, enumType: Suit::class)]
private Suit|null $suit = null;
Default Values
~~~~~~~~~~~~~~
You can specify a database-level default using an enum case directly in the
column options:
.. code-block:: php
<?php
#[ORM\Column(options: ['default' => Suit::Hearts])]
public Suit $suit;
Using Enums in Queries
~~~~~~~~~~~~~~~~~~~~~~
Enum instances can be used directly as parameters in DQL, QueryBuilder, and
repository methods. Doctrine converts them to their scalar value automatically.
.. code-block:: php
<?php
// QueryBuilder
$qb = $em->createQueryBuilder();
$qb->select('c')
->from(Card::class, 'c')
->where('c.suit = :suit')
->setParameter('suit', Suit::Clubs);
// Repository
$cards = $em->getRepository(Card::class)->findBy(['suit' => Suit::Clubs]);
XML Mapping
~~~~~~~~~~~
When using XML mapping, the ``enum-type`` attribute is used on ``<field>``
elements:
.. code-block:: xml
<field name="suit" type="string" enum-type="App\Entity\Suit" />
.. _reference-mapping-types:
Doctrine Mapping Types

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
#[Entity]
class Message
{
#[Column(options: ['default' => 'Hello World!'])]
private string $text;
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Player
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
/** @var list<Suit> */
#[ORM\Column(type: 'json', enumType: Suit::class)]
private array $favouriteSuits = [];
/** @var list<Suit> */
#[ORM\Column(type: 'simple_array', enumType: Suit::class)]
private array $allowedSuits = [];
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Card
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private int $id;
#[ORM\Column(enumType: Suit::class)]
private Suit $suit;
}

View File

@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Entity;
enum Suit: string
{
case Hearts = 'hearts';
case Diamonds = 'diamonds';
case Clubs = 'clubs';
case Spades = 'spades';
}

View File

@@ -0,0 +1,9 @@
<doctrine-mapping>
<entity name="Message">
<field name="text">
<options>
<option name="default">Hello World!</option>
</options>
</field>
</entity>
</doctrine-mapping>

View File

@@ -18,30 +18,6 @@ In your mapping configuration, the column definition (for example, the
the ``charset`` and ``collation``. The default values are ``utf8`` and
``utf8_unicode_ci``, respectively.
Entity Classes
--------------
How can I add default values to a column?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL.
This is not necessary however, you can just use your class properties as default values. These are then used
upon insert:
.. code-block:: php
class User
{
private const STATUS_DISABLED = 0;
private const STATUS_ENABLED = 1;
private string $algorithm = "sha1";
/** @var self::STATUS_* */
private int $status = self::STATUS_DISABLED;
}
.
Mapping
-------

View File

@@ -232,6 +232,23 @@ Example:
// ...
}
.. code-block:: xml
<doctrine-mapping>
<entity name="MyProject\Model\Person" inheritance-type="SINGLE_TABLE">
<discriminator-column name="discr" type="string" />
<discriminator-map>
<discriminator-mapping value="person" class="MyProject\Model\Person"/>
<discriminator-mapping value="employee" class="MyProject\Model\Employee"/>
</discriminator-map>
</entity>
</doctrine-mapping>
<doctrine-mapping>
<entity name="MyProject\Model\Employee">
</entity>
</doctrine-mapping>
.. code-block:: yaml
MyProject\Model\Person:

View File

@@ -133,7 +133,7 @@ Caching mode
* Read Write cache employs locks before update/delete.
* Use if data needs to be updated.
* Slowest strategy.
* To use it a the cache region implementation must support locking.
* To use it the cache region implementation must support locking.
Built-in cached persisters

View File

@@ -22,7 +22,7 @@ have to register them yourself.
All the commands of the Doctrine Console require access to the
``EntityManager``. You have to inject it into the console application.
Here is an example of a the project-specific ``bin/doctrine`` binary.
Here is an example of a project-specific ``bin/doctrine`` binary.
.. code-block:: php

View File

@@ -859,7 +859,7 @@ parameters:
path: src/EntityRepository.php
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\>&Doctrine\\Common\\Collections\\Selectable\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
identifier: return.type
count: 1
path: src/EntityRepository.php
@@ -1056,6 +1056,12 @@ parameters:
count: 1
path: src/Internal/HydrationCompleteHandler.php
-
message: '#^Offset int\|null might not exist on array\<int, object\>\.$#'
identifier: offsetAccess.notFound
count: 1
path: src/Internal/StronglyConnectedComponents.php
-
message: '#^Property Doctrine\\ORM\\Internal\\StronglyConnectedComponents\:\:\$representingNodes \(array\<int, object\>\) does not accept array\<int\|string, object\>\.$#'
identifier: assign.propertyType
@@ -3561,12 +3567,6 @@ parameters:
count: 1
path: src/Query/Exec/SingleTableDeleteUpdateExecutor.php
-
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Andx\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
identifier: property.phpDocType
count: 1
path: src/Query/Expr/Andx.php
-
message: '#^Cannot cast object\|string to string\.$#'
identifier: cast.string
@@ -3579,22 +3579,10 @@ parameters:
count: 1
path: src/Query/Expr/Func.php
-
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Orx\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
identifier: property.phpDocType
count: 1
path: src/Query/Expr/Orx.php
-
message: '#^PHPDoc type array\<string\> of property Doctrine\\ORM\\Query\\Expr\\Select\:\:\$allowedClasses is not covariant with PHPDoc type list\<class\-string\> of overridden property Doctrine\\ORM\\Query\\Expr\\Base\:\:\$allowedClasses\.$#'
identifier: property.phpDocType
count: 1
path: src/Query/Expr/Select.php
-
message: '#^Property Doctrine\\ORM\\Query\\Filter\\SQLFilter\:\:\$parameters \(array\<string, array\{type\: string, value\: mixed, is_list\: bool\}\>\) does not accept non\-empty\-array\<string, array\{value\: mixed, type\: int\|string, is_list\: bool\}\>\.$#'
identifier: assign.propertyType
count: 1
count: 2
path: src/Query/Filter/SQLFilter.php
-
@@ -4173,12 +4161,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/ConvertMappingCommand.php
-
message: '#^Parameter \#2 \$destPath of method Doctrine\\ORM\\Tools\\Console\\Command\\ConvertMappingCommand\:\:getExporter\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/ConvertMappingCommand.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
@@ -4203,12 +4185,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/GenerateEntitiesCommand.php
-
message: '#^Parameter \#2 \$outputDirectory of method Doctrine\\ORM\\Tools\\EntityGenerator\:\:generate\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateEntitiesCommand.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
identifier: property.notFound
@@ -4227,12 +4203,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Parameter \#2 \$proxyDir of method Doctrine\\ORM\\Proxy\\ProxyFactory\:\:generateProxyClasses\(\) expects string\|null, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateProxiesCommand.php
-
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$customRepositoryClassName\.$#'
identifier: property.notFound
@@ -4251,12 +4221,6 @@ parameters:
count: 1
path: src/Tools/Console/Command/GenerateRepositoriesCommand.php
-
message: '#^Parameter \#2 \$outputDirectory of method Doctrine\\ORM\\Tools\\EntityRepositoryGenerator\:\:writeEntityRepositoryClass\(\) expects string, string\|false given\.$#'
identifier: argument.type
count: 1
path: src/Tools/Console/Command/GenerateRepositoriesCommand.php
-
message: '#^Method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:formatMappings\(\) has parameter \$propertyMappings with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue

View File

@@ -33,6 +33,10 @@ parameters:
message: '/^Instanceof between Doctrine\\DBAL\\Platforms\\AbstractPlatform and Doctrine\\DBAL\\Platforms\\MySQLPlatform will always evaluate to false\.$/'
path: src/Utility/LockSqlHelper.php
# Compatibility with Collections 1
-
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\>&Doctrine\\Common\\Collections\\Selectable\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
# Forward compatibility with Collections 3
-
message: '#^Parameter \$order of anonymous function has invalid type Doctrine\\Common\\Collections\\Order\.$#'

View File

@@ -1375,7 +1375,7 @@ abstract class AbstractQuery
}
/**
* Executes the query and returns a the resulting Statement object.
* Executes the query and returns the resulting Statement object.
*
* @return Result|int The executed database statement that holds
* the results, or an integer indicating how

View File

@@ -1113,7 +1113,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
if ($flag && ! trait_exists(LazyGhostTrait::class)) {
throw new LogicException(
'Lazy ghost objects cannot be enabled because the "symfony/var-exporter" library'
. ' version 6.2 or higher is not installed. Please run "composer require symfony/var-exporter:^6.2".'
. ' version 6.2 or 7 is not installed. Please run "composer require symfony/var-exporter:^6.4".'
);
}

View File

@@ -280,7 +280,7 @@ class MappingException extends ORMException
*/
public static function joinTableRequired($fieldName)
{
return new self(sprintf("The mapping of field '%s' requires an the 'joinTable' attribute.", $fieldName));
return new self(sprintf("The mapping of field '%s' requires the 'joinTable' attribute.", $fieldName));
}
/**

View File

@@ -45,13 +45,21 @@ abstract class AbstractEntityInheritancePersister extends BasicEntityPersister
{
$tableAlias = $alias === 'r' ? '' : $alias;
$fieldMapping = $class->fieldMappings[$field];
$columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']);
$sql = sprintf(
'%s.%s',
$this->getSQLTableAlias($class->name, $tableAlias),
$this->quoteStrategy->getColumnName($field, $class, $this->platform)
);
$columnAlias = null;
if ($this->currentPersisterContext->rsm->hasColumnAliasByField($alias, $field)) {
$columnAlias = $this->currentPersisterContext->rsm->getColumnAliasByField($alias, $field);
}
if ($columnAlias === null) {
$columnAlias = $this->getSQLColumnAlias($fieldMapping['columnName']);
}
$this->currentPersisterContext->rsm->addFieldResult($alias, $columnAlias, $field, $class->name);
if (isset($fieldMapping['requireSQLConversion'])) {

View File

@@ -1753,6 +1753,11 @@ class BasicEntityPersister implements EntityPersister
$value = [$value];
}
if ($value === []) {
$selectedColumns[] = '1=0';
continue;
}
$nullKeys = array_keys($value, null, true);
$nonNullValues = array_diff_key($value, array_flip($nullKeys));

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\Expr;
use Stringable;
/**
* Expression class for building DQL and parts.
*
@@ -14,7 +16,7 @@ class Andx extends Composite
/** @var string */
protected $separator = ' AND ';
/** @var string[] */
/** @var list<class-string<Stringable>> */
protected $allowedClasses = [
Comparison::class,
Func::class,

View File

@@ -12,6 +12,7 @@ use function get_class;
use function get_debug_type;
use function implode;
use function in_array;
use function is_object;
use function is_string;
use function sprintf;
@@ -31,7 +32,7 @@ abstract class Base
/** @var string */
protected $postSeparator = ')';
/** @var list<class-string> */
/** @var list<class-string<Stringable>> */
protected $allowedClasses = [];
/** @var list<string|Stringable> */
@@ -59,7 +60,7 @@ abstract class Base
}
/**
* @param mixed $arg
* @param string|Stringable|null $arg
*
* @return $this
*
@@ -69,7 +70,8 @@ abstract class Base
{
if ($arg !== null && (! $arg instanceof self || $arg->count() > 0)) {
// If we decide to keep Expr\Base instances, we can use this check
if (! is_string($arg) && ! in_array(get_class($arg), $this->allowedClasses, true)) {
// @phpstan-ignore function.alreadyNarrowedType (input validation)
if (! is_string($arg) && ! (is_object($arg) && in_array(get_class($arg), $this->allowedClasses, true))) {
throw new InvalidArgumentException(sprintf(
"Expression of type '%s' not allowed in this context.",
get_debug_type($arg)

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\Expr;
use Stringable;
/**
* Expression class for building DQL OR clauses.
*
@@ -14,7 +16,7 @@ class Orx extends Composite
/** @var string */
protected $separator = ' OR ';
/** @var string[] */
/** @var list<class-string<Stringable>> */
protected $allowedClasses = [
Comparison::class,
Func::class,

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Doctrine\ORM\Query\Expr;
use Stringable;
/**
* Expression class for building DQL select statements.
*
@@ -17,7 +19,7 @@ class Select extends Base
/** @var string */
protected $postSeparator = '';
/** @var string[] */
/** @var list<class-string<Stringable>> */
protected $allowedClasses = [Func::class];
/** @phpstan-var list<string|Func> */

View File

@@ -356,6 +356,10 @@ class ResultSetMapping
public function hasColumnAliasByField(string $alias, string $fieldName): bool
{
if (! isset($this->aliasMap[$alias])) {
return false;
}
$declaringClass = $this->aliasMap[$alias];
return isset($this->columnAliasMappings[$declaringClass][$alias][$fieldName]);

View File

@@ -18,11 +18,12 @@ trait ApplicationCompatibility
{
private static function addCommandToApplication(Application $application, Command $command): ?Command
{
// @phpstan-ignore function.alreadyNarrowedType (This method did not exist before Symfony 7.4)
if (method_exists(Application::class, 'addCommand')) {
// @phpstan-ignore method.notFound (This method will be added in Symfony 7.4)
return $application->addCommand($command);
}
// @phpstan-ignore method.notFound
return $application->add($command);
}
}

View File

@@ -23,8 +23,7 @@ class CollectionRegionCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:region:collection')
->setDescription('Clear a second-level cache collection region')

View File

@@ -23,8 +23,7 @@ class EntityRegionCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:region:entity')
->setDescription('Clear a second-level cache entity region')

View File

@@ -21,8 +21,7 @@ class MetadataCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:metadata')
->setDescription('Clear all metadata cache of the various cache drivers')

View File

@@ -30,8 +30,7 @@ class QueryCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:query')
->setDescription('Clear all query cache of the various cache drivers')

View File

@@ -23,8 +23,7 @@ class QueryRegionCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:region:query')
->setDescription('Clear a second-level cache query region')

View File

@@ -32,8 +32,7 @@ class ResultCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:clear-cache:result')
->setDescription('Clear all result cache of the various cache drivers')

View File

@@ -75,8 +75,7 @@ class ConvertDoctrine1SchemaCommand extends Command
$this->metadataExporter = $metadataExporter;
}
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:convert-d1-schema')
->setAliases(['orm:convert:d1-schema'])

View File

@@ -40,8 +40,7 @@ class ConvertMappingCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:convert-mapping')
->setAliases(['orm:convert:mapping'])

View File

@@ -22,8 +22,7 @@ class EnsureProductionSettingsCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:ensure-production-settings')
->setDescription('Verify that Doctrine is properly configured for a production environment')

View File

@@ -31,8 +31,7 @@ class GenerateEntitiesCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:generate-entities')
->setAliases(['orm:generate:entities'])

View File

@@ -29,8 +29,7 @@ class GenerateProxiesCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:generate-proxies')
->setAliases(['orm:generate:proxies'])

View File

@@ -30,8 +30,7 @@ class GenerateRepositoriesCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:generate-repositories')
->setAliases(['orm:generate:repositories'])

View File

@@ -23,8 +23,7 @@ class InfoCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:info')
->setDescription('Show basic information about all mapped entities')

View File

@@ -30,8 +30,7 @@ class RunDqlCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:run-dql')
->setDescription('Executes arbitrary DQL directly from the command line')

View File

@@ -27,6 +27,10 @@ abstract class AbstractCommand extends AbstractEntityManagerCommand
*/
abstract protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui);
private function doConfigure(): void
{
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
$ui = new SymfonyStyle($input, $output);

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command\SchemaTool;
use Doctrine\ORM\Tools\Console\CommandCompatibility;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -19,8 +20,9 @@ use function sprintf;
*/
class CreateCommand extends AbstractCommand
{
/** @return void */
protected function configure()
use CommandCompatibility;
private function doConfigure(): void
{
$this->setName('orm:schema-tool:create')
->setDescription('Processes the schema and either create it directly on EntityManager Storage Connection or generate the SQL output')
@@ -44,6 +46,11 @@ EOT
);
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
return parent::execute($input, $output);
}
/**
* {@inheritDoc}
*/

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command\SchemaTool;
use Doctrine\ORM\Tools\Console\CommandCompatibility;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -20,8 +21,9 @@ use function sprintf;
*/
class DropCommand extends AbstractCommand
{
/** @return void */
protected function configure()
use CommandCompatibility;
private function doConfigure(): void
{
$this->setName('orm:schema-tool:drop')
->setDescription('Drop the complete database schema of EntityManager Storage Connection or generate the corresponding SQL output')
@@ -48,6 +50,11 @@ EOT
);
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
return parent::execute($input, $output);
}
/**
* {@inheritDoc}
*/

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Doctrine\ORM\Tools\Console\Command\SchemaTool;
use Doctrine\ORM\Tools\Console\CommandCompatibility;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -21,11 +22,12 @@ use function sprintf;
*/
class UpdateCommand extends AbstractCommand
{
use CommandCompatibility;
/** @var string */
protected $name = 'orm:schema-tool:update';
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName($this->name)
->setDescription('Executes (or dumps) the SQL needed to update the database schema to match the current mapping metadata')
@@ -72,6 +74,11 @@ EOT
);
}
private function doExecute(InputInterface $input, OutputInterface $output): int
{
return parent::execute($input, $output);
}
/**
* {@inheritDoc}
*/

View File

@@ -23,8 +23,7 @@ class ValidateSchemaCommand extends AbstractEntityManagerCommand
{
use CommandCompatibility;
/** @return void */
protected function configure()
private function doConfigure(): void
{
$this->setName('orm:validate-schema')
->setDescription('Validate the mapping files')

View File

@@ -9,10 +9,32 @@ use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
if ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) {
// Symfony 8
if ((new ReflectionMethod(Command::class, 'configure'))->hasReturnType()) {
/** @internal */
trait CommandCompatibility
{
protected function configure(): void
{
$this->doConfigure();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
return $this->doExecute($input, $output);
}
}
// Symfony 7
} elseif ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) {
/** @internal */
trait CommandCompatibility
{
/** @return void */
protected function configure()
{
$this->doConfigure();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
return $this->doExecute($input, $output);
@@ -22,6 +44,12 @@ if ((new ReflectionMethod(Command::class, 'execute'))->hasReturnType()) {
/** @internal */
trait CommandCompatibility
{
/** @return void */
protected function configure()
{
$this->doConfigure();
}
/**
* {@inheritDoc}
*

View File

@@ -62,6 +62,7 @@ use function array_merge;
use function array_sum;
use function array_values;
use function assert;
use function count;
use function current;
use function func_get_arg;
use function func_num_args;
@@ -3172,8 +3173,14 @@ EXCEPTION
$reflField->setValue($entity, $pColl);
if ($hints['fetchMode'][$class->name][$field] === ClassMetadata::FETCH_EAGER) {
$isIteration = isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION];
if ($assoc['type'] === ClassMetadata::ONE_TO_MANY && ! $isIteration && ! $targetClass->isIdentifierComposite && ! isset($assoc['indexBy'])) {
if (
$assoc['type'] === ClassMetadata::ONE_TO_MANY
// is iteration
&& ! (isset($hints[Query::HINT_INTERNAL_ITERATION]) && $hints[Query::HINT_INTERNAL_ITERATION])
// is foreign key composite
&& ! ($targetClass->hasAssociation($assoc['mappedBy']) && count($targetClass->getAssociationMapping($assoc['mappedBy'])['joinColumns'] ?? []) > 1)
&& ! isset($assoc['indexBy'])
) {
$this->scheduleCollectionForBatchLoading($pColl, $class);
} else {
$this->loadCollection($pColl);

View File

@@ -18,8 +18,7 @@ class Rot13Type extends Type
/**
* {@inheritDoc}
*
* @param string|null $value
* @param AbstractPlatform $platform
* @param string|null $value
*
* @return string|null
*/
@@ -35,8 +34,7 @@ class Rot13Type extends Type
/**
* {@inheritDoc}
*
* @param string|null $value
* @param AbstractPlatform $platform
* @param string|null $value
*
* @return string|null
*/
@@ -52,9 +50,6 @@ class Rot13Type extends Type
/**
* {@inheritDoc}
*
* @param array $fieldDeclaration
* @param AbstractPlatform $platform
*
* @return string
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
@@ -69,8 +64,6 @@ class Rot13Type extends Type
/**
* {@inheritDoc}
*
* @param AbstractPlatform $platform
*
* @return int|null
*/
public function getDefaultLength(AbstractPlatform $platform)

View File

@@ -37,11 +37,19 @@ class RootEntity
*/
private $secondLevel;
/**
* @ORM\OneToMany(mappedBy="root", targetEntity=SecondLevelWithoutCompositePrimaryKey::class, fetch="EAGER")
*
* @var Collection<int, SecondLevelWithoutCompositePrimaryKey>
*/
private $anotherSecondLevel;
public function __construct(int $id, string $other)
{
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->id = $id;
$this->otherKey = $other;
$this->secondLevel = new ArrayCollection();
$this->anotherSecondLevel = new ArrayCollection();
$this->id = $id;
}
public function getId(): ?int

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\Models\EagerFetchedCompositeOneToMany;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SecondLevelWithoutCompositePrimaryKey
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer", nullable=false)
*
* @var int|null
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=RootEntity::class, inversedBy="anotherSecondLevel")
* @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->root = $upper;
}
public function getId(): ?int
{
return $this->id;
}
}

View File

@@ -6,6 +6,7 @@ namespace Doctrine\Tests\ORM\Functional;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\RootEntity;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevel;
use Doctrine\Tests\Models\EagerFetchedCompositeOneToMany\SecondLevelWithoutCompositePrimaryKey;
use Doctrine\Tests\OrmFunctionalTestCase;
final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCase
@@ -13,7 +14,7 @@ final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCas
/** @ticket 11154 */
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
{
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class, SecondLevelWithoutCompositePrimaryKey::class]);
$a1 = new RootEntity(1, 'A');

View File

@@ -673,7 +673,7 @@ SQL
public function testDifferentResultLengthsDoNotRequireExtraQueryCacheEntries(): void
{
$dql = 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id >= :id';
$dql = 'SELECT u FROM Doctrine\Tests\Models\CMS\CmsUser u WHERE u.id >= :id ORDER BY u.id';
$query = $this->_em->createQuery($dql);
$query->setMaxResults(10);

View File

@@ -430,7 +430,9 @@ class QueryTest extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$query = $this->_em->createQuery('select a, a.topic, a.text from ' . CmsArticle::class . ' a');
$query = $this->_em->createQuery(
'select a, a.topic, a.text from ' . CmsArticle::class . ' a order by a.id asc'
);
$result = $query->toIterable();
$it = iterator_to_array($result);

View File

@@ -49,7 +49,7 @@ class DDC1452Test extends OrmFunctionalTestCase
$this->_em->flush();
$this->_em->clear();
$dql = 'SELECT a, b, ba FROM ' . __NAMESPACE__ . '\DDC1452EntityA AS a LEFT JOIN a.entitiesB AS b LEFT JOIN b.entityATo AS ba';
$dql = 'SELECT a, b, ba FROM ' . __NAMESPACE__ . '\DDC1452EntityA AS a LEFT JOIN a.entitiesB AS b LEFT JOIN b.entityATo AS ba ORDER BY a.id';
$results = $this->_em->createQuery($dql)->setMaxResults(1)->getResult();
self::assertSame($results[0], $results[0]->entitiesB[0]->entityAFrom);

View File

@@ -51,7 +51,7 @@ class DDC2359Test extends TestCase
$entityManager->expects(self::any())->method('getConnection')->willReturn($connection);
$entityManager
->method('getEventManager')
->willReturn($this->createMock(EventManager::class));
->willReturn(new EventManager());
$metadataFactory->method('newClassMetadataInstance')->willReturn($mockMetadata);
$metadataFactory->expects(self::once())->method('wakeupReflection');

View File

@@ -36,7 +36,7 @@ class GH10387Test extends OrmTestCase
{
yield 'hierarchy with Entity classes only' => [[GH10387EntitiesOnlyRoot::class, GH10387EntitiesOnlyMiddle::class, GH10387EntitiesOnlyLeaf::class]];
yield 'MappedSuperclass in the middle of the hierarchy' => [[GH10387MappedSuperclassRoot::class, GH10387MappedSuperclassMiddle::class, GH10387MappedSuperclassLeaf::class]];
yield 'abstract entity the the root and in the middle of the hierarchy' => [[GH10387AbstractEntitiesRoot::class, GH10387AbstractEntitiesMiddle::class, GH10387AbstractEntitiesLeaf::class]];
yield 'abstract entity at the root and in the middle of the hierarchy' => [[GH10387AbstractEntitiesRoot::class, GH10387AbstractEntitiesMiddle::class, GH10387AbstractEntitiesLeaf::class]];
}
}

View File

@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\DiscriminatorColumn;
use Doctrine\ORM\Mapping\DiscriminatorMap;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Index;
use Doctrine\ORM\Mapping\InheritanceType;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\Table;
/**
* @Entity
* @Table(name="gh_12225_directory", indexes={@Index(columns={"dir_key"})})
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="type", type="string")
* @DiscriminatorMap({"main" = ConcreteDirectory::class})
*/
#[Entity]
#[Table(name: 'gh_12225_directory')]
#[Index(columns: ['dir_key'])]
#[InheritanceType('SINGLE_TABLE')]
#[DiscriminatorColumn(name: 'type', type: 'string')]
#[DiscriminatorMap(['main' => ConcreteDirectory::class])]
class AbstractDirectory
{
/**
* @var int
* @Id
* @GeneratedValue
* @Column(name="id", type="integer")
*/
#[Id]
#[GeneratedValue]
#[Column(name: 'id', type: 'integer')]
private $id;
/**
* @var string
* @Column(name="dir_key", type="string")
*/
#[Column(name: 'dir_key', type: 'string')]
private $dirKey;
/**
* @var DateTimeImmutable|null
* @Column(name="deleted_at", type="datetime_immutable", nullable=true)
*/
#[Column(name: 'deleted_at', type: 'datetime_immutable', nullable: true)]
private $deletedAt = null;
/**
* @var AbstractDirectory|null
* @ManyToOne(targetEntity=AbstractDirectory::class, fetch="LAZY", inversedBy="directories")
*/
#[ManyToOne(targetEntity: self::class, fetch: 'LAZY', inversedBy: 'directories')]
private $parent = null;
/**
* @var Collection<string, self>
* @OneToMany(mappedBy="parent", targetEntity=AbstractDirectory::class, fetch="EXTRA_LAZY", indexBy="dirKey")
*/
#[OneToMany(mappedBy: 'parent', targetEntity: self::class, fetch: 'EXTRA_LAZY', indexBy: 'dirKey')]
private $children;
public function __construct(string $dirKey)
{
$this->dirKey = $dirKey;
$this->children = new ArrayCollection();
}
public function getId(): int
{
return $this->id;
}
public function getDirKey(): string
{
return $this->dirKey;
}
public function getDeletedAt(): ?DateTimeImmutable
{
return $this->deletedAt;
}
public function setDeletedAt(?DateTimeImmutable $deletedAt): AbstractDirectory
{
$this->deletedAt = $deletedAt;
return $this;
}
public function getParent(): ?AbstractDirectory
{
return $this->parent;
}
public function setParent(?AbstractDirectory $parent): AbstractDirectory
{
$this->parent = $parent;
return $this;
}
/**
* @return Collection<string, self>
*/
public function getChildren(): Collection
{
return $this->children;
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
use Doctrine\ORM\Mapping\Entity;
/**
* @Entity
*/
#[Entity]
class ConcreteDirectory extends AbstractDirectory
{
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH12225Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
AbstractDirectory::class,
ConcreteDirectory::class,
]);
}
public function testHydrateWithIndexByFilterAndInheritanceMapping(): void
{
// Enable the filter
$this->_em->getConfiguration()->addFilter('my_filter', MyFilter::class);
$this->_em->getFilters()->enable('my_filter');
// Load entities into database
$parent = new ConcreteDirectory('parent');
$child = (new ConcreteDirectory('child'))->setParent($parent);
$this->_em->persist($parent);
$this->_em->persist($child);
$this->_em->flush();
$this->_em->clear();
$repository = $this->_em->getRepository(AbstractDirectory::class);
// Fetch entities from database while changing filters
$this->_em->getFilters()->suspend('my_filter');
$directories = $repository->findBy(['parent' => null]);
$this->_em->getFilters()->restore('my_filter');
// Ensure we got the parent directory
self::assertCount(1, $directories);
self::assertEquals('parent', $directories[0]->getDirKey());
// Try to hydrate all children of the parent directory (toArray is important here to initialize the collection)
self::assertCount(1, $directories[0]->getChildren()->toArray());
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket\GH12225;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class MyFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
{
return $targetTableAlias . '.deleted_at IS NULL';
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\Tests\OrmFunctionalTestCase;
class GH12254Test extends OrmFunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->setUpEntitySchema([
GH12254EntityA::class,
]);
$this->_em->persist(new GH12254EntityA());
$this->_em->flush();
$this->_em->clear();
}
public function testFindByEmptyArrayShouldReturnEmptyArray(): void
{
// pretend we are starting afresh
$this->_em = $this->getEntityManager();
$result = $this->_em->getRepository(GH12254EntityA::class)->findBy(['id' => []]);
$this->assertEmpty($result);
}
public function testFindByInNullableField(): void
{
$this->_em = $this->getEntityManager();
$result = $this->_em->getRepository(GH12254EntityA::class)->findBy(['name' => []]);
$this->assertEmpty($result);
}
}
/**
* @Entity()
*/
class GH12254EntityA
{
/**
* @Column(type="integer")
* @Id()
* @GeneratedValue(strategy="AUTO")
* @var int
*/
public $id;
/**
* @Column(type="string", nullable=true)
* @var string|null
*/
public $name = null;
}

View File

@@ -22,7 +22,7 @@ class GH6394Test extends OrmFunctionalTestCase
}
/**
* Test the the version of an entity can be fetched, when the id field and
* Test the version of an entity can be fetched, when the id field and
* the id column are different.
*
* @group 6393

View File

@@ -22,8 +22,8 @@ use function iterator_to_array;
/** @covers \Doctrine\ORM\Internal\Hydration\AbstractHydrator */
class AbstractHydratorTest extends OrmFunctionalTestCase
{
/** @var EventManager&MockObject */
private $mockEventManager;
/** @var EventManager */
private $eventManager;
/** @var Result&MockObject */
private $mockResult;
@@ -40,14 +40,14 @@ class AbstractHydratorTest extends OrmFunctionalTestCase
$mockConnection = $this->createMock(Connection::class);
$mockEntityManagerInterface = $this->createMock(EntityManagerInterface::class);
$this->mockEventManager = $this->createMock(EventManager::class);
$this->eventManager = new EventManager();
$this->mockResult = $this->createMock(Result::class);
$this->mockResultMapping = $this->createMock(ResultSetMapping::class);
$mockEntityManagerInterface
->expects(self::any())
->method('getEventManager')
->willReturn($this->mockEventManager);
->willReturn($this->eventManager);
$mockEntityManagerInterface
->expects(self::any())
->method('getConnection')
@@ -69,85 +69,29 @@ class AbstractHydratorTest extends OrmFunctionalTestCase
*/
public function testOnClearEventListenerIsDetachedOnCleanup(): void
{
$eventListenerHasBeenRegistered = false;
$this
->mockEventManager
->expects(self::once())
->method('addEventListener')
->with([Events::onClear], $this->hydrator)
->willReturnCallback(function () use (&$eventListenerHasBeenRegistered): void {
$this->assertFalse($eventListenerHasBeenRegistered);
$eventListenerHasBeenRegistered = true;
});
$this
->mockEventManager
->expects(self::once())
->method('removeEventListener')
->with([Events::onClear], $this->hydrator)
->willReturnCallback(function () use (&$eventListenerHasBeenRegistered): void {
$this->assertTrue($eventListenerHasBeenRegistered);
});
iterator_to_array($this->hydrator->iterate($this->mockResult, $this->mockResultMapping));
$iterator = $this->hydrator->iterate($this->mockResult, $this->mockResultMapping);
self::assertTrue($this->eventManager->hasListeners(Events::onClear));
iterator_to_array($iterator);
self::assertFalse($this->eventManager->hasListeners(Events::onClear));
}
/** @group #6623 */
public function testHydrateAllRegistersAndClearsAllAttachedListeners(): void
{
$eventListenerHasBeenRegistered = false;
$this
->mockEventManager
->expects(self::once())
->method('addEventListener')
->with([Events::onClear], $this->hydrator)
->willReturnCallback(function () use (&$eventListenerHasBeenRegistered): void {
$this->assertFalse($eventListenerHasBeenRegistered);
$eventListenerHasBeenRegistered = true;
});
$this
->mockEventManager
->expects(self::once())
->method('removeEventListener')
->with([Events::onClear], $this->hydrator)
->willReturnCallback(function () use (&$eventListenerHasBeenRegistered): void {
$this->assertTrue($eventListenerHasBeenRegistered);
});
$this->hydrator->hydrateAll($this->mockResult, $this->mockResultMapping);
self::assertTrue($this->hydrator->hasListener);
self::assertFalse($this->eventManager->hasListeners(Events::onClear));
}
/** @group #8482 */
public function testHydrateAllClearsAllAttachedListenersEvenOnError(): void
{
$eventListenerHasBeenRegistered = false;
$this
->mockEventManager
->expects(self::once())
->method('addEventListener')
->with([Events::onClear], $this->hydrator)
->willReturnCallback(function () use (&$eventListenerHasBeenRegistered): void {
$this->assertFalse($eventListenerHasBeenRegistered);
$eventListenerHasBeenRegistered = true;
});
$this
->mockEventManager
->expects(self::once())
->method('removeEventListener')
->with([Events::onClear], $this->hydrator)
->willReturnCallback(function () use (&$eventListenerHasBeenRegistered): void {
$this->assertTrue($eventListenerHasBeenRegistered);
});
$this->hydrator->throwException = true;
$this->expectException(ORMException::class);
$this->hydrator->hydrateAll($this->mockResult, $this->mockResultMapping);
self::assertTrue($this->hydrator->hasListener);
self::assertFalse($this->eventManager->hasListeners(Events::onClear));
}
public function testToIterableIfYieldAndBreakBeforeFinishAlwaysCleansUp(): void
@@ -185,6 +129,9 @@ class DummyHydrator extends AbstractHydrator
/** @var bool */
public $throwException = false;
/** @var bool */
public $hasListener = false;
/** {@inheritDoc} */
protected function hydrateAllData()
{
@@ -194,4 +141,9 @@ class DummyHydrator extends AbstractHydrator
return [];
}
public function prepare(): void
{
$this->hasListener = $this->_em->getEventManager()->hasListeners(Events::onClear);
}
}

View File

@@ -129,7 +129,10 @@ class BasicEntityPersisterTypeValueSqlTest extends OrmTestCase
self::assertEquals('test IS NOT NULL', $statement);
}
/** @group DDC-3056 */
/**
* @group DDC-3056
* @group GH12254
*/
public function testSelectConditionStatementWithMultipleValuesContainingNull(): void
{
self::assertEquals(
@@ -151,6 +154,11 @@ class BasicEntityPersisterTypeValueSqlTest extends OrmTestCase
'(t0.id IN (?, ?) OR t0.id IS NULL)',
$this->persister->getSelectConditionStatementSQL('id', [123, null, 234])
);
self::assertEquals(
'1=0',
$this->persister->getSelectConditionStatementSQL('id', [])
);
}
public function testCountCondition(): void

View File

@@ -9,6 +9,7 @@ use Doctrine\ORM\Query\Expr;
use Doctrine\Tests\Models\Company\CompanyEmployee;
use Doctrine\Tests\OrmTestCase;
use Generator;
use InvalidArgumentException;
/**
* Test case for the DQL Expr class used for generating DQL snippets through
@@ -366,11 +367,31 @@ class ExprTest extends OrmTestCase
public function testAddThrowsException(): void
{
$this->expectException('InvalidArgumentException');
$orExpr = $this->expr->orX();
$this->expectException(InvalidArgumentException::class);
$orExpr->add($this->expr->quot(5, 2));
}
/**
* @param mixed $arg
*
* @dataProvider provideInvalidTypesForAdd
*/
public function testAddThrowsExceptionOnInvalidType($arg): void
{
$orExpr = $this->expr->orX();
$this->expectException(InvalidArgumentException::class);
$orExpr->add($arg);
}
/** @return Generator<string, array{mixed}> */
public static function provideInvalidTypesForAdd(): Generator
{
yield 'integer 1' => [1];
yield 'object' => [(object) ['foo' => 'bar']];
yield 'array' => [['foo' => 'bar']];
}
/** @group DDC-1683 */
public function testBooleanLiteral(): void
{