mirror of
https://github.com/doctrine/orm.git
synced 2026-04-02 20:32:19 +02:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fe8ce4bf7 | ||
|
|
a46ff16339 | ||
|
|
94e60e4318 | ||
|
|
f59cd4019a | ||
|
|
81558a8b2a | ||
|
|
e431ee113d | ||
|
|
de4ec208fd | ||
|
|
df014a74c9 | ||
|
|
2f46d95028 | ||
|
|
3fda5629f6 | ||
|
|
6b273234d6 | ||
|
|
d2266c7d0c | ||
|
|
eb0485869a | ||
|
|
8372d600c6 | ||
|
|
dde1d71b34 | ||
|
|
0d03255061 | ||
|
|
75a18090d9 | ||
|
|
77ffd2ab68 | ||
|
|
401cc06d71 | ||
|
|
5029b193ee | ||
|
|
6068b61a0d | ||
|
|
00024f7d88 | ||
|
|
b2faba62b7 | ||
|
|
da426a0036 | ||
|
|
1891a76f13 | ||
|
|
026f5bfe1b | ||
|
|
0b0f2f4d86 | ||
|
|
d2418ab074 | ||
|
|
39a05e31c9 | ||
|
|
3b8c23c51d | ||
|
|
60d4ea694a | ||
|
|
0f8730a6e5 | ||
|
|
0aeddd0592 | ||
|
|
492745d710 | ||
|
|
67419cf951 | ||
|
|
1237f5c909 | ||
|
|
dcdd46251e | ||
|
|
3d98b43561 | ||
|
|
9f3f70944a | ||
|
|
05e07c0ae0 | ||
|
|
fea42ab984 | ||
|
|
01fd55e9ea | ||
|
|
87f1ba74e0 | ||
|
|
ab148d3d9d | ||
|
|
3924c38fab | ||
|
|
9814078a2c | ||
|
|
6de5684fd9 | ||
|
|
c142503a52 | ||
|
|
15537bc218 | ||
|
|
bc95c7c08d | ||
|
|
c1becd54e6 | ||
|
|
e4d7df29c2 | ||
|
|
e38278bfca | ||
|
|
5bff0919a7 | ||
|
|
9ef0f5301b | ||
|
|
4989ca6f15 | ||
|
|
32d1e97ce7 | ||
|
|
ca8147b148 | ||
|
|
c8ebea77f0 | ||
|
|
23f22860f1 | ||
|
|
b24586b1b5 | ||
|
|
fe5ee705db | ||
|
|
0511a9f790 | ||
|
|
0e3d5e8c82 | ||
|
|
72ffb3bfbf | ||
|
|
2e9a1adc23 |
2
.github/workflows/coding-standards.yml
vendored
2
.github/workflows/coding-standards.yml
vendored
@@ -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
20
.github/workflows/composer-lint.yml
vendored
Normal 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"
|
||||
76
.github/workflows/continuous-integration.yml
vendored
76
.github/workflows/continuous-integration.yml
vendored
@@ -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:
|
||||
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -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"
|
||||
|
||||
4
.github/workflows/phpbench.yml
vendored
4
.github/workflows/phpbench.yml
vendored
@@ -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"
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
4
.github/workflows/static-analysis.yml
vendored
4
.github/workflows/static-analysis.yml
vendored
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>`_
|
||||
|
||||
@@ -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
|
||||
|
||||
15
docs/en/reference/basic-mapping/DefaultValues.php
Normal file
15
docs/en/reference/basic-mapping/DefaultValues.php
Normal 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;
|
||||
}
|
||||
24
docs/en/reference/basic-mapping/EnumCollectionMapping.php
Normal file
24
docs/en/reference/basic-mapping/EnumCollectionMapping.php
Normal 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 = [];
|
||||
}
|
||||
19
docs/en/reference/basic-mapping/EnumMapping.php
Normal file
19
docs/en/reference/basic-mapping/EnumMapping.php
Normal 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;
|
||||
}
|
||||
13
docs/en/reference/basic-mapping/Suit.php
Normal file
13
docs/en/reference/basic-mapping/Suit.php
Normal 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';
|
||||
}
|
||||
9
docs/en/reference/basic-mapping/default-values.xml
Normal file
9
docs/en/reference/basic-mapping/default-values.xml
Normal 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>
|
||||
@@ -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
|
||||
-------
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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\.$#'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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".'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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'])) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> */
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
*/
|
||||
|
||||
@@ -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}
|
||||
*/
|
||||
|
||||
@@ -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}
|
||||
*/
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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}
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
123
tests/Tests/ORM/Functional/Ticket/GH12225/AbstractDirectory.php
Normal file
123
tests/Tests/ORM/Functional/Ticket/GH12225/AbstractDirectory.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
49
tests/Tests/ORM/Functional/Ticket/GH12225/GH12225Test.php
Normal file
49
tests/Tests/ORM/Functional/Ticket/GH12225/GH12225Test.php
Normal 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());
|
||||
}
|
||||
}
|
||||
16
tests/Tests/ORM/Functional/Ticket/GH12225/MyFilter.php
Normal file
16
tests/Tests/ORM/Functional/Ticket/GH12225/MyFilter.php
Normal 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';
|
||||
}
|
||||
}
|
||||
62
tests/Tests/ORM/Functional/Ticket/GH12254Test.php
Normal file
62
tests/Tests/ORM/Functional/Ticket/GH12254Test.php
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user