Compare commits

...

42 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
38 changed files with 573 additions and 139 deletions

View File

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

View File

@@ -17,4 +17,4 @@ on:
jobs:
composer-lint:
name: "Composer Lint"
uses: "doctrine/.github/.github/workflows/composer-lint.yml@13.0.0"
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:
@@ -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"
@@ -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"
@@ -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"
@@ -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:
@@ -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 }}"
@@ -382,12 +412,12 @@ jobs:
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@13.0.0"
uses: "doctrine/.github/.github/workflows/documentation.yml@14.0.0"

View File

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

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

@@ -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.
@@ -311,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,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

@@ -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
@@ -3567,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
@@ -3585,18 +3579,6 @@ 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

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

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

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

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

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

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