mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Compare commits
216 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78dd074266 | ||
|
|
ff22a00fcf | ||
|
|
02e8ff9663 | ||
|
|
2e75a7f1c1 | ||
|
|
152b0e3d65 | ||
|
|
9d11fdd3da | ||
|
|
87f1ba74e0 | ||
|
|
ee70178314 | ||
|
|
ab148d3d9d | ||
|
|
3924c38fab | ||
|
|
9814078a2c | ||
|
|
6de5684fd9 | ||
|
|
c142503a52 | ||
|
|
01c178b297 | ||
|
|
ffa50a777f | ||
|
|
649048f745 | ||
|
|
15537bc218 | ||
|
|
bc95c7c08d | ||
|
|
3df11d518c | ||
|
|
c1becd54e6 | ||
|
|
e4d7df29c2 | ||
|
|
608705427e | ||
|
|
9f19310f27 | ||
|
|
e38278bfca | ||
|
|
f18de9d569 | ||
|
|
37f76a8381 | ||
|
|
b62292256a | ||
|
|
b138395194 | ||
|
|
dede2d775a | ||
|
|
c502190712 | ||
|
|
5bff0919a7 | ||
|
|
9ef0f5301b | ||
|
|
4989ca6f15 | ||
|
|
32d1e97ce7 | ||
|
|
ca8147b148 | ||
|
|
c8ebea77f0 | ||
|
|
23f22860f1 | ||
|
|
b24586b1b5 | ||
|
|
7d8e51c934 | ||
|
|
2f8f1cfcb8 | ||
|
|
fe5ee705db | ||
|
|
0511a9f790 | ||
|
|
0e3d5e8c82 | ||
|
|
72ffb3bfbf | ||
|
|
2e9a1adc23 | ||
|
|
ffd3f50ad7 | ||
|
|
483b45d449 | ||
|
|
1220edf953 | ||
|
|
7e4693d629 | ||
|
|
59938cae57 | ||
|
|
298dc9bb6a | ||
|
|
da67f323e0 | ||
|
|
63635cad0e | ||
|
|
a0d401b688 | ||
|
|
6acbadfbbe | ||
|
|
c64dcb4d38 | ||
|
|
3304290b21 | ||
|
|
1865717721 | ||
|
|
828b06e20f | ||
|
|
c2b844d2e3 | ||
|
|
dd4e8fe78f | ||
|
|
4f36f0129a | ||
|
|
b45d5329f8 | ||
|
|
c1047b30e3 | ||
|
|
f71aa73ef1 | ||
|
|
aa62efa30a | ||
|
|
f71956f001 | ||
|
|
7cc210424c | ||
|
|
4fd9e94819 | ||
|
|
587caf88a7 | ||
|
|
1e33b775d3 | ||
|
|
96f9b29573 | ||
|
|
c6207b1793 | ||
|
|
8c92903430 | ||
|
|
8616a98023 | ||
|
|
9a55cf4f30 | ||
|
|
9d680a6de4 | ||
|
|
7602a5341c | ||
|
|
7a59281157 | ||
|
|
214b1ad739 | ||
|
|
5def068fe9 | ||
|
|
693acbf812 | ||
|
|
28d9472a38 | ||
|
|
cf11f1e453 | ||
|
|
c6955ec056 | ||
|
|
c1ce2bb687 | ||
|
|
6863272943 | ||
|
|
eae6577ce2 | ||
|
|
5f6896a2f9 | ||
|
|
e3106d439d | ||
|
|
930a790a5a | ||
|
|
40aa44914f | ||
|
|
7c8a528914 | ||
|
|
3b7de17f2e | ||
|
|
40fedadecf | ||
|
|
0d97a44f28 | ||
|
|
c472a1535d | ||
|
|
8afaa63d73 | ||
|
|
2ad720b304 | ||
|
|
64cd5cad20 | ||
|
|
f1a8ee175c | ||
|
|
d3e1440175 | ||
|
|
a9bd00a70b | ||
|
|
a939dc2e0d | ||
|
|
f8186b1203 | ||
|
|
28dd32790f | ||
|
|
4f3a5c5514 | ||
|
|
6641989e35 | ||
|
|
048e308241 | ||
|
|
8ca72a4e96 | ||
|
|
daf74b74b5 | ||
|
|
4274dac8a2 | ||
|
|
ac19b21a71 | ||
|
|
c6db9feade | ||
|
|
c1af765960 | ||
|
|
86d847edb8 | ||
|
|
5f3551852f | ||
|
|
8144cad07c | ||
|
|
70fd68cf7f | ||
|
|
437259556c | ||
|
|
e7e2fef56c | ||
|
|
3e34b8e86a | ||
|
|
7d950aba62 | ||
|
|
8ad560c34d | ||
|
|
ccfb620f31 | ||
|
|
94c4d48ae5 | ||
|
|
2ca63df90c | ||
|
|
0d4413c248 | ||
|
|
a7a14cffaf | ||
|
|
48434f4c53 | ||
|
|
200a505f36 | ||
|
|
17d7814fdc | ||
|
|
8fe1200edf | ||
|
|
f7d4e379bc | ||
|
|
c164ae434f | ||
|
|
ceb04bf3f6 | ||
|
|
21e9fcbfbb | ||
|
|
ed9ba16ff4 | ||
|
|
db456976ed | ||
|
|
2d9091778f | ||
|
|
17059e5265 | ||
|
|
680a9ef632 | ||
|
|
9d5f112c7e | ||
|
|
b7423c96cf | ||
|
|
fc1bf3b815 | ||
|
|
b6b342cada | ||
|
|
28735afae3 | ||
|
|
0f229fbb4b | ||
|
|
bea4814d55 | ||
|
|
85c13edc80 | ||
|
|
397358c308 | ||
|
|
ac37a87a3d | ||
|
|
238c15952c | ||
|
|
613f52db5a | ||
|
|
cf5503b0d8 | ||
|
|
fdc88ba236 | ||
|
|
c49bf58682 | ||
|
|
ae5e9c8c6c | ||
|
|
ce844d94a0 | ||
|
|
1a2826d147 | ||
|
|
05760f9454 | ||
|
|
26af013842 | ||
|
|
c1f7a60c5b | ||
|
|
d1d13d5956 | ||
|
|
4f8dde2d1e | ||
|
|
e3c320c705 | ||
|
|
5a541b8b3a | ||
|
|
9fb9cc46e4 | ||
|
|
c322c71cd4 | ||
|
|
3c733a2fee | ||
|
|
5984ad586a | ||
|
|
ee2c3a506b | ||
|
|
781ed30926 | ||
|
|
04694a9f7b | ||
|
|
257c5094c4 | ||
|
|
831232e05e | ||
|
|
66e0e92816 | ||
|
|
a774cedb24 | ||
|
|
6b8207bb11 | ||
|
|
3d3b5b51cd | ||
|
|
5b2060e25f | ||
|
|
760616291b | ||
|
|
8584da8fdc | ||
|
|
07bb0def60 | ||
|
|
39e35fc06c | ||
|
|
7f061c3870 | ||
|
|
74495711fb | ||
|
|
97a7cb8d2f | ||
|
|
8cf161d8bc | ||
|
|
e0052390e1 | ||
|
|
8c6419e0e0 | ||
|
|
6f5ce1aca2 | ||
|
|
98e7a53b42 | ||
|
|
3aaaf37dfb | ||
|
|
154a4652ee | ||
|
|
ae7489ff19 | ||
|
|
a2990e1a0a | ||
|
|
d355c4a990 | ||
|
|
88c395c488 | ||
|
|
256d6cb0d7 | ||
|
|
a1fdc6eb6e | ||
|
|
d583460d63 | ||
|
|
79d4cfdce8 | ||
|
|
5301b99533 | ||
|
|
d68c1dcd6d | ||
|
|
00c7b70211 | ||
|
|
528b8837e1 | ||
|
|
9bf407f336 | ||
|
|
4fb044d5f6 | ||
|
|
2a953c5e2b | ||
|
|
abc6a40ccb | ||
|
|
73e68f3c7d | ||
|
|
73777d0bd4 | ||
|
|
e89b58a13f | ||
|
|
2b94ec18b9 | ||
|
|
2a662149f4 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -11,7 +11,6 @@ build.properties.dev export-ignore
|
||||
build.xml export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
run-all.sh export-ignore
|
||||
phpcs.xml.dist export-ignore
|
||||
phpbench.json export-ignore
|
||||
phpstan.neon export-ignore
|
||||
|
||||
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@7.3.0"
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@13.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@13.0.0"
|
||||
100
.github/workflows/continuous-integration.yml
vendored
100
.github/workflows/continuous-integration.yml
vendored
@@ -35,6 +35,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -67,7 +68,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -83,6 +84,10 @@ jobs:
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Downgrade VarExporter"
|
||||
run: 'composer require --no-update "symfony/var-exporter:^6.4 || ^7.4"'
|
||||
if: "${{ matrix.native_lazy == '0' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
with:
|
||||
@@ -95,19 +100,69 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
|
||||
- name: "Run PHPUnit with Second Level Cache and PHPUnit 10"
|
||||
run: |
|
||||
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
|
||||
--exclude-group=performance,non-cacheable,locking_functional \
|
||||
--coverage-clover=coverage-cache.xml
|
||||
if: "${{ matrix.php-version == '8.1' }}"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache and PHPUnit 11+"
|
||||
run: |
|
||||
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
|
||||
--exclude-group=performance \
|
||||
--exclude-group=non-cacheable \
|
||||
--exclude-group=locking_functional \
|
||||
--coverage-clover=coverage-cache.xml
|
||||
if: "${{ matrix.php-version != '8.1' }}"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
ENABLE_NATIVE_LAZY_OBJECTS: ${{ matrix.native_lazy }}
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
uses: "actions/upload-artifact@v5"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.native_lazy }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
phpunit-deprecations:
|
||||
name: "PHPUnit (fail on deprecations)"
|
||||
runs-on: "ubuntu-24.04"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v5"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "8.5"
|
||||
extensions: "apcu, pdo, sqlite3"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Allow dev dependencies"
|
||||
run: composer config minimum-stability dev
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v3"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite3.xml --fail-on-deprecation"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
ENABLE_NATIVE_LAZY_OBJECTS: 1
|
||||
|
||||
|
||||
phpunit-postgres:
|
||||
name: "PHPUnit with PostgreSQL"
|
||||
runs-on: "ubuntu-22.04"
|
||||
@@ -119,6 +174,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -151,7 +207,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -176,7 +232,7 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
uses: "actions/upload-artifact@v5"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -193,6 +249,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -218,7 +275,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -243,7 +300,7 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
uses: "actions/upload-artifact@v5"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -260,6 +317,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -293,7 +351,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -319,13 +377,27 @@ jobs:
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-no-cache.xml"
|
||||
- name: "Run PHPUnit with Second Level Cache and PHPUnit 10"
|
||||
run: |
|
||||
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
|
||||
--exclude-group=performance,non-cacheable,locking_functional \
|
||||
--coverage-clover=coverage-no-cache.xml"
|
||||
if: "${{ matrix.php-version == '8.1' }}"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
- name: "Run PHPUnit with Second Level Cache and PHPUnit 11+"
|
||||
run: |
|
||||
vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml \
|
||||
--exclude-group=performance \
|
||||
--exclude-group=non-cacheable \
|
||||
--exclude-group=locking_functional \
|
||||
--coverage-clover=coverage-no-cache.xml
|
||||
if: "${{ matrix.php-version != '8.1' }}"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage files"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
uses: "actions/upload-artifact@v5"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -343,12 +415,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v4"
|
||||
uses: "actions/download-artifact@v6"
|
||||
with:
|
||||
path: "reports"
|
||||
|
||||
|
||||
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@7.3.0"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@13.0.0"
|
||||
|
||||
2
.github/workflows/phpbench.yml
vendored
2
.github/workflows/phpbench.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@7.3.0"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@13.0.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
2
.github/workflows/static-analysis.yml
vendored
2
.github/workflows/static-analysis.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
|
||||
- name: Install PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
65
UPGRADE.md
65
UPGRADE.md
@@ -1,5 +1,36 @@
|
||||
Note about upgrading: Doctrine uses static and runtime mechanisms to raise
|
||||
awareness about deprecated code.
|
||||
|
||||
- Use of `@deprecated` docblock that is detected by IDEs (like PHPStorm) or
|
||||
Static Analysis tools (like Psalm, phpstan)
|
||||
- Use of our low-overhead runtime deprecation API, details:
|
||||
https://github.com/doctrine/deprecations/
|
||||
|
||||
# Upgrade to 3.x General Notes
|
||||
|
||||
We recommend you upgrade to DBAL 3 first before upgrading to ORM 3. See
|
||||
the DBAL upgrade docs: https://github.com/doctrine/dbal/blob/3.10.x/UPGRADE.md
|
||||
|
||||
Rather than doing several major upgrades at once, we recommend you do the following:
|
||||
|
||||
- upgrade to DBAL 3
|
||||
- deploy and monitor
|
||||
- upgrade to ORM 3
|
||||
- deploy and monitor
|
||||
- upgrade to DBAL 4
|
||||
- deploy and monitor
|
||||
|
||||
If you are using Symfony, the recommended minimal Doctrine Bundle version is 2.15
|
||||
to run with ORM 3.
|
||||
|
||||
At this point, we recommend upgrading to PHP 8.4 first and then directly from
|
||||
ORM 2.19 to 3.5 and up so that you can skip the lazy ghost proxy generation
|
||||
and directly start using native lazy objects.
|
||||
|
||||
# Upgrade to 3.5
|
||||
|
||||
See the General notes to upgrading to 3.x versions above.
|
||||
|
||||
## Deprecate not using native lazy objects on PHP 8.4+
|
||||
|
||||
Having native lazy objects disabled on PHP 8.4+ is deprecated and will not be
|
||||
@@ -53,6 +84,8 @@ decide to remove it before it is used too widely.
|
||||
|
||||
# Upgrade to 3.4
|
||||
|
||||
See the General notes to upgrading to 3.x versions above.
|
||||
|
||||
## Discriminator Map class duplicates
|
||||
|
||||
Using the same class several times in a discriminator map is deprecated.
|
||||
@@ -73,6 +106,8 @@ Companion accessor methods are deprecated as well.
|
||||
|
||||
# Upgrade to 3.3
|
||||
|
||||
See the General notes to upgrading to 3.x versions above.
|
||||
|
||||
## Deprecate `DatabaseDriver`
|
||||
|
||||
The class `Doctrine\ORM\Mapping\Driver\DatabaseDriver` is deprecated without replacement.
|
||||
@@ -89,6 +124,8 @@ method. Details can be found at https://github.com/doctrine/orm/pull/11188.
|
||||
|
||||
# Upgrade to 3.2
|
||||
|
||||
See the General notes to upgrading to 3.x versions above.
|
||||
|
||||
## Deprecate the `NotSupported` exception
|
||||
|
||||
The class `Doctrine\ORM\Exception\NotSupported` is deprecated without replacement.
|
||||
@@ -115,6 +152,8 @@ using the `\Doctrine\ORM\Mapping\UniqueConstraint` and `\Doctrine\ORM\Mapping\In
|
||||
|
||||
# Upgrade to 3.1
|
||||
|
||||
See the General notes to upgrading to 3.x versions above.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Mapping\ReflectionEnumProperty`
|
||||
|
||||
This class is deprecated and will be removed in 4.0.
|
||||
@@ -138,6 +177,8 @@ Using array access on instances of the following classes is deprecated:
|
||||
|
||||
# Upgrade to 3.0
|
||||
|
||||
See the General notes to upgrading to 3.x versions above.
|
||||
|
||||
## BC BREAK: Calling `ClassMetadata::getAssociationMappedByTargetField()` with the owning side of an association now throws an exception
|
||||
|
||||
Previously, calling
|
||||
@@ -163,6 +204,9 @@ so `$targetEntity` is a first argument now. This change affects only non-named a
|
||||
When using the `AUTO` strategy to let Doctrine determine the identity generation mechanism for
|
||||
an entity, and when using `doctrine/dbal` 4, PostgreSQL now uses `IDENTITY`
|
||||
instead of `SEQUENCE` or `SERIAL`.
|
||||
|
||||
There are three ways to handle this change.
|
||||
|
||||
* If you want to upgrade your existing tables to identity columns, you will need to follow [migration to identity columns on PostgreSQL](https://www.doctrine-project.org/projects/doctrine-dbal/en/4.0/how-to/postgresql-identity-migration.html)
|
||||
* If you want to keep using SQL sequences, you need to configure the ORM this way:
|
||||
```php
|
||||
@@ -175,6 +219,27 @@ $configuration->setIdentityGenerationPreferences([
|
||||
PostgreSQLPlatform::CLASS => ClassMetadata::GENERATOR_TYPE_SEQUENCE,
|
||||
]);
|
||||
```
|
||||
* You can change individual entities to use the `SEQUENCE` strategy instead of `AUTO`:
|
||||
```php
|
||||
|
||||
diff --git a/src/Entity/Example.php b/src/Entity/Example.php
|
||||
index 28be8df378..3b7d61bda6 100644
|
||||
--- a/src/Entity/Example.php
|
||||
+++ b/src/Entity/Example.php
|
||||
@@ -38,7 +38,7 @@ class Example
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
- #[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
+ #[ORM\GeneratedValue(strategy: 'SEQUENCE')]
|
||||
private int $id;
|
||||
|
||||
#[Assert\Length(max: 255)]
|
||||
```
|
||||
The later two options require a small database migration that will remove the default
|
||||
expression fetching the next value from the sequence. It's not strictly necessary to
|
||||
do this migration because the code will work anyway. A benefit of this approach is
|
||||
that you can just make and roll out the code changes first and then migrate the database later.
|
||||
|
||||
## BC BREAK: Throw exceptions when using illegal attributes on Embeddable
|
||||
|
||||
|
||||
@@ -1,29 +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"
|
||||
],
|
||||
"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": "^8.1",
|
||||
"composer-runtime-api": "^2",
|
||||
"ext-ctype": "*",
|
||||
"composer-runtime-api": "^2",
|
||||
"doctrine/collections": "^2.2",
|
||||
"doctrine/dbal": "^3.8.2 || ^4",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
@@ -33,36 +43,44 @@
|
||||
"doctrine/lexer": "^3",
|
||||
"doctrine/persistence": "^3.3.1 || ^4",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/var-exporter": "^6.3.9 || ^7.0"
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0 || ^8.0",
|
||||
"symfony/var-exporter": "^6.3.9 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^13.0",
|
||||
"doctrine/coding-standard": "^14.0",
|
||||
"phpbench/phpbench": "^1.0",
|
||||
"phpdocumentor/guides-cli": "^1.4",
|
||||
"phpstan/extension-installer": "^1.4",
|
||||
"phpstan/phpstan": "2.0.3",
|
||||
"phpstan/phpstan": "2.1.23",
|
||||
"phpstan/phpstan-deprecation-rules": "^2",
|
||||
"phpunit/phpunit": "^10.4.0",
|
||||
"phpunit/phpunit": "^10.5.0 || ^11.5",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.12.0",
|
||||
"symfony/cache": "^5.4 || ^6.2 || ^7.0"
|
||||
"symfony/cache": "^5.4 || ^6.2 || ^7.0 || ^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "Provides support for XSD validation for XML mapping files",
|
||||
"symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
2
docs/.gitignore
vendored
2
docs/.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
composer.lock
|
||||
vendor/
|
||||
build/
|
||||
output/
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Makefile for Doctrine ORM documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
DOCOPTS =
|
||||
BUILD = vendor/bin/guides
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
ALLGUIDESOPTS = $(DOCOPTS) en/
|
||||
|
||||
.PHONY: help clean html
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
|
||||
clean:
|
||||
-rm -rf ./$(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(BUILD) $(ALLGUIDESOPTS) --output=$(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
@@ -4,19 +4,15 @@ The documentation is written in [ReStructured Text](https://docutils.sourceforge
|
||||
|
||||
## How to Generate:
|
||||
|
||||
In the `docs/` folder, run
|
||||
In the project root, run
|
||||
|
||||
composer update
|
||||
composer docs
|
||||
|
||||
Then compile the documentation with:
|
||||
|
||||
make html
|
||||
|
||||
This will generate the documentation into the `build` subdirectory.
|
||||
This will generate the documentation into the `docs/output` subdirectory.
|
||||
|
||||
To browse the documentation, you need to run a webserver:
|
||||
|
||||
cd build/html
|
||||
cd docs/output
|
||||
php -S localhost:8000
|
||||
|
||||
Now the documentation is available at [http://localhost:8000](http://localhost:8000).
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
"description": "Documentation for the Object-Relational Mapper\"",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"phpdocumentor/guides-cli": "1.7.1",
|
||||
"phpdocumentor/filesystem": "1.7.1"
|
||||
"require-dev": {
|
||||
"doctrine/docs-builder": "^1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,8 +101,16 @@ The ``Paginate::count(Query $query)`` looks like:
|
||||
{
|
||||
static public function count(Query $query)
|
||||
{
|
||||
/** @var Query $countQuery */
|
||||
$countQuery = clone $query;
|
||||
/*
|
||||
To avoid changing the $query passed into the method and to make sure a possibly existing
|
||||
ResultSetMapping is discarded, we create a new query object any copy relevant data over.
|
||||
*/
|
||||
$countQuery = new Query($query->getEntityManager());
|
||||
$countQuery->setDQL($query->getDQL());
|
||||
$countQuery->setParameters(clone $query->getParameters());
|
||||
foreach ($query->getHints() as $name => $value) {
|
||||
$countQuery->setHint($name, $value);
|
||||
}
|
||||
|
||||
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
|
||||
$countQuery->setFirstResult(null)->setMaxResults(null);
|
||||
@@ -111,7 +119,7 @@ The ``Paginate::count(Query $query)`` looks like:
|
||||
}
|
||||
}
|
||||
|
||||
It clones the query, resets the limit clause first and max results
|
||||
This resets the limit clause first and max results
|
||||
and registers the ``CountSqlWalker`` custom tree walker which
|
||||
will modify the AST to execute a count query. The walkers
|
||||
implementation is:
|
||||
|
||||
@@ -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>`_
|
||||
|
||||
@@ -76,6 +76,8 @@ Configuration Options
|
||||
The following sections describe all the configuration options
|
||||
available on a ``Doctrine\ORM\Configuration`` instance.
|
||||
|
||||
.. _reference-native-lazy-objects:
|
||||
|
||||
Native Lazy Objects (**OPTIONAL**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -302,7 +304,7 @@ requests.
|
||||
Connection
|
||||
----------
|
||||
|
||||
The ``$connection`` passed as the first argument to he constructor of
|
||||
The ``$connection`` passed as the first argument to the constructor of
|
||||
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
|
||||
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
|
||||
to create such a connection. The DBAL configuration is explained in the
|
||||
|
||||
@@ -79,8 +79,9 @@ Entities
|
||||
An entity is a lightweight, persistent domain object. An entity can
|
||||
be any regular PHP class observing the following restrictions:
|
||||
|
||||
- An entity class must not be final nor read-only but
|
||||
it may contain final methods or read-only properties.
|
||||
- An entity class can be final or read-only when
|
||||
you use :ref:`native lazy objects <reference-native-lazy-objects>`.
|
||||
It may contain final methods or read-only properties too.
|
||||
- Any two entity classes in a class hierarchy that inherit
|
||||
directly or indirectly from one another must not have a mapped
|
||||
property with the same name. That is, if B inherits from A then B
|
||||
@@ -167,7 +168,7 @@ recommended, at least not as long as an entity instance still holds
|
||||
references to proxy objects or is still managed by an EntityManager.
|
||||
By default, serializing proxy objects does not initialize them. On
|
||||
unserialization, resulting objects are detached from the entity
|
||||
manager and cannot be initialiazed anymore. You can implement the
|
||||
manager and cannot be initialized anymore. You can implement the
|
||||
``__serialize()`` method if you want to change that behavior, but
|
||||
then you need to ensure that you won't generate large serialized
|
||||
object graphs and take care of circular associations.
|
||||
|
||||
@@ -182,6 +182,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
|
||||
@@ -389,17 +404,19 @@ Here is the list of possible generation strategies:
|
||||
|
||||
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
|
||||
preferred by the used database platform. The preferred strategies
|
||||
are ``IDENTITY`` for MySQL, SQLite, MsSQL and SQL Anywhere and, for
|
||||
historical reasons, ``SEQUENCE`` for Oracle and PostgreSQL. This
|
||||
strategy provides full portability.
|
||||
are ``IDENTITY`` for MySQL, SQLite, MsSQL, SQL Anywhere and
|
||||
PostgreSQL (on DBAL 4) and, for historical reasons, ``SEQUENCE``
|
||||
for Oracle and PostgreSQL (on DBAL 3). This strategy provides
|
||||
full portability.
|
||||
- ``IDENTITY``: Tells Doctrine to use special identity columns in
|
||||
the database that generate a value on insertion of a row. This
|
||||
strategy does currently not provide full portability and is
|
||||
supported by the following platforms: MySQL/SQLite/SQL Anywhere
|
||||
(``AUTO_INCREMENT``), MSSQL (``IDENTITY``) and PostgreSQL (``SERIAL``).
|
||||
(``AUTO_INCREMENT``), MSSQL (``IDENTITY``) and PostgreSQL (``SERIAL``
|
||||
on DBAL 3, ``GENERATED BY DEFAULT AS IDENTITY`` on DBAL 4).
|
||||
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
|
||||
generation. This strategy does currently not provide full
|
||||
portability. Sequences are supported by Oracle, PostgreSql and
|
||||
portability. Sequences are supported by Oracle, PostgreSQL and
|
||||
SQL Anywhere.
|
||||
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
|
||||
thus generated) by your code. The assignment must take place before
|
||||
|
||||
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;
|
||||
}
|
||||
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
|
||||
-------
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ table alias of the SQL table of the entity.
|
||||
|
||||
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
|
||||
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
|
||||
2. The filter must be deterministic. Don't change the values base on external inputs.
|
||||
2. The filter must be deterministic. Don't change the values based on external inputs.
|
||||
|
||||
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
|
||||
|
||||
|
||||
@@ -178,6 +178,14 @@ internally by the ORM currently refers to fields by their name only, without tak
|
||||
class containing the field into consideration. This makes it impossible to keep separate
|
||||
mapping configuration for both fields.
|
||||
|
||||
Apart from that, in the case of having multiple ``private`` fields of the same name within
|
||||
the class hierarchy an entity or mapped superclass, the Collection filtering API cannot determine
|
||||
the right field to look at. Even if only one of these fields is actually mapped, the ``ArrayCollection``
|
||||
will not be able to tell, since it does not have access to any metadata.
|
||||
|
||||
Thus, to avoid problems in this regard, it is best to avoid having multiple ``private`` fields of the
|
||||
same name in class hierarchies containing entity and mapped superclasses.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ Something like below for an entity region:
|
||||
|
||||
|
||||
If the entity holds a collection that also needs to be cached.
|
||||
An collection region could look something like:
|
||||
A collection region could look something like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -518,7 +518,7 @@ DELETE / UPDATE queries
|
||||
DQL UPDATE / DELETE statements are ported directly into a database and bypass
|
||||
the second-level cache.
|
||||
Entities that are already cached will NOT be invalidated.
|
||||
However the cached data could be evicted using the cache API or an special query hint.
|
||||
However the cached data could be evicted using the cache API or a special query hint.
|
||||
|
||||
|
||||
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
|
||||
|
||||
@@ -118,7 +118,7 @@ entity might look like this:
|
||||
}
|
||||
}
|
||||
|
||||
Now the possiblity of mass-assignment exists on this entity and can
|
||||
Now the possibility of mass-assignment exists on this entity and can
|
||||
be exploited by attackers to set the "isAdmin" flag to true on any
|
||||
object when you pass the whole request data to this method like:
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
:depth: 3
|
||||
|
||||
tutorials/getting-started
|
||||
tutorials/getting-started-database
|
||||
tutorials/getting-started-models
|
||||
tutorials/working-with-indexed-associations
|
||||
tutorials/extra-lazy-associations
|
||||
tutorials/composite-primary-keys
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
Getting Started: Database First
|
||||
===============================
|
||||
|
||||
.. note:: *Development Workflows*
|
||||
|
||||
When you :doc:`Code First <getting-started>`, you
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you :doc:`Model First <getting-started-models>`, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a Database First, you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
.. note::
|
||||
|
||||
This getting started guide is in development.
|
||||
|
||||
Development of new applications often starts with an existing database schema.
|
||||
When the database schema is the starting point for your application, then
|
||||
development is said to use the *Database First* approach to Doctrine.
|
||||
|
||||
In this workflow you would modify the database schema first and then
|
||||
regenerate the PHP code to use with this schema. You need a flexible
|
||||
code-generator for this task.
|
||||
|
||||
We spun off a subproject, Doctrine CodeGenerator, that will fill this gap and
|
||||
allow you to do *Database First* development.
|
||||
@@ -1,24 +0,0 @@
|
||||
Getting Started: Model First
|
||||
============================
|
||||
|
||||
.. note:: *Development Workflows*
|
||||
|
||||
When you :doc:`Code First <getting-started>`, you
|
||||
start with developing Objects and then map them onto your database. When
|
||||
you Model First, you are modelling your application using tools (for
|
||||
example UML) and generate database schema and PHP code from this model.
|
||||
When you have a :doc:`Database First <getting-started-database>`, then you already have a database schema
|
||||
and generate the corresponding PHP code from it.
|
||||
|
||||
.. note::
|
||||
|
||||
This getting started guide is in development.
|
||||
|
||||
There are applications when you start with a high-level description of the
|
||||
model using modelling tools such as UML. Modelling tools could also be Excel,
|
||||
XML or CSV files that describe the model in some structured way. If your
|
||||
application is using a modelling tool, then the development workflow is said to
|
||||
be a *Model First* approach to Doctrine2.
|
||||
|
||||
In this workflow you always change the model description and then regenerate
|
||||
both PHP code and database schema from this model.
|
||||
@@ -49,8 +49,9 @@ An entity contains persistable properties. A persistable property
|
||||
is an instance variable of the entity that is saved into and retrieved from the database
|
||||
by Doctrine's data mapping capabilities.
|
||||
|
||||
An entity class must not be final nor read-only, although
|
||||
it can contain final methods or read-only properties.
|
||||
An entity class can be final or read-only when you use
|
||||
:ref:`native lazy objects <reference-native-lazy-objects>`.
|
||||
It may contain final methods or read-only properties too.
|
||||
|
||||
An Example Model: Bug Tracker
|
||||
-----------------------------
|
||||
@@ -534,7 +535,7 @@ the ``id`` tag. It has a ``generator`` tag nested inside, which
|
||||
specifies that the primary key generation mechanism should automatically
|
||||
use the database platform's native id generation strategy (for
|
||||
example, AUTO INCREMENT in the case of MySql, or Sequences in the
|
||||
case of PostgreSql and Oracle).
|
||||
case of PostgreSQL and Oracle).
|
||||
|
||||
Now that we have defined our first entity and its metadata,
|
||||
let's update the database schema:
|
||||
@@ -1287,7 +1288,7 @@ The console output of this script is then:
|
||||
result set to retrieve entities from the database. DQL boils down to a
|
||||
Native SQL statement and a ``ResultSetMapping`` instance itself. Using
|
||||
Native SQL you could even use stored procedures for data retrieval, or
|
||||
make use of advanced non-portable database queries like PostgreSql's
|
||||
make use of advanced non-portable database queries like PostgreSQL's
|
||||
recursive queries.
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#^Expression "\$setCacheEntry\(\$data\)" on a separate line does not do anything\.$#'
|
||||
identifier: expr.resultUnused
|
||||
count: 1
|
||||
path: src/AbstractQuery.php
|
||||
|
||||
-
|
||||
message: '#^Expression "\$setCacheEntry\(\$stmt\)" on a separate line does not do anything\.$#'
|
||||
identifier: expr.resultUnused
|
||||
count: 1
|
||||
path: src/AbstractQuery.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\AbstractQuery\:\:getParameter\(\) should return Doctrine\\ORM\\Query\\Parameter\|null but returns Doctrine\\ORM\\Query\\Parameter\|false\|null\.$#'
|
||||
identifier: return.type
|
||||
@@ -234,12 +222,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Cache/DefaultQueryCache.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$result of class Doctrine\\ORM\\Cache\\QueryCacheEntry constructor expects array\<string, mixed\>, array\<int\|string, array\<string, array\<mixed\>\>\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Cache/DefaultQueryCache.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$key of method Doctrine\\ORM\\Cache\\Logging\\CacheLogger\:\:entityCacheHit\(\) expects Doctrine\\ORM\\Cache\\EntityCacheKey, Doctrine\\ORM\\Cache\\CacheKey given\.$#'
|
||||
identifier: argument.type
|
||||
@@ -516,6 +498,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Cache/TimestampQueryCacheValidator.php
|
||||
|
||||
-
|
||||
message: '#^Call to function is_a\(\) with arguments class\-string\<Doctrine\\ORM\\EntityRepository\>, ''Doctrine\\\\ORM\\\\EntityRepository'' and true will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Configuration.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Configuration\:\:getDefaultRepositoryClassName\(\) return type with generic class Doctrine\\ORM\\EntityRepository does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -558,12 +546,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Decorator/EntityManagerDecorator.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method object\:\:setEntityManager\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 1
|
||||
path: src/EntityManager.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\EntityManager\:\:checkLockRequirements\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -733,13 +715,13 @@ parameters:
|
||||
path: src/Internal/Hydration/AbstractHydrator.php
|
||||
|
||||
-
|
||||
message: '#^Parameter &\$id by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, string\>, array\<int\|string, string\> given\.$#'
|
||||
message: '#^Parameter &\$id by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, string\>, array\<string\> given\.$#'
|
||||
identifier: parameterByRef.type
|
||||
count: 1
|
||||
path: src/Internal/Hydration/AbstractHydrator.php
|
||||
|
||||
-
|
||||
message: '#^Parameter &\$nonemptyComponents by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, bool\>, array\<int\|string, bool\> given\.$#'
|
||||
message: '#^Parameter &\$nonemptyComponents by\-ref type of method Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator\:\:gatherRowData\(\) expects array\<string, bool\>, array\<bool\> given\.$#'
|
||||
identifier: parameterByRef.type
|
||||
count: 1
|
||||
path: src/Internal/Hydration/AbstractHydrator.php
|
||||
@@ -834,6 +816,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
|
||||
@@ -1080,6 +1068,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Mapping/ClassMetadata.php
|
||||
|
||||
-
|
||||
message: '#^Template type T is declared as covariant, but occurs in invariant position in return type of method Doctrine\\ORM\\Mapping\\ClassMetadata\:\:getReflectionClass\(\)\.$#'
|
||||
identifier: generics.variance
|
||||
count: 1
|
||||
path: src/Mapping/ClassMetadata.php
|
||||
|
||||
-
|
||||
message: '#^Unable to resolve the template type C in call to method Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:fullyQualifiedClassName\(\)$#'
|
||||
identifier: argument.templateType
|
||||
@@ -1374,12 +1368,6 @@ parameters:
|
||||
count: 2
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Cannot call method getName\(\) on Doctrine\\DBAL\\Schema\\Column\|false\.$#'
|
||||
identifier: method.nonObject
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Instanceof between Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\> and Doctrine\\ORM\\Mapping\\ClassMetadata will always evaluate to true\.$#'
|
||||
identifier: instanceof.alwaysTrue
|
||||
@@ -1410,6 +1398,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) has parameter \$asset with generic class Doctrine\\DBAL\\Schema\\AbstractAsset but does not specify its types\: N$#'
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getClassNameForTable\(\) should return class\-string but returns string\.$#'
|
||||
identifier: return.type
|
||||
@@ -1536,6 +1530,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Mapping/LegacyReflectionFields.php
|
||||
|
||||
-
|
||||
message: '#^Strict comparison using \!\=\= between array\<string, string\> and null will always evaluate to true\.$#'
|
||||
identifier: notIdentical.alwaysTrue
|
||||
count: 1
|
||||
path: src/Mapping/ManyToManyOwningSideMapping.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Mapping\\MappedSuperclass\:\:__construct\(\) has parameter \$repositoryClass with generic class Doctrine\\ORM\\EntityRepository but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -2022,18 +2022,6 @@ parameters:
|
||||
count: 4
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$relationToTargetKeyColumns\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$sourceToTargetKeyColumns\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:indexBy\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
@@ -2046,18 +2034,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandCriteriaParameters\(\) should return array\{list\<mixed\>, list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\>\} but returns array\{array\<mixed\>, list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\>\}\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandParameters\(\) should return array\{list\<mixed\>, list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\>\} but returns array\{array\<mixed\>, list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\>\}\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:expandToManyParameters\(\) return type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
@@ -2094,12 +2070,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getIndividualValue\(\) should return list\<mixed\> but returns array\<mixed\>\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getSelectColumnAssociationSQL\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -2112,18 +2082,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getTypes\(\) should return list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\> but returns list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|int\>\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:loadCollectionFromStatement\(\) has parameter \$coll with generic class Doctrine\\ORM\\PersistentCollection but does not specify its types\: TKey, T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -2178,12 +2136,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Strict comparison using \=\=\= between string and null will always evaluate to false\.$#'
|
||||
identifier: identical.alwaysFalse
|
||||
count: 1
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\CachedPersisterContext\:\:__construct\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -2655,7 +2607,7 @@ parameters:
|
||||
-
|
||||
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\: Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|int\|string, is_list\: bool\}\>\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
count: 2
|
||||
path: src/Query/Filter/SQLFilter.php
|
||||
|
||||
-
|
||||
@@ -2928,12 +2880,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: '#^Method Doctrine\\ORM\\Tools\\Console\\Command\\MappingDescribeCommand\:\:getClassMetadata\(\) return type with generic class Doctrine\\ORM\\Mapping\\ClassMetadata does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -3090,12 +3036,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Tools/ResolveTargetEntityListener.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$className of method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory\<Doctrine\\ORM\\Mapping\\ClassMetadata\>\:\:setMetadataFor\(\) expects class\-string, \(int\|string\) given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/ResolveTargetEntityListener.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$joinColumns\.$#'
|
||||
identifier: property.notFound
|
||||
@@ -3231,6 +3171,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getAssetName\(\) has parameter \$asset with generic class Doctrine\\DBAL\\Schema\\AbstractAsset but does not specify its types\: N$#'
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Tools\\SchemaTool\:\:getCreateSchemaSql\(\) has parameter \$classes with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -3303,24 +3249,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:setPrimaryKey\(\) expects non\-empty\-list\<string\>, array\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$value of static method Doctrine\\DBAL\\Schema\\Name\\Identifier\:\:unquoted\(\) expects non\-empty\-string, string given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$columnNames of class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint constructor expects non\-empty\-list\<Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\>, list\<Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$columns of class Doctrine\\DBAL\\Schema\\Index constructor expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
@@ -3333,6 +3261,18 @@ parameters:
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$primaryKeyColumns of method Doctrine\\ORM\\Tools\\SchemaTool\:\:addPrimaryKeyConstraint\(\) expects non\-empty\-array\<non\-empty\-string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 2
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$primaryKeyColumns of method Doctrine\\ORM\\Tools\\SchemaTool\:\:addPrimaryKeyConstraint\(\) expects non\-empty\-array\<non\-empty\-string\>, non\-empty\-list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$foreignColumnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addForeignKeyConstraint\(\) expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
@@ -3405,6 +3345,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\AssociationMapping\:\:\$joinColumns\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$inversedBy\.$#'
|
||||
identifier: property.notFound
|
||||
@@ -3420,7 +3366,7 @@ parameters:
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$mappedBy\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
count: 3
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
@@ -3597,6 +3543,18 @@ parameters:
|
||||
count: 1
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$relationToTargetKeyColumns\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping\:\:\$sourceToTargetKeyColumns\.$#'
|
||||
identifier: property.notFound
|
||||
count: 1
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getTypeOfColumn\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -3608,3 +3566,15 @@ parameters:
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:inferParameterTypes\(\) should return list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|Doctrine\\DBAL\\ParameterType\:\:ASCII\|Doctrine\\DBAL\\ParameterType\:\:BINARY\|Doctrine\\DBAL\\ParameterType\:\:BOOLEAN\|Doctrine\\DBAL\\ParameterType\:\:INTEGER\|Doctrine\\DBAL\\ParameterType\:\:LARGE_OBJECT\|Doctrine\\DBAL\\ParameterType\:\:NULL\|Doctrine\\DBAL\\ParameterType\:\:STRING\|string\> but returns list\<Doctrine\\DBAL\\ArrayParameterType\:\:ASCII\|Doctrine\\DBAL\\ArrayParameterType\:\:BINARY\|Doctrine\\DBAL\\ArrayParameterType\:\:INTEGER\|Doctrine\\DBAL\\ArrayParameterType\:\:STRING\|int\>\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
@@ -11,7 +11,7 @@ parameters:
|
||||
# We can be certain that those values are not matched.
|
||||
-
|
||||
message: '~^Match expression does not handle remaining values:~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
# DBAL 4 compatibility
|
||||
-
|
||||
@@ -34,6 +34,14 @@ parameters:
|
||||
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Call to static method quoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getObjectName\(\)\.$~'
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint::get.*\.$~'
|
||||
identifier: method.notFound
|
||||
@@ -79,13 +87,28 @@ parameters:
|
||||
message: '~^Call to method toString.*UnqualifiedName\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
- '~^Call to method getObjectName\(\) on an unknown class Doctrine\\DBAL\\Schema\\NamedObject\.$~'
|
||||
|
||||
- '~^Class Doctrine\\DBAL\\Platforms\\SQLitePlatform not found\.$~'
|
||||
|
||||
- '~^Class Doctrine\\DBAL\\Schema\\NamedObject not found\.$~'
|
||||
|
||||
-
|
||||
message: '~sort~'
|
||||
identifier: argument.unresolvableType
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$asset of static method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getAssetName\(\) expects Doctrine\\DBAL\\Schema\\AbstractAsset, Doctrine\\DBAL\\Schema\\Column\|false given\.$#'
|
||||
identifier: argument.type
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Instantiated class Doctrine\\DBAL\\Schema\\DefaultExpression\\\w+ not found\.$#'
|
||||
identifier: class.notFound
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
|
||||
# To be removed in 4.0
|
||||
-
|
||||
message: '#Negated boolean expression is always false\.#'
|
||||
@@ -105,12 +128,12 @@ parameters:
|
||||
path: src/Mapping/Driver/AttributeDriver.php
|
||||
|
||||
-
|
||||
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
-
|
||||
message: '~getTypes.*should return~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
message: '~inferParameterTypes.*should return~'
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
-
|
||||
message: '~.*appendLockHint.*expects.*LockMode given~'
|
||||
|
||||
@@ -10,15 +10,15 @@ parameters:
|
||||
# We can be certain that those values are not matched.
|
||||
-
|
||||
message: '~^Match expression does not handle remaining values:~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
# DBAL 4 compatibility
|
||||
-
|
||||
message: '~^Method Doctrine\\ORM\\Query\\AST\\Functions\\TrimFunction::getTrimMode\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Query/AST/Functions/TrimFunction.php
|
||||
-
|
||||
message: '~^Method Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Persisters/Entity/BasicEntityPersister.php
|
||||
message: '~^Method Doctrine\\ORM\\Utility\\PersisterHelper\:\:getArrayBindingType\(\) never returns .* so it can be removed from the return type\.$~'
|
||||
path: src/Utility/PersisterHelper.php
|
||||
|
||||
# Compatibility with DBAL 3
|
||||
# See https://github.com/doctrine/dbal/pull/3480
|
||||
|
||||
21
run-all.sh
21
run-all.sh
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is a small convenience wrapper for running the doctrine testsuite against a large bunch of databases.
|
||||
# Just create the phpunit.xmls as described in the array below and configure the specific files <php /> section
|
||||
# to connect to that database. Just omit a file if you don't have that database and the tests will be skipped.
|
||||
|
||||
configs[1]="mysql.phpunit.xml"
|
||||
configs[2]='postgres.phpunit.xml'
|
||||
configs[3]='sqlite.phpunit.xml'
|
||||
configs[4]='oracle.phpunit.xml'
|
||||
configs[5]='db2.phpunit.xml'
|
||||
configs[6]='pdo-ibm.phpunit.xml'
|
||||
configs[7]='sqlsrv.phpunit.xml'
|
||||
|
||||
for i in "${configs[@]}"; do
|
||||
if [ -f "$i" ];
|
||||
then
|
||||
echo "RUNNING TESTS WITH CONFIG $i"
|
||||
phpunit -c "$i" "$@"
|
||||
fi;
|
||||
done
|
||||
@@ -18,7 +18,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
|
||||
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use LogicException;
|
||||
@@ -865,10 +864,6 @@ abstract class AbstractQuery
|
||||
throw new LogicException('Uninitialized result set mapping.');
|
||||
}
|
||||
|
||||
if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
|
||||
throw QueryException::iterateWithMixedResultNotAllowed();
|
||||
}
|
||||
|
||||
$stmt = $this->_doExecute();
|
||||
|
||||
return $this->em->newHydrator($this->hydrationMode)->toIterable($stmt, $rsm, $this->hints);
|
||||
|
||||
@@ -23,9 +23,11 @@ use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function current;
|
||||
use function end;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function ksort;
|
||||
|
||||
/**
|
||||
@@ -126,8 +128,10 @@ abstract class AbstractHydrator
|
||||
} else {
|
||||
yield from $result;
|
||||
}
|
||||
} else {
|
||||
} elseif (is_object(current($result))) {
|
||||
yield $result;
|
||||
} else {
|
||||
yield array_merge(...$result);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\DBAL\Schema\AbstractAsset;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
@@ -11,6 +12,7 @@ use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Index\IndexedColumn;
|
||||
use Doctrine\DBAL\Schema\Index\IndexType;
|
||||
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
|
||||
use Doctrine\DBAL\Schema\NamedObject;
|
||||
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
@@ -143,14 +145,14 @@ class DatabaseDriver implements MappingDriver
|
||||
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
|
||||
|
||||
foreach ($entityTables as $table) {
|
||||
$className = $this->getClassNameForTable($table->getName());
|
||||
$className = $this->getClassNameForTable(self::getAssetName($table));
|
||||
|
||||
$this->classToTableNames[$className] = $table->getName();
|
||||
$this->tables[$table->getName()] = $table;
|
||||
$this->classToTableNames[$className] = self::getAssetName($table);
|
||||
$this->tables[self::getAssetName($table)] = $table;
|
||||
}
|
||||
|
||||
foreach ($manyToManyTables as $table) {
|
||||
$this->manyToManyTables[$table->getName()] = $table;
|
||||
$this->manyToManyTables[self::getAssetName($table)] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,13 +221,13 @@ class DatabaseDriver implements MappingDriver
|
||||
$localColumn = current(self::getReferencingColumnNames($myFk));
|
||||
|
||||
$associationMapping = [];
|
||||
$associationMapping['fieldName'] = $this->getFieldNameForColumn($manyTable->getName(), current(self::getReferencingColumnNames($otherFk)), true);
|
||||
$associationMapping['fieldName'] = $this->getFieldNameForColumn(self::getAssetName($manyTable), current(self::getReferencingColumnNames($otherFk)), true);
|
||||
$associationMapping['targetEntity'] = $this->getClassNameForTable(self::getReferencedTableName($otherFk));
|
||||
|
||||
if (current($manyTable->getColumns())->getName() === $localColumn) {
|
||||
$associationMapping['inversedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current(self::getReferencingColumnNames($myFk)), true);
|
||||
if (self::getAssetName(current($manyTable->getColumns())) === $localColumn) {
|
||||
$associationMapping['inversedBy'] = $this->getFieldNameForColumn(self::getAssetName($manyTable), current(self::getReferencingColumnNames($myFk)), true);
|
||||
$associationMapping['joinTable'] = [
|
||||
'name' => strtolower($manyTable->getName()),
|
||||
'name' => strtolower(self::getAssetName($manyTable)),
|
||||
'joinColumns' => [],
|
||||
'inverseJoinColumns' => [],
|
||||
];
|
||||
@@ -250,7 +252,14 @@ class DatabaseDriver implements MappingDriver
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$associationMapping['mappedBy'] = $this->getFieldNameForColumn($manyTable->getName(), current(self::getReferencingColumnNames($myFk)), true);
|
||||
$associationMapping['mappedBy'] = $this->getFieldNameForColumn(
|
||||
// @phpstan-ignore function.alreadyNarrowedType (DBAL 3 compatibility)
|
||||
method_exists(Table::class, 'getObjectName')
|
||||
? $manyTable->getObjectName()->toString()
|
||||
: $manyTable->getName(), // DBAL < 4.4
|
||||
current(self::getReferencingColumnNames($myFk)),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
$metadata->mapManyToMany($associationMapping);
|
||||
@@ -270,7 +279,7 @@ class DatabaseDriver implements MappingDriver
|
||||
$this->tables = $this->manyToManyTables = $this->classToTableNames = [];
|
||||
|
||||
foreach ($this->sm->listTables() as $table) {
|
||||
$tableName = $table->getName();
|
||||
$tableName = self::getAssetName($table);
|
||||
$foreignKeys = $table->getForeignKeys();
|
||||
|
||||
$allForeignKeyColumns = [];
|
||||
@@ -335,7 +344,7 @@ class DatabaseDriver implements MappingDriver
|
||||
$isUnique = $index->isUnique();
|
||||
}
|
||||
|
||||
$indexName = $index->getName();
|
||||
$indexName = self::getAssetName($index);
|
||||
$indexColumns = self::getIndexedColumns($index);
|
||||
$constraintType = $isUnique
|
||||
? 'uniqueConstraints'
|
||||
@@ -364,13 +373,13 @@ class DatabaseDriver implements MappingDriver
|
||||
$fieldMappings = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if (in_array($column->getName(), $allForeignKeys, true)) {
|
||||
if (in_array(self::getAssetName($column), $allForeignKeys, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldMapping = $this->buildFieldMapping($tableName, $column);
|
||||
|
||||
if ($primaryKeys && in_array($column->getName(), $primaryKeys, true)) {
|
||||
if ($primaryKeys && in_array(self::getAssetName($column), $primaryKeys, true)) {
|
||||
$fieldMapping['id'] = true;
|
||||
$ids[] = $fieldMapping;
|
||||
}
|
||||
@@ -411,8 +420,8 @@ class DatabaseDriver implements MappingDriver
|
||||
private function buildFieldMapping(string $tableName, Column $column): array
|
||||
{
|
||||
$fieldMapping = [
|
||||
'fieldName' => $this->getFieldNameForColumn($tableName, $column->getName(), false),
|
||||
'columnName' => $column->getName(),
|
||||
'fieldName' => $this->getFieldNameForColumn($tableName, self::getAssetName($column), false),
|
||||
'columnName' => self::getAssetName($column),
|
||||
'type' => Type::getTypeRegistry()->lookupName($column->getType()),
|
||||
'nullable' => ! $column->getNotnull(),
|
||||
'options' => [
|
||||
@@ -619,4 +628,12 @@ class DatabaseDriver implements MappingDriver
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function getAssetName(AbstractAsset $asset): string
|
||||
{
|
||||
return $asset instanceof NamedObject
|
||||
? $asset->getObjectName()->toString()
|
||||
// DBAL < 4.4
|
||||
: $asset->getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,8 @@ trait ReflectionBasedDriver
|
||||
*/
|
||||
private function isRepeatedPropertyDeclaration(ReflectionProperty $property, ClassMetadata $metadata): bool
|
||||
{
|
||||
/** @var class-string $declaringClass */
|
||||
$declaringClass = $property->class;
|
||||
|
||||
if ($this->isTransient($declaringClass)) {
|
||||
return isset($metadata->fieldMappings[$property->name]);
|
||||
}
|
||||
|
||||
if (
|
||||
isset($metadata->fieldMappings[$property->name]->declared)
|
||||
&& $metadata->fieldMappings[$property->name]->declared === $declaringClass
|
||||
|
||||
@@ -137,7 +137,6 @@ class XmlDriver extends FileDriver
|
||||
];
|
||||
|
||||
if (isset($discrColumn['options'])) {
|
||||
assert($discrColumn['options'] instanceof SimpleXMLElement);
|
||||
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ trait JoinColumnProperties
|
||||
public readonly string|null $referencedColumnName = null,
|
||||
public readonly bool $deferrable = false,
|
||||
public readonly bool $unique = false,
|
||||
public readonly bool $nullable = true,
|
||||
public readonly bool|null $nullable = null,
|
||||
public readonly mixed $onDelete = null,
|
||||
public readonly string|null $columnDefinition = null,
|
||||
public readonly string|null $fieldName = null,
|
||||
|
||||
@@ -127,6 +127,8 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
$mapping->joinTableColumns = [];
|
||||
|
||||
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
|
||||
$joinColumn->nullable = false;
|
||||
|
||||
if (empty($joinColumn->referencedColumnName)) {
|
||||
$joinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
|
||||
}
|
||||
@@ -150,6 +152,8 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
}
|
||||
|
||||
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
|
||||
$inverseJoinColumn->nullable = false;
|
||||
|
||||
if (empty($inverseJoinColumn->referencedColumnName)) {
|
||||
$inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
|
||||
}
|
||||
|
||||
@@ -130,6 +130,12 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
|
||||
$uniqueConstraintColumns = [];
|
||||
|
||||
foreach ($mapping->joinColumns as $joinColumn) {
|
||||
if ($mapping->id) {
|
||||
$joinColumn->nullable = false;
|
||||
} elseif ($joinColumn->nullable === null) {
|
||||
$joinColumn->nullable = true;
|
||||
}
|
||||
|
||||
if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) {
|
||||
if (count($mapping->joinColumns) === 1) {
|
||||
if (empty($mapping->id)) {
|
||||
|
||||
@@ -160,6 +160,11 @@ EXCEPTION
|
||||
return new self('You must configure a proxy directory. See docs for details');
|
||||
}
|
||||
|
||||
public static function lazyGhostUnavailable(): self
|
||||
{
|
||||
return new self('Symfony LazyGhost is not available. Please install the "symfony/var-exporter" package version 6.4 or 7 to use this feature or enable PHP 8.4 native lazy objects.');
|
||||
}
|
||||
|
||||
public static function proxyNamespaceRequired(): self
|
||||
{
|
||||
return new self('You must configure a proxy namespace');
|
||||
|
||||
@@ -19,6 +19,7 @@ use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Utility\PersisterHelper;
|
||||
|
||||
use function array_fill;
|
||||
use function array_merge;
|
||||
use function array_pop;
|
||||
use function assert;
|
||||
use function count;
|
||||
@@ -247,10 +248,17 @@ class ManyToManyPersister extends AbstractCollectionPersister
|
||||
|
||||
if ($value === null && ($operator === Comparison::EQ || $operator === Comparison::NEQ)) {
|
||||
$whereClauses[] = sprintf('te.%s %s NULL', $field, $operator === Comparison::EQ ? 'IS' : 'IS NOT');
|
||||
} elseif ($operator === Comparison::IN || $operator === Comparison::NIN) {
|
||||
$whereClauses[] = sprintf('te.%s %s (%s)', $field, $operator === Comparison::IN ? 'IN' : 'NOT IN', implode(', ', array_fill(0, count($value), '?')));
|
||||
foreach ($value as $item) {
|
||||
$params = array_merge($params, PersisterHelper::convertToParameterValue($item, $this->em));
|
||||
$paramTypes = array_merge($paramTypes, PersisterHelper::inferParameterTypes($name, $item, $targetClass, $this->em));
|
||||
}
|
||||
} else {
|
||||
$whereClauses[] = sprintf('te.%s %s ?', $field, $operator);
|
||||
$params[] = $value;
|
||||
$paramTypes[] = PersisterHelper::getTypeOfField($name, $targetClass, $this->em)[0];
|
||||
|
||||
$params = [...$params, ...PersisterHelper::convertToParameterValue($value, $this->em)];
|
||||
$paramTypes = [...$paramTypes, ...PersisterHelper::inferParameterTypes($name, $value, $targetClass, $this->em)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
// only works with single id identifier entities. Will throw an
|
||||
// exception in Entity Persisters if that is not the case for the
|
||||
// 'mappedBy' field.
|
||||
$criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner()));
|
||||
$criteria = Criteria::create(true)->where(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner()));
|
||||
|
||||
return $persister->count($criteria);
|
||||
}
|
||||
@@ -118,7 +118,7 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
// only works with single id identifier entities. Will throw an
|
||||
// exception in Entity Persisters if that is not the case for the
|
||||
// 'mappedBy' field.
|
||||
$criteria = new Criteria();
|
||||
$criteria = Criteria::create(true);
|
||||
|
||||
$criteria->andWhere(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner()));
|
||||
$criteria->andWhere(Criteria::expr()->eq($mapping->indexBy(), $key));
|
||||
@@ -138,7 +138,7 @@ class OneToManyPersister extends AbstractCollectionPersister
|
||||
// only works with single id identifier entities. Will throw an
|
||||
// exception in Entity Persisters if that is not the case for the
|
||||
// 'mappedBy' field.
|
||||
$criteria = new Criteria(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner()));
|
||||
$criteria = Criteria::create(true)->where(Criteria::expr()->eq($mapping->mappedBy, $collection->getOwner()));
|
||||
|
||||
return $persister->exists($element, $criteria);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,7 @@ use Doctrine\ORM\Persisters\Exception\InvalidOrientation;
|
||||
use Doctrine\ORM\Persisters\Exception\UnrecognizedField;
|
||||
use Doctrine\ORM\Persisters\SqlExpressionVisitor;
|
||||
use Doctrine\ORM\Persisters\SqlValueVisitor;
|
||||
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Repository\Exception\InvalidFindByCall;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
@@ -43,17 +41,17 @@ use Doctrine\ORM\Utility\PersisterHelper;
|
||||
use LengthException;
|
||||
|
||||
use function array_combine;
|
||||
use function array_diff_key;
|
||||
use function array_fill;
|
||||
use function array_flip;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_search;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function reset;
|
||||
use function spl_object_id;
|
||||
use function sprintf;
|
||||
@@ -153,12 +151,6 @@ class BasicEntityPersister implements EntityPersister
|
||||
*/
|
||||
protected array $quotedColumns = [];
|
||||
|
||||
/**
|
||||
* The INSERT SQL statement used for entities handled by this persister.
|
||||
* This SQL is only generated once per request, if at all.
|
||||
*/
|
||||
private string|null $insertSql = null;
|
||||
|
||||
/**
|
||||
* The quote strategy.
|
||||
*/
|
||||
@@ -273,8 +265,8 @@ class BasicEntityPersister implements EntityPersister
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
|
||||
// Unset this queued insert, so that the prepareUpdateData() method knows right away
|
||||
// (for the next entity already) that the current entity has been written to the database
|
||||
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
|
||||
// knows right away (for the next entity already) that the current entity has been written to the database
|
||||
// and no extra updates need to be scheduled to refer to it.
|
||||
//
|
||||
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
|
||||
@@ -359,7 +351,7 @@ class BasicEntityPersister implements EntityPersister
|
||||
$types = [];
|
||||
|
||||
foreach ($id as $field => $value) {
|
||||
$types = [...$types, ...$this->getTypes($field, $value, $versionedClass)];
|
||||
$types = [...$types, ...PersisterHelper::inferParameterTypes($field, $value, $versionedClass, $this->em)];
|
||||
}
|
||||
|
||||
return $types;
|
||||
@@ -925,8 +917,31 @@ class BasicEntityPersister implements EntityPersister
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqlParams = [...$sqlParams, ...$this->getValues($value)];
|
||||
$sqlTypes = [...$sqlTypes, ...$this->getTypes($field, $value, $this->class)];
|
||||
if ($operator === Comparison::IN || $operator === Comparison::NIN) {
|
||||
if (! is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
foreach ($value as $item) {
|
||||
if ($item === null) {
|
||||
/*
|
||||
* Compare this to how \Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getSelectConditionStatementSQL
|
||||
* creates the "[NOT] IN (...)" expression - for NULL values, it does _not_ insert a placeholder in the
|
||||
* SQL and instead adds an extra ... OR ... IS NULL condition. So we need to skip NULL values here as
|
||||
* well to create a parameters list that matches the SQL.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqlParams = [...$sqlParams, ...PersisterHelper::convertToParameterValue($item, $this->em)];
|
||||
$sqlTypes = [...$sqlTypes, ...PersisterHelper::inferParameterTypes($field, $item, $this->class, $this->em)];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqlParams = [...$sqlParams, ...PersisterHelper::convertToParameterValue($value, $this->em)];
|
||||
$sqlTypes = [...$sqlTypes, ...PersisterHelper::inferParameterTypes($field, $value, $this->class, $this->em)];
|
||||
}
|
||||
|
||||
return [$sqlParams, $sqlTypes];
|
||||
@@ -1418,22 +1433,17 @@ class BasicEntityPersister implements EntityPersister
|
||||
|
||||
public function getInsertSQL(): string
|
||||
{
|
||||
if ($this->insertSql !== null) {
|
||||
return $this->insertSql;
|
||||
}
|
||||
|
||||
$columns = $this->getInsertColumnList();
|
||||
$tableName = $this->quoteStrategy->getTableName($this->class, $this->platform);
|
||||
|
||||
if (empty($columns)) {
|
||||
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
|
||||
$this->insertSql = $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
|
||||
if ($columns === []) {
|
||||
$identityColumn = $this->quoteStrategy->getColumnName($this->class->identifier[0], $this->class, $this->platform);
|
||||
|
||||
return $this->insertSql;
|
||||
return $this->platform->getEmptyIdentityInsertSQL($tableName, $identityColumn);
|
||||
}
|
||||
|
||||
$values = [];
|
||||
$columns = array_unique($columns);
|
||||
$placeholders = [];
|
||||
$columns = array_unique($columns);
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$placeholder = '?';
|
||||
@@ -1447,15 +1457,13 @@ class BasicEntityPersister implements EntityPersister
|
||||
$placeholder = $type->convertToDatabaseValueSQL('?', $this->platform);
|
||||
}
|
||||
|
||||
$values[] = $placeholder;
|
||||
$placeholders[] = $placeholder;
|
||||
}
|
||||
|
||||
$columns = implode(', ', $columns);
|
||||
$values = implode(', ', $values);
|
||||
$columns = implode(', ', $columns);
|
||||
$placeholders = implode(', ', $placeholders);
|
||||
|
||||
$this->insertSql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $values);
|
||||
|
||||
return $this->insertSql;
|
||||
return sprintf('INSERT INTO %s (%s) VALUES (%s)', $tableName, $columns, $placeholders);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1621,6 +1629,8 @@ class BasicEntityPersister implements EntityPersister
|
||||
AssociationMapping|null $assoc = null,
|
||||
string|null $comparison = null,
|
||||
): string {
|
||||
$comparison ??= (is_array($value) ? Comparison::IN : Comparison::EQ);
|
||||
|
||||
$selectedColumns = [];
|
||||
$columns = $this->getSelectConditionStatementColumnSQL($field, $assoc);
|
||||
|
||||
@@ -1640,46 +1650,50 @@ class BasicEntityPersister implements EntityPersister
|
||||
$placeholder = $type->convertToDatabaseValueSQL($placeholder, $this->platform);
|
||||
}
|
||||
|
||||
if ($comparison !== null) {
|
||||
// special case null value handling
|
||||
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) {
|
||||
$selectedColumns[] = $column . ' IS NULL';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($comparison === Comparison::NEQ && $value === null) {
|
||||
$selectedColumns[] = $column . ' IS NOT NULL';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
|
||||
// special case null value handling
|
||||
if (($comparison === Comparison::EQ || $comparison === Comparison::IS) && $value === null) {
|
||||
$selectedColumns[] = $column . ' IS NULL';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$in = sprintf('%s IN (%s)', $column, $placeholder);
|
||||
if ($comparison === Comparison::NEQ && $value === null) {
|
||||
$selectedColumns[] = $column . ' IS NOT NULL';
|
||||
|
||||
if (array_search(null, $value, true) !== false) {
|
||||
$selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($comparison === Comparison::IN || $comparison === Comparison::NIN) {
|
||||
if (! is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
|
||||
if ($value === []) {
|
||||
$selectedColumns[] = '1=0';
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedColumns[] = $in;
|
||||
$nullKeys = array_keys($value, null, true);
|
||||
$nonNullValues = array_diff_key($value, array_flip($nullKeys));
|
||||
|
||||
$placeholders = implode(', ', array_fill(0, count($nonNullValues), $placeholder));
|
||||
|
||||
$in = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholders);
|
||||
|
||||
if ($nullKeys) {
|
||||
if ($nonNullValues) {
|
||||
$selectedColumns[] = sprintf('(%s OR %s IS NULL)', $in, $column);
|
||||
} else {
|
||||
$selectedColumns[] = $column . ' IS NULL';
|
||||
}
|
||||
} else {
|
||||
$selectedColumns[] = $in;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$selectedColumns[] = sprintf('%s IS NULL', $column);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedColumns[] = sprintf('%s = %s', $column, $placeholder);
|
||||
$selectedColumns[] = $column . ' ' . sprintf(self::$comparisonMap[$comparison], $placeholder);
|
||||
}
|
||||
|
||||
return implode(' AND ', $selectedColumns);
|
||||
@@ -1871,8 +1885,18 @@ class BasicEntityPersister implements EntityPersister
|
||||
continue; // skip null values.
|
||||
}
|
||||
|
||||
$types = [...$types, ...$this->getTypes($field, $value, $this->class)];
|
||||
$params = array_merge($params, $this->getValues($value));
|
||||
if (is_array($value)) {
|
||||
$nonNullValues = array_diff_key($value, array_flip(array_keys($value, null, true)));
|
||||
foreach ($nonNullValues as $item) {
|
||||
$types = [...$types, ...PersisterHelper::inferParameterTypes($field, $item, $this->class, $this->em)];
|
||||
$params = [...$params, ...PersisterHelper::convertToParameterValue($item, $this->em)];
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$types = [...$types, ...PersisterHelper::inferParameterTypes($field, $value, $this->class, $this->em)];
|
||||
$params = [...$params, ...PersisterHelper::convertToParameterValue($value, $this->em)];
|
||||
}
|
||||
|
||||
return [$params, $types];
|
||||
@@ -1900,130 +1924,13 @@ class BasicEntityPersister implements EntityPersister
|
||||
continue; // skip null values.
|
||||
}
|
||||
|
||||
$types = [...$types, ...$this->getTypes($criterion['field'], $criterion['value'], $criterion['class'])];
|
||||
$params = array_merge($params, $this->getValues($criterion['value']));
|
||||
$types = [...$types, ...PersisterHelper::inferParameterTypes($criterion['field'], $criterion['value'], $criterion['class'], $this->em)];
|
||||
$params = [...$params, ...PersisterHelper::convertToParameterValue($criterion['value'], $this->em)];
|
||||
}
|
||||
|
||||
return [$params, $types];
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers field types to be used by parameter type casting.
|
||||
*
|
||||
* @return list<ParameterType|ArrayParameterType|int|string>
|
||||
* @phpstan-return list<ParameterType::*|ArrayParameterType::*|string>
|
||||
*
|
||||
* @throws QueryException
|
||||
*/
|
||||
private function getTypes(string $field, mixed $value, ClassMetadata $class): array
|
||||
{
|
||||
$types = [];
|
||||
|
||||
switch (true) {
|
||||
case isset($class->fieldMappings[$field]):
|
||||
$types = array_merge($types, [$class->fieldMappings[$field]->type]);
|
||||
break;
|
||||
|
||||
case isset($class->associationMappings[$field]):
|
||||
$assoc = $this->em->getMetadataFactory()->getOwningSide($class->associationMappings[$field]);
|
||||
$class = $this->em->getClassMetadata($assoc->targetEntity);
|
||||
|
||||
if ($assoc->isManyToManyOwningSide()) {
|
||||
$columns = $assoc->relationToTargetKeyColumns;
|
||||
} else {
|
||||
assert($assoc->isToOneOwningSide());
|
||||
$columns = $assoc->sourceToTargetKeyColumns;
|
||||
}
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$types[] = PersisterHelper::getTypeOfColumn($column, $class, $this->em);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$types[] = ParameterType::STRING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return array_map($this->getArrayBindingType(...), $types);
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/** @phpstan-return ArrayParameterType::* */
|
||||
private function getArrayBindingType(ParameterType|int|string $type): ArrayParameterType|int
|
||||
{
|
||||
if (! $type instanceof ParameterType) {
|
||||
$type = Type::getType((string) $type)->getBindingType();
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
ParameterType::STRING => ArrayParameterType::STRING,
|
||||
ParameterType::INTEGER => ArrayParameterType::INTEGER,
|
||||
ParameterType::ASCII => ArrayParameterType::ASCII,
|
||||
ParameterType::BINARY => ArrayParameterType::BINARY,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the parameters that identifies a value.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function getValues(mixed $value): array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$newValue = [];
|
||||
|
||||
foreach ($value as $itemValue) {
|
||||
$newValue = array_merge($newValue, $this->getValues($itemValue));
|
||||
}
|
||||
|
||||
return [$newValue];
|
||||
}
|
||||
|
||||
return $this->getIndividualValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an individual parameter value.
|
||||
*
|
||||
* @phpstan-return list<mixed>
|
||||
*/
|
||||
private function getIndividualValue(mixed $value): array
|
||||
{
|
||||
if (! is_object($value)) {
|
||||
return [$value];
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return [$value->value];
|
||||
}
|
||||
|
||||
$valueClass = DefaultProxyClassNameResolver::getClass($value);
|
||||
|
||||
if ($this->em->getMetadataFactory()->isTransient($valueClass)) {
|
||||
return [$value];
|
||||
}
|
||||
|
||||
$class = $this->em->getClassMetadata($valueClass);
|
||||
|
||||
if ($class->isIdentifierComposite) {
|
||||
$newValue = [];
|
||||
|
||||
foreach ($class->getIdentifierValues($value) as $innerValue) {
|
||||
$newValue = array_merge($newValue, $this->getValues($innerValue));
|
||||
}
|
||||
|
||||
return $newValue;
|
||||
}
|
||||
|
||||
return [$this->em->getUnitOfWork()->getSingleIdentifierValue($value)];
|
||||
}
|
||||
|
||||
public function exists(object $entity, Criteria|null $extraConditions = null): bool
|
||||
{
|
||||
$criteria = $this->class->getIdentifierValues($entity);
|
||||
|
||||
@@ -69,7 +69,7 @@ interface EntityPersister
|
||||
/**
|
||||
* Expands the parameters from the given criteria and use the correct binding types if found.
|
||||
*
|
||||
* @param string[] $criteria
|
||||
* @param array<string, mixed> $criteria
|
||||
*
|
||||
* @phpstan-return array{list<mixed>, list<ParameterType::*|ArrayParameterType::*|string>}
|
||||
*/
|
||||
|
||||
@@ -61,7 +61,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
*/
|
||||
private function getVersionedClassMetadata(): ClassMetadata
|
||||
{
|
||||
if (isset($this->class->fieldMappings[$this->class->versionField]->inherited)) {
|
||||
if ($this->class->versionField !== null && isset($this->class->fieldMappings[$this->class->versionField]->inherited)) {
|
||||
$definingClassName = $this->class->fieldMappings[$this->class->versionField]->inherited;
|
||||
|
||||
return $this->em->getClassMetadata($definingClassName);
|
||||
@@ -134,7 +134,7 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
// Execute all inserts. For each entity:
|
||||
// 1) Insert on root table
|
||||
// 2) Insert on sub tables
|
||||
foreach ($this->queuedInserts as $entity) {
|
||||
foreach ($this->queuedInserts as $key => $entity) {
|
||||
$insertData = $this->prepareInsertData($entity);
|
||||
|
||||
// Execute insert on root table
|
||||
@@ -179,9 +179,16 @@ class JoinedSubclassPersister extends AbstractEntityInheritancePersister
|
||||
if ($this->class->requiresFetchAfterChange) {
|
||||
$this->assignDefaultVersionAndUpsertableValues($entity, $id);
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedInserts = [];
|
||||
// Unset this queued insert, so that the prepareUpdateData() method (called via prepareInsertData() method)
|
||||
// knows right away (for the next entity already) that the current entity has been written to the database
|
||||
// and no extra updates need to be scheduled to refer to it.
|
||||
//
|
||||
// In \Doctrine\ORM\UnitOfWork::executeInserts(), the UoW already removed entities
|
||||
// from its own list (\Doctrine\ORM\UnitOfWork::$entityInsertions) right after they
|
||||
// were given to our addInsert() method.
|
||||
unset($this->queuedInserts[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(object $entity): void
|
||||
|
||||
@@ -37,6 +37,7 @@ use function is_dir;
|
||||
use function is_int;
|
||||
use function is_writable;
|
||||
use function ltrim;
|
||||
use function method_exists;
|
||||
use function mkdir;
|
||||
use function preg_match_all;
|
||||
use function random_bytes;
|
||||
@@ -161,6 +162,11 @@ EOPHP;
|
||||
);
|
||||
}
|
||||
|
||||
// @phpstan-ignore function.impossibleType (This method has been removed in Symfony 8)
|
||||
if (! method_exists(ProxyHelper::class, 'generateLazyGhost')) {
|
||||
throw ORMInvalidArgumentException::lazyGhostUnavailable();
|
||||
}
|
||||
|
||||
if (! $proxyDir) {
|
||||
throw ORMInvalidArgumentException::proxyDirectoryRequired();
|
||||
}
|
||||
@@ -464,7 +470,7 @@ EOPHP;
|
||||
|
||||
private function generateUseLazyGhostTrait(ClassMetadata $class): string
|
||||
{
|
||||
// @phpstan-ignore staticMethod.deprecated (Because we support Symfony < 7.3)
|
||||
// @phpstan-ignore staticMethod.notFound (This method has been removed in Symfony 8)
|
||||
$code = ProxyHelper::generateLazyGhost($class->getReflectionClass());
|
||||
$code = substr($code, 7 + (int) strpos($code, "\n{"));
|
||||
$code = substr($code, 0, (int) strpos($code, "\n}"));
|
||||
|
||||
@@ -16,7 +16,11 @@ class EntityAsDtoArgumentExpression extends Node
|
||||
public function __construct(
|
||||
public mixed $expression,
|
||||
public string|null $identificationVariable,
|
||||
public string|null $aliasVariable = null,
|
||||
) {
|
||||
if (! $aliasVariable) {
|
||||
$this->aliasVariable = $expression;
|
||||
}
|
||||
}
|
||||
|
||||
public function dispatch(SqlWalker $walker): string
|
||||
|
||||
@@ -1147,7 +1147,7 @@ final class Parser
|
||||
];
|
||||
}
|
||||
|
||||
return new AST\EntityAsDtoArgumentExpression($expression, $identVariable);
|
||||
return new AST\EntityAsDtoArgumentExpression($expression, $identVariable, $aliasResultVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1895,6 +1895,7 @@ final class Parser
|
||||
$expression = $this->NewObjectExpression();
|
||||
} elseif ($token->type === TokenType::T_IDENTIFIER && $peek->type !== TokenType::T_DOT && $peek->type !== TokenType::T_OPEN_PARENTHESIS) {
|
||||
$expression = $this->EntityAsDtoArgumentExpression();
|
||||
$fieldAlias = $expression->aliasVariable;
|
||||
} else {
|
||||
$expression = $this->ScalarExpression();
|
||||
}
|
||||
|
||||
@@ -1412,7 +1412,9 @@ class SqlWalker
|
||||
|
||||
$sqlParts[] = $col . ' AS ' . $columnAlias;
|
||||
|
||||
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
|
||||
if ($resultAlias !== null) {
|
||||
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
|
||||
}
|
||||
|
||||
$this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
|
||||
|
||||
@@ -1445,7 +1447,9 @@ class SqlWalker
|
||||
|
||||
$sqlParts[] = $col . ' AS ' . $columnAlias;
|
||||
|
||||
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
|
||||
if ($resultAlias !== null) {
|
||||
$this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
|
||||
}
|
||||
|
||||
$this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
|
||||
}
|
||||
|
||||
@@ -18,11 +18,12 @@ trait ApplicationCompatibility
|
||||
{
|
||||
private static function addCommandToApplication(Application $application, Command $command): Command|null
|
||||
{
|
||||
// @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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,9 @@ class LimitSubqueryOutputWalker extends SqlOutputWalker
|
||||
$selectAliasToExpressionMap = [];
|
||||
// Get any aliases that are available for select expressions.
|
||||
foreach ($AST->selectClause->selectExpressions as $selectExpression) {
|
||||
$selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
|
||||
if ($selectExpression->fieldIdentificationVariable !== null) {
|
||||
$selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild string orderby expressions to use the select expression they're referencing
|
||||
|
||||
@@ -202,7 +202,22 @@ class Paginator implements Countable, IteratorAggregate
|
||||
*/
|
||||
private function getCountQuery(): Query
|
||||
{
|
||||
$countQuery = $this->cloneQuery($this->query);
|
||||
/*
|
||||
As opposed to using self::cloneQuery, the following code does not transfer
|
||||
a potentially existing result set mapping (either set directly by the user,
|
||||
or taken from the parser result from a previous invocation of Query::parse())
|
||||
to the new query object. This is fine, since we are going to completely change the
|
||||
select clause, so a previously existing result set mapping (RSM) is probably wrong anyway.
|
||||
In the case of using output walkers, we are even creating a new RSM down below.
|
||||
In the case of using a tree walker, we want to have a new RSM created by the parser.
|
||||
*/
|
||||
$countQuery = new Query($this->query->getEntityManager());
|
||||
$countQuery->setDQL($this->query->getDQL());
|
||||
$countQuery->setParameters(clone $this->query->getParameters());
|
||||
$countQuery->setCacheable(false);
|
||||
foreach ($this->query->getHints() as $name => $value) {
|
||||
$countQuery->setHint($name, $value);
|
||||
}
|
||||
|
||||
if (! $countQuery->hasHint(CountWalker::HINT_DISTINCT)) {
|
||||
$countQuery->setHint(CountWalker::HINT_DISTINCT, true);
|
||||
|
||||
@@ -75,12 +75,6 @@ class ResolveTargetEntityListener implements EventSubscriber
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->resolveTargetEntities as $interface => $data) {
|
||||
if ($data['targetEntity'] === $cm->getName()) {
|
||||
$args->getEntityManager()->getMetadataFactory()->setMetadataFor($interface, $cm);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($cm->discriminatorMap as $value => $class) {
|
||||
if (isset($this->resolveTargetEntities[$class])) {
|
||||
$cm->addDiscriminatorMapClass($value, $this->resolveTargetEntities[$class]['targetEntity']);
|
||||
|
||||
@@ -9,14 +9,20 @@ use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\AbstractAsset;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\ComparatorConfig;
|
||||
use Doctrine\DBAL\Schema\DefaultExpression;
|
||||
use Doctrine\DBAL\Schema\DefaultExpression\CurrentDate;
|
||||
use Doctrine\DBAL\Schema\DefaultExpression\CurrentTime;
|
||||
use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraintEditor;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Index\IndexedColumn;
|
||||
use Doctrine\DBAL\Schema\Name\Identifier;
|
||||
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
|
||||
use Doctrine\DBAL\Schema\NamedObject;
|
||||
use Doctrine\DBAL\Schema\PrimaryKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\AssociationMapping;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
@@ -38,14 +44,17 @@ use function array_filter;
|
||||
use function array_flip;
|
||||
use function array_intersect_key;
|
||||
use function array_map;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function current;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function interface_exists;
|
||||
use function is_numeric;
|
||||
use function method_exists;
|
||||
use function preg_match;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
@@ -486,6 +495,34 @@ class SchemaTool
|
||||
// the 'default' option can be overwritten here
|
||||
$options = $this->gatherColumnOptions($mapping) + $options;
|
||||
|
||||
if (isset($options['default']) && interface_exists(DefaultExpression::class)) {
|
||||
if (
|
||||
in_array($mapping->type, [
|
||||
Types::DATETIME_MUTABLE,
|
||||
Types::DATETIME_IMMUTABLE,
|
||||
Types::DATETIMETZ_MUTABLE,
|
||||
Types::DATETIMETZ_IMMUTABLE,
|
||||
], true)
|
||||
&& $options['default'] === $this->platform->getCurrentTimestampSQL()
|
||||
) {
|
||||
$options['default'] = new CurrentTimestamp();
|
||||
}
|
||||
|
||||
if (
|
||||
in_array($mapping->type, [Types::TIME_MUTABLE, Types::TIME_IMMUTABLE], true)
|
||||
&& $options['default'] === $this->platform->getCurrentTimeSQL()
|
||||
) {
|
||||
$options['default'] = new CurrentTime();
|
||||
}
|
||||
|
||||
if (
|
||||
in_array($mapping->type, [Types::DATE_MUTABLE, Types::DATE_IMMUTABLE], true)
|
||||
&& $options['default'] === $this->platform->getCurrentDateSQL()
|
||||
) {
|
||||
$options['default'] = new CurrentDate();
|
||||
}
|
||||
}
|
||||
|
||||
if ($class->isIdGeneratorIdentity() && $class->getIdentifierFieldNames() === [$mapping->fieldName]) {
|
||||
$options['autoincrement'] = true;
|
||||
}
|
||||
@@ -735,7 +772,7 @@ class SchemaTool
|
||||
$theJoinTable->addUniqueIndex($unique['columns'], is_numeric($indexName) ? null : $indexName);
|
||||
}
|
||||
|
||||
$compositeName = $theJoinTable->getName() . '.' . implode('', $localColumns);
|
||||
$compositeName = $this->getAssetName($theJoinTable) . '.' . implode('', $localColumns);
|
||||
if (
|
||||
isset($addedFks[$compositeName])
|
||||
&& ($foreignTableName !== $addedFks[$compositeName]['foreignTableName']
|
||||
@@ -859,15 +896,15 @@ class SchemaTool
|
||||
$deployedSchema = $this->schemaManager->introspectSchema();
|
||||
|
||||
foreach ($schema->getTables() as $table) {
|
||||
if (! $deployedSchema->hasTable($table->getName())) {
|
||||
$schema->dropTable($table->getName());
|
||||
if (! $deployedSchema->hasTable($this->getAssetName($table))) {
|
||||
$schema->dropTable($this->getAssetName($table));
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->platform->supportsSequences()) {
|
||||
foreach ($schema->getSequences() as $sequence) {
|
||||
if (! $deployedSchema->hasSequence($sequence->getName())) {
|
||||
$schema->dropSequence($sequence->getName());
|
||||
if (! $deployedSchema->hasSequence($this->getAssetName($sequence))) {
|
||||
$schema->dropSequence($this->getAssetName($sequence));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,7 +926,7 @@ class SchemaTool
|
||||
}
|
||||
|
||||
if (count($columns) === 1) {
|
||||
$checkSequence = $table->getName() . '_' . $columns[0] . '_seq';
|
||||
$checkSequence = $this->getAssetName($table) . '_' . $columns[0] . '_seq';
|
||||
if ($deployedSchema->hasSequence($checkSequence) && ! $schema->hasSequence($checkSequence)) {
|
||||
$schema->createSequence($checkSequence);
|
||||
}
|
||||
@@ -955,8 +992,9 @@ class SchemaTool
|
||||
}
|
||||
|
||||
// whitelist assets we already know about in $toSchema, use the existing filter otherwise
|
||||
$config->setSchemaAssetsFilter(static function ($asset) use ($previousFilter, $toSchema): bool {
|
||||
$assetName = $asset instanceof AbstractAsset ? $asset->getName() : $asset;
|
||||
$getAssetName = $this->getAssetName(...);
|
||||
$config->setSchemaAssetsFilter(static function ($asset) use ($previousFilter, $toSchema, $getAssetName): bool {
|
||||
$assetName = $asset instanceof AbstractAsset ? $getAssetName($asset) : $asset;
|
||||
|
||||
return $toSchema->hasTable($assetName) || $toSchema->hasSequence($assetName) || $previousFilter($asset);
|
||||
});
|
||||
@@ -969,20 +1007,26 @@ class SchemaTool
|
||||
}
|
||||
}
|
||||
|
||||
/** @param string[] $primaryKeyColumns */
|
||||
/** @param non-empty-array<non-empty-string> $primaryKeyColumns */
|
||||
private function addPrimaryKeyConstraint(Table $table, array $primaryKeyColumns): void
|
||||
{
|
||||
if (class_exists(PrimaryKeyConstraint::class)) {
|
||||
$primaryKeyColumnNames = [];
|
||||
if (! class_exists(PrimaryKeyConstraint::class)) {
|
||||
$table->setPrimaryKey(array_values($primaryKeyColumns));
|
||||
|
||||
foreach ($primaryKeyColumns as $primaryKeyColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
$primaryKeyColumnNames = [];
|
||||
|
||||
foreach ($primaryKeyColumns as $primaryKeyColumn) {
|
||||
if (preg_match('/^"(.+)"$/', $primaryKeyColumn, $matches) === 1) {
|
||||
$primaryKeyColumnNames[] = new UnqualifiedName(Identifier::quoted($matches[1]));
|
||||
} else {
|
||||
$primaryKeyColumnNames[] = new UnqualifiedName(Identifier::unquoted($primaryKeyColumn));
|
||||
}
|
||||
|
||||
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, $primaryKeyColumnNames, true));
|
||||
} else {
|
||||
$table->setPrimaryKey($primaryKeyColumns);
|
||||
}
|
||||
|
||||
$table->addPrimaryKeyConstraint(new PrimaryKeyConstraint(null, $primaryKeyColumnNames, true));
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
@@ -994,4 +1038,12 @@ class SchemaTool
|
||||
|
||||
return $index->getColumns();
|
||||
}
|
||||
|
||||
private function getAssetName(AbstractAsset $asset): string
|
||||
{
|
||||
return $asset instanceof NamedObject
|
||||
? $asset->getObjectName()->toString()
|
||||
// @phpstan-ignore method.deprecated (DBAL < 4.4)
|
||||
: $asset->getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ use function array_map;
|
||||
use function array_sum;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function current;
|
||||
use function get_debug_type;
|
||||
use function implode;
|
||||
@@ -934,7 +935,9 @@ class UnitOfWork implements PropertyChangedListener
|
||||
|
||||
$this->entityStates[$oid] = self::STATE_MANAGED;
|
||||
|
||||
$this->scheduleForInsert($entity);
|
||||
if (! isset($this->entityInsertions[$oid])) {
|
||||
$this->scheduleForInsert($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param mixed[] $idValue */
|
||||
@@ -2592,8 +2595,14 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$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 (! $isIteration && $assoc->isOneToMany() && ! $targetClass->isIdentifierComposite && ! $assoc->isIndexed()) {
|
||||
if (
|
||||
$assoc->isOneToMany()
|
||||
// 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)
|
||||
&& ! $assoc->isIndexed()
|
||||
) {
|
||||
$this->scheduleCollectionForBatchLoading($pColl, $class);
|
||||
} else {
|
||||
$this->loadCollection($pColl);
|
||||
|
||||
@@ -4,11 +4,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Utility;
|
||||
|
||||
use BackedEnum;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
@@ -105,4 +115,121 @@ class PersisterHelper
|
||||
$class->getName(),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers field types to be used by parameter type casting.
|
||||
*
|
||||
* @return list<ParameterType|int|string>
|
||||
* @phpstan-return list<ParameterType::*|ArrayParameterType::*|string>
|
||||
*
|
||||
* @throws QueryException
|
||||
*/
|
||||
public static function inferParameterTypes(
|
||||
string $field,
|
||||
mixed $value,
|
||||
ClassMetadata $class,
|
||||
EntityManagerInterface $em,
|
||||
): array {
|
||||
$types = [];
|
||||
|
||||
switch (true) {
|
||||
case isset($class->fieldMappings[$field]):
|
||||
$types = array_merge($types, [$class->fieldMappings[$field]->type]);
|
||||
break;
|
||||
|
||||
case isset($class->associationMappings[$field]):
|
||||
$assoc = $em->getMetadataFactory()->getOwningSide($class->associationMappings[$field]);
|
||||
$class = $em->getClassMetadata($assoc->targetEntity);
|
||||
|
||||
if ($assoc->isManyToManyOwningSide()) {
|
||||
$columns = $assoc->relationToTargetKeyColumns;
|
||||
} else {
|
||||
assert($assoc->isToOneOwningSide());
|
||||
$columns = $assoc->sourceToTargetKeyColumns;
|
||||
}
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$types[] = self::getTypeOfColumn($column, $class, $em);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
$types[] = ParameterType::STRING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return array_map(self::getArrayBindingType(...), $types);
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/** @phpstan-return ArrayParameterType::* */
|
||||
private static function getArrayBindingType(ParameterType|int|string $type): ArrayParameterType|int
|
||||
{
|
||||
if (! $type instanceof ParameterType) {
|
||||
$type = Type::getType((string) $type)->getBindingType();
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
ParameterType::STRING => ArrayParameterType::STRING,
|
||||
ParameterType::INTEGER => ArrayParameterType::INTEGER,
|
||||
ParameterType::ASCII => ArrayParameterType::ASCII,
|
||||
ParameterType::BINARY => ArrayParameterType::BINARY,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a value to the type and value required to bind it as a parameter.
|
||||
*
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public static function convertToParameterValue(mixed $value, EntityManagerInterface $em): array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
$newValue = [];
|
||||
|
||||
foreach ($value as $itemValue) {
|
||||
$newValue = array_merge($newValue, self::convertToParameterValue($itemValue, $em));
|
||||
}
|
||||
|
||||
return [$newValue];
|
||||
}
|
||||
|
||||
return self::convertIndividualValue($value, $em);
|
||||
}
|
||||
|
||||
/** @phpstan-return list<mixed> */
|
||||
private static function convertIndividualValue(mixed $value, EntityManagerInterface $em): array
|
||||
{
|
||||
if (! is_object($value)) {
|
||||
return [$value];
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return [$value->value];
|
||||
}
|
||||
|
||||
$valueClass = DefaultProxyClassNameResolver::getClass($value);
|
||||
|
||||
if ($em->getMetadataFactory()->isTransient($valueClass)) {
|
||||
return [$value];
|
||||
}
|
||||
|
||||
$class = $em->getClassMetadata($valueClass);
|
||||
|
||||
if ($class->isIdentifierComposite) {
|
||||
$newValue = [];
|
||||
|
||||
foreach ($class->getIdentifierValues($value) as $innerValue) {
|
||||
$newValue = array_merge($newValue, self::convertToParameterValue($innerValue, $em));
|
||||
}
|
||||
|
||||
return $newValue;
|
||||
}
|
||||
|
||||
return [$em->getUnitOfWork()->getSingleIdentifierValue($value)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH12063 extends OrmFunctionalTestCase
|
||||
class GH12063Test extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
@@ -67,7 +67,7 @@ enum GH12063Code: string
|
||||
class GH12063Association
|
||||
{
|
||||
#[Id]
|
||||
#[Column]
|
||||
#[Column(length: 3)]
|
||||
public GH12063Code $code;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,6 @@ class GH12063Entity
|
||||
public int|null $id = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(referencedColumnName: 'code')]
|
||||
#[ORM\JoinColumn(referencedColumnName: 'code', options: ['length' => 3])]
|
||||
public GH12063Association $association;
|
||||
}
|
||||
@@ -13,6 +13,8 @@ class CmsUserDTONamedArgs
|
||||
public int|null $phonenumbers = null,
|
||||
public CmsAddressDTO|null $addressDto = null,
|
||||
public CmsAddressDTONamedArgs|null $addressDtoNamedArgs = null,
|
||||
public CmsDumbDTO|null $dumb = null,
|
||||
public CmsAddress|null $addressEntity = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ class CmsUserDTOVariadicArg
|
||||
public string|null $email = null;
|
||||
public string|null $address = null;
|
||||
public int|null $phonenumbers = null;
|
||||
public array $otherProperties = [];
|
||||
|
||||
public function __construct(...$args)
|
||||
{
|
||||
$this->name = $args['name'] ?? null;
|
||||
$this->email = $args['email'] ?? null;
|
||||
$this->phonenumbers = $args['phonenumbers'] ?? null;
|
||||
$this->address = $args['address'] ?? null;
|
||||
$this->name = $args['name'] ?? null;
|
||||
$this->email = $args['email'] ?? null;
|
||||
$this->phonenumbers = $args['phonenumbers'] ?? null;
|
||||
$this->address = $args['address'] ?? null;
|
||||
$this->otherProperties = $args;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,16 @@ class RootEntity
|
||||
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevel::class, fetch: 'EAGER')]
|
||||
private Collection $secondLevel;
|
||||
|
||||
/** @var Collection<int, SecondLevelWithoutCompositePrimaryKey> */
|
||||
#[ORM\OneToMany(mappedBy: 'root', targetEntity: SecondLevelWithoutCompositePrimaryKey::class, fetch: 'EAGER')]
|
||||
private Collection $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|null
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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)]
|
||||
private int|null $id;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: RootEntity::class, inversedBy: 'anotherSecondLevel')]
|
||||
#[ORM\JoinColumn(name: 'root_id', referencedColumnName: 'id')]
|
||||
#[ORM\JoinColumn(name: 'root_other_key', referencedColumnName: 'other_key')]
|
||||
private RootEntity $root;
|
||||
|
||||
public function __construct(RootEntity $upper)
|
||||
{
|
||||
$this->root = $upper;
|
||||
}
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
30
tests/Tests/Models/Enums/BookCategory.php
Normal file
30
tests/Tests/Models/Enums/BookCategory.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Enums;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToMany;
|
||||
|
||||
#[Entity]
|
||||
class BookCategory
|
||||
{
|
||||
#[Id]
|
||||
#[Column]
|
||||
#[GeneratedValue]
|
||||
public int $id;
|
||||
|
||||
#[ManyToMany(targetEntity: BookWithGenre::class, mappedBy: 'categories')]
|
||||
public Collection $books;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->books = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
11
tests/Tests/Models/Enums/BookGenre.php
Normal file
11
tests/Tests/Models/Enums/BookGenre.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Enums;
|
||||
|
||||
enum BookGenre: string
|
||||
{
|
||||
case FICTION = 'fiction';
|
||||
case NON_FICTION = 'non fiction';
|
||||
}
|
||||
38
tests/Tests/Models/Enums/BookWithGenre.php
Normal file
38
tests/Tests/Models/Enums/BookWithGenre.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Enums;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToMany;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
|
||||
#[Entity]
|
||||
class BookWithGenre
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
#[Column]
|
||||
public int $id;
|
||||
|
||||
#[ManyToOne(targetEntity: Library::class, inversedBy: 'books')]
|
||||
public Library $library;
|
||||
|
||||
#[Column(enumType: BookGenre::class)]
|
||||
public BookGenre $genre;
|
||||
|
||||
#[ManyToMany(targetEntity: BookCategory::class, inversedBy: 'books')]
|
||||
public Collection $categories;
|
||||
|
||||
public function __construct(BookGenre $genre)
|
||||
{
|
||||
$this->genre = $genre;
|
||||
$this->categories = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
32
tests/Tests/Models/Enums/Library.php
Normal file
32
tests/Tests/Models/Enums/Library.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\Models\Enums;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
#[Entity]
|
||||
#[Table('`library`')]
|
||||
class Library
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
#[Column]
|
||||
public int $id;
|
||||
|
||||
#[OneToMany(targetEntity: BookWithGenre::class, mappedBy: 'library')]
|
||||
public Collection $books;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->books = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
@@ -21,8 +21,12 @@ class InversedManyToManyEntity
|
||||
#[Id]
|
||||
public $id1;
|
||||
|
||||
/** @var string */
|
||||
#[Column(type: 'rot13', length: 255, nullable: true)]
|
||||
public $field = null;
|
||||
|
||||
/** @phpstan-var Collection<int, OwningManyToManyEntity> */
|
||||
#[ManyToMany(targetEntity: 'OwningManyToManyEntity', mappedBy: 'associatedEntities')]
|
||||
#[ManyToMany(targetEntity: OwningManyToManyEntity::class, mappedBy: 'associatedEntities')]
|
||||
public $associatedEntities;
|
||||
|
||||
public function __construct()
|
||||
|
||||
@@ -26,7 +26,7 @@ class InversedOneToManyEntity
|
||||
public $associatedEntities;
|
||||
|
||||
/** @var string */
|
||||
#[Column(type: 'string', name: 'some_property', length: 255)]
|
||||
#[Column(type: 'string', name: 'some_property', length: 255, nullable: true)]
|
||||
public $someProperty;
|
||||
|
||||
public function __construct()
|
||||
|
||||
@@ -24,6 +24,10 @@ class OwningManyToManyEntity
|
||||
#[Id]
|
||||
public $id2;
|
||||
|
||||
/** @var string */
|
||||
#[Column(type: 'rot13', length: 255, nullable: true)]
|
||||
public $field = null;
|
||||
|
||||
/** @var Collection<int, InversedManyToManyEntity> */
|
||||
#[JoinTable(name: 'vct_xref_manytomany')]
|
||||
#[JoinColumn(name: 'owning_id', referencedColumnName: 'id2')]
|
||||
|
||||
@@ -20,8 +20,12 @@ class OwningManyToOneEntity
|
||||
#[Id]
|
||||
public $id2;
|
||||
|
||||
/** @var string */
|
||||
#[Column(type: 'rot13', length: 255, nullable: true)]
|
||||
public $field = null;
|
||||
|
||||
/** @var InversedOneToManyEntity */
|
||||
#[ManyToOne(targetEntity: 'InversedOneToManyEntity', inversedBy: 'associatedEntities')]
|
||||
#[ManyToOne(targetEntity: InversedOneToManyEntity::class, inversedBy: 'associatedEntities')]
|
||||
#[JoinColumn(name: 'associated_id', referencedColumnName: 'id1')]
|
||||
public $associatedEntity;
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ abstract class EntityPersisterTestCase extends OrmTestCase
|
||||
public function testInvokeExpandCriteriaParameters(): void
|
||||
{
|
||||
$persister = $this->createPersisterDefault();
|
||||
$criteria = new Criteria();
|
||||
$criteria = Criteria::create(true);
|
||||
|
||||
$this->entityPersister->expects(self::once())
|
||||
->method('expandCriteriaParameters')
|
||||
@@ -320,7 +320,7 @@ abstract class EntityPersisterTestCase extends OrmTestCase
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$persister = $this->createPersisterDefault();
|
||||
$entity = new Country('Foo');
|
||||
$criteria = new Criteria();
|
||||
$criteria = Criteria::create(true);
|
||||
|
||||
$this->em->getUnitOfWork()->registerManaged($entity, ['id' => 1], ['id' => 1, 'name' => 'Foo']);
|
||||
$rsm->addEntityResult(Country::class, 'c');
|
||||
|
||||
@@ -18,8 +18,8 @@ use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Tests\Models\DDC753\DDC753CustomRepository;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
|
||||
use PHPUnit\Framework\Attributes\RequiresPhp;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
@@ -39,7 +39,7 @@ class ConfigurationTest extends TestCase
|
||||
$this->configuration = new Configuration();
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
#[IgnoreDeprecations]
|
||||
public function testSetGetProxyDir(): void
|
||||
{
|
||||
self::assertNull($this->configuration->getProxyDir()); // defaults
|
||||
@@ -48,7 +48,7 @@ class ConfigurationTest extends TestCase
|
||||
self::assertSame(__DIR__, $this->configuration->getProxyDir());
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
#[IgnoreDeprecations]
|
||||
public function testSetGetAutoGenerateProxyClasses(): void
|
||||
{
|
||||
self::assertSame(ProxyFactory::AUTOGENERATE_ALWAYS, $this->configuration->getAutoGenerateProxyClasses()); // defaults
|
||||
@@ -63,7 +63,7 @@ class ConfigurationTest extends TestCase
|
||||
self::assertSame(ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS, $this->configuration->getAutoGenerateProxyClasses());
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
#[IgnoreDeprecations]
|
||||
public function testSetGetProxyNamespace(): void
|
||||
{
|
||||
self::assertNull($this->configuration->getProxyNamespace()); // defaults
|
||||
@@ -222,7 +222,7 @@ class ConfigurationTest extends TestCase
|
||||
}
|
||||
|
||||
#[RequiresPhp('8.4')]
|
||||
#[WithoutErrorHandler]
|
||||
#[IgnoreDeprecations]
|
||||
public function testDisablingNativeLazyObjectsIsDeprecated(): void
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/12005');
|
||||
|
||||
@@ -494,13 +494,13 @@ class ClassTableInheritanceTest extends OrmFunctionalTestCase
|
||||
$this->_em->flush();
|
||||
|
||||
$repository = $this->_em->getRepository(CompanyEmployee::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('department', 'IT'),
|
||||
));
|
||||
self::assertCount(1, $users);
|
||||
|
||||
$repository = $this->_em->getRepository(CompanyManager::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('department', 'IT'),
|
||||
));
|
||||
self::assertCount(1, $users);
|
||||
|
||||
43
tests/Tests/ORM/Functional/DefaultTimeExpressionTest.php
Normal file
43
tests/Tests/ORM/Functional/DefaultTimeExpressionTest.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class DefaultTimeExpressionTest extends OrmFunctionalTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
public function testUsingTimeRelatedDefaultExpressionCausesNoDbalDeprecation(): void
|
||||
{
|
||||
$this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/dbal/pull/7195');
|
||||
|
||||
$this->createSchemaForModels(TimeEntity::class);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class TimeEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
public int $id;
|
||||
|
||||
#[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'])]
|
||||
public DateTime $createdAt;
|
||||
|
||||
#[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'])]
|
||||
public DateTimeImmutable $createdAtImmutable;
|
||||
|
||||
#[ORM\Column(options: ['default' => 'CURRENT_TIME'])]
|
||||
public DateTime $createdTime;
|
||||
|
||||
#[ORM\Column(options: ['default' => 'CURRENT_DATE'])]
|
||||
public DateTime $createdDate;
|
||||
}
|
||||
@@ -6,14 +6,16 @@ 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;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
final class EagerFetchOneToManyWithCompositeKeyTest extends OrmFunctionalTestCase
|
||||
{
|
||||
/** @ticket 11154 */
|
||||
#[Group('GH11154')]
|
||||
public function testItDoesNotThrowAnExceptionWhenTriggeringALoad(): void
|
||||
{
|
||||
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class]);
|
||||
$this->setUpEntitySchema([RootEntity::class, SecondLevel::class, SecondLevelWithoutCompositePrimaryKey::class]);
|
||||
|
||||
$a1 = new RootEntity(1, 'A');
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class EntityRepositoryCriteriaTest extends OrmFunctionalTestCase
|
||||
$this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(DateTimeModel::class);
|
||||
$dates = $repository->matching(new Criteria(
|
||||
$dates = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->lte('datetime', new DateTime('today')),
|
||||
));
|
||||
|
||||
@@ -98,7 +98,7 @@ class EntityRepositoryCriteriaTest extends OrmFunctionalTestCase
|
||||
$this->loadNullFieldFixtures();
|
||||
$repository = $this->_em->getRepository(DateTimeModel::class);
|
||||
|
||||
$dates = $repository->matching(new Criteria(
|
||||
$dates = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->isNull('time'),
|
||||
));
|
||||
|
||||
@@ -110,7 +110,7 @@ class EntityRepositoryCriteriaTest extends OrmFunctionalTestCase
|
||||
$this->loadNullFieldFixtures();
|
||||
$repository = $this->_em->getRepository(DateTimeModel::class);
|
||||
|
||||
$dates = $repository->matching(new Criteria(
|
||||
$dates = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('time', null),
|
||||
));
|
||||
|
||||
@@ -122,7 +122,7 @@ class EntityRepositoryCriteriaTest extends OrmFunctionalTestCase
|
||||
$this->loadNullFieldFixtures();
|
||||
$repository = $this->_em->getRepository(DateTimeModel::class);
|
||||
|
||||
$dates = $repository->matching(new Criteria(
|
||||
$dates = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->neq('time', null),
|
||||
));
|
||||
|
||||
@@ -134,14 +134,14 @@ class EntityRepositoryCriteriaTest extends OrmFunctionalTestCase
|
||||
$this->loadFixture();
|
||||
$repository = $this->_em->getRepository(DateTimeModel::class);
|
||||
|
||||
$dates = $repository->matching(new Criteria());
|
||||
$dates = $repository->matching(Criteria::create(true));
|
||||
|
||||
self::assertFalse($dates->isInitialized());
|
||||
self::assertCount(3, $dates);
|
||||
self::assertFalse($dates->isInitialized());
|
||||
|
||||
// Test it can work even with a constraint
|
||||
$dates = $repository->matching(new Criteria(
|
||||
$dates = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->lte('datetime', new DateTime('today')),
|
||||
));
|
||||
|
||||
@@ -169,7 +169,7 @@ class EntityRepositoryCriteriaTest extends OrmFunctionalTestCase
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria = Criteria::create(true);
|
||||
$criteria->andWhere($criteria->expr()->contains('content', 'Criteria'));
|
||||
|
||||
$user = $this->_em->find(User::class, $user->id);
|
||||
|
||||
@@ -661,7 +661,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria());
|
||||
$users = $repository->matching(Criteria::create(true));
|
||||
|
||||
self::assertCount(4, $users);
|
||||
}
|
||||
@@ -672,7 +672,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('username', 'beberlei'),
|
||||
));
|
||||
|
||||
@@ -685,7 +685,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->neq('username', 'beberlei'),
|
||||
));
|
||||
|
||||
@@ -698,7 +698,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->in('username', ['beberlei', 'gblanco']),
|
||||
));
|
||||
|
||||
@@ -711,7 +711,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->notIn('username', ['beberlei', 'gblanco', 'asm89']),
|
||||
));
|
||||
|
||||
@@ -724,7 +724,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$firstUserId = $this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->lt('id', $firstUserId + 1),
|
||||
));
|
||||
|
||||
@@ -737,7 +737,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$firstUserId = $this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->lte('id', $firstUserId + 1),
|
||||
));
|
||||
|
||||
@@ -750,7 +750,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$firstUserId = $this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->gt('id', $firstUserId),
|
||||
));
|
||||
|
||||
@@ -763,7 +763,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$firstUserId = $this->loadFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$users = $repository->matching(new Criteria(
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->gte('id', $firstUserId),
|
||||
));
|
||||
|
||||
@@ -777,7 +777,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
|
||||
$user = $this->_em->find(CmsUser::class, $userId);
|
||||
|
||||
$criteria = new Criteria(
|
||||
$criteria = Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('user', $user),
|
||||
);
|
||||
|
||||
@@ -798,7 +798,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
|
||||
$user = $this->_em->find(CmsUser::class, $userId);
|
||||
|
||||
$criteria = new Criteria(
|
||||
$criteria = Criteria::create(true)->where(
|
||||
Criteria::expr()->in('user', [$user]),
|
||||
);
|
||||
|
||||
@@ -818,13 +818,13 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->contains('name', 'Foobar')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(Criteria::expr()->contains('name', 'Foobar')));
|
||||
self::assertCount(0, $users);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->contains('name', 'Rom')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(Criteria::expr()->contains('name', 'Rom')));
|
||||
self::assertCount(1, $users);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->contains('status', 'dev')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(Criteria::expr()->contains('status', 'dev')));
|
||||
self::assertCount(2, $users);
|
||||
}
|
||||
|
||||
@@ -834,13 +834,19 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->startsWith('name', 'Foo')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->startsWith('name', 'Foo'),
|
||||
));
|
||||
self::assertCount(0, $users);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->startsWith('name', 'R')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->startsWith('name', 'R'),
|
||||
));
|
||||
self::assertCount(1, $users);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->startsWith('status', 'de')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->startsWith('status', 'de'),
|
||||
));
|
||||
self::assertCount(2, $users);
|
||||
}
|
||||
|
||||
@@ -850,13 +856,19 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->endsWith('name', 'foo')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->endsWith('name', 'foo'),
|
||||
));
|
||||
self::assertCount(0, $users);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->endsWith('name', 'oman')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->endsWith('name', 'oman'),
|
||||
));
|
||||
self::assertCount(1, $users);
|
||||
|
||||
$users = $repository->matching(new Criteria(Criteria::expr()->endsWith('status', 'ev')));
|
||||
$users = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->endsWith('status', 'ev'),
|
||||
));
|
||||
self::assertCount(2, $users);
|
||||
}
|
||||
|
||||
@@ -866,8 +878,8 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$fixtures = $this->loadFixtureUserEmail();
|
||||
$user = $this->_em->find(CmsUser::class, $fixtures[0]->id);
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$criteriaIsNull = Criteria::create()->where(Criteria::expr()->isNull('email'));
|
||||
$criteriaEqNull = Criteria::create()->where(Criteria::expr()->eq('email', null));
|
||||
$criteriaIsNull = Criteria::create(true)->where(Criteria::expr()->isNull('email'));
|
||||
$criteriaEqNull = Criteria::create(true)->where(Criteria::expr()->eq('email', null));
|
||||
|
||||
$user->setEmail(null);
|
||||
$this->_em->persist($user);
|
||||
@@ -924,7 +936,7 @@ class EntityRepositoryTest extends OrmFunctionalTestCase
|
||||
$this->expectExceptionMessage('Unrecognized field: ');
|
||||
|
||||
$repository = $this->_em->getRepository(CmsUser::class);
|
||||
$result = $repository->matching(new Criteria(
|
||||
$result = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('username = ?; DELETE FROM cms_users; SELECT 1 WHERE 1', 'beberlei'),
|
||||
));
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Expr\Comparison;
|
||||
use Doctrine\DBAL\Types\EnumType;
|
||||
use Doctrine\ORM\AbstractQuery;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
@@ -13,10 +15,14 @@ use Doctrine\ORM\Query\Expr\Func;
|
||||
use Doctrine\ORM\Tools\SchemaTool;
|
||||
use Doctrine\Tests\Models\DataTransferObjects\DtoWithArrayOfEnums;
|
||||
use Doctrine\Tests\Models\DataTransferObjects\DtoWithEnum;
|
||||
use Doctrine\Tests\Models\Enums\BookCategory;
|
||||
use Doctrine\Tests\Models\Enums\BookGenre;
|
||||
use Doctrine\Tests\Models\Enums\BookWithGenre;
|
||||
use Doctrine\Tests\Models\Enums\Card;
|
||||
use Doctrine\Tests\Models\Enums\CardNativeEnum;
|
||||
use Doctrine\Tests\Models\Enums\CardWithDefault;
|
||||
use Doctrine\Tests\Models\Enums\CardWithNullable;
|
||||
use Doctrine\Tests\Models\Enums\Library;
|
||||
use Doctrine\Tests\Models\Enums\Product;
|
||||
use Doctrine\Tests\Models\Enums\Quantity;
|
||||
use Doctrine\Tests\Models\Enums\Scale;
|
||||
@@ -25,6 +31,7 @@ use Doctrine\Tests\Models\Enums\TypedCard;
|
||||
use Doctrine\Tests\Models\Enums\TypedCardNativeEnum;
|
||||
use Doctrine\Tests\Models\Enums\Unit;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
|
||||
use function class_exists;
|
||||
@@ -527,4 +534,68 @@ EXCEPTION
|
||||
|
||||
self::assertSame(Suit::Hearts, $card->suit);
|
||||
}
|
||||
|
||||
#[DataProvider('provideGenreMatchingExpressions')]
|
||||
public function testEnumCollectionMatchingOnOneToMany(Comparison $comparison): void
|
||||
{
|
||||
$this->setUpEntitySchema([BookWithGenre::class, Library::class, BookCategory::class]);
|
||||
|
||||
$library = new Library();
|
||||
|
||||
$fictionBook = new BookWithGenre(BookGenre::FICTION);
|
||||
$fictionBook->library = $library;
|
||||
|
||||
$nonfictionBook = new BookWithGenre(BookGenre::NON_FICTION);
|
||||
$nonfictionBook->library = $library;
|
||||
|
||||
$this->_em->persist($library);
|
||||
$this->_em->persist($nonfictionBook);
|
||||
$this->_em->persist($fictionBook);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$library = $this->_em->find(Library::class, $library->id);
|
||||
self::assertFalse($library->books->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$result = $library->books->matching(Criteria::create(true)->where($comparison));
|
||||
|
||||
self::assertCount(1, $result);
|
||||
self::assertSame($nonfictionBook->id, $result[0]->id);
|
||||
}
|
||||
|
||||
#[DataProvider('provideGenreMatchingExpressions')]
|
||||
public function testEnumCollectionMatchingOnManyToMany(Comparison $comparison): void
|
||||
{
|
||||
$this->setUpEntitySchema([Library::class, BookWithGenre::class, BookCategory::class]);
|
||||
|
||||
$category = new BookCategory();
|
||||
|
||||
$fictionBook = new BookWithGenre(BookGenre::FICTION);
|
||||
$fictionBook->categories->add($category);
|
||||
|
||||
$nonfictionBook = new BookWithGenre(BookGenre::NON_FICTION);
|
||||
$nonfictionBook->categories->add($category);
|
||||
|
||||
$this->_em->persist($category);
|
||||
$this->_em->persist($nonfictionBook);
|
||||
$this->_em->persist($fictionBook);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$category = $this->_em->find(BookCategory::class, $category->id);
|
||||
self::assertFalse($category->books->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$result = $category->books->matching(Criteria::create(true)->where($comparison));
|
||||
|
||||
self::assertCount(1, $result);
|
||||
self::assertSame($nonfictionBook->id, $result[0]->id);
|
||||
}
|
||||
|
||||
public static function provideGenreMatchingExpressions(): Generator
|
||||
{
|
||||
yield [Criteria::expr()->eq('genre', BookGenre::NON_FICTION)];
|
||||
yield [Criteria::expr()->in('genre', [BookGenre::NON_FICTION])];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* Basic many-to-many association tests.
|
||||
@@ -436,7 +437,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
|
||||
$user = $this->_em->find($user::class, $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
$criteria = Criteria::create(true)
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
@@ -476,7 +477,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
|
||||
$user = $this->_em->find($user::class, $user->id);
|
||||
|
||||
$criteria = Criteria::create()
|
||||
$criteria = Criteria::create(true)
|
||||
->orderBy(['name' => class_exists(Order::class) ? Order::Ascending : Criteria::ASC]);
|
||||
|
||||
self::assertEquals(
|
||||
@@ -499,7 +500,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$groups = $user->groups;
|
||||
self::assertFalse($user->groups->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$criteria = Criteria::create()->setMaxResults(1);
|
||||
$criteria = Criteria::create(true)->setMaxResults(1);
|
||||
$result = $groups->matching($criteria);
|
||||
|
||||
self::assertCount(1, $result);
|
||||
@@ -517,7 +518,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$groups = $user->groups;
|
||||
self::assertFalse($user->groups->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$criteria = Criteria::create()->setFirstResult(1);
|
||||
$criteria = Criteria::create(true)->setFirstResult(1);
|
||||
$result = $groups->matching($criteria);
|
||||
|
||||
self::assertCount(1, $result);
|
||||
@@ -538,7 +539,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$groups = $user->groups;
|
||||
self::assertFalse($user->groups->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$criteria = Criteria::create()->setFirstResult(1)->setMaxResults(3);
|
||||
$criteria = Criteria::create(true)->setFirstResult(1)->setMaxResults(3);
|
||||
$result = $groups->matching($criteria);
|
||||
|
||||
self::assertCount(3, $result);
|
||||
@@ -562,7 +563,7 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
$groups = $user->groups;
|
||||
self::assertFalse($user->groups->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) 'Developers_0'));
|
||||
$criteria = Criteria::create(true)->where(Criteria::expr()->eq('name', (string) 'Developers_0'));
|
||||
$result = $groups->matching($criteria);
|
||||
|
||||
self::assertCount(1, $result);
|
||||
@@ -573,6 +574,44 @@ class ManyToManyBasicAssociationTest extends OrmFunctionalTestCase
|
||||
self::assertFalse($user->groups->isInitialized(), 'Post-condition: matching does not initialize collection');
|
||||
}
|
||||
|
||||
public function testMatchingWithInCondition(): void
|
||||
{
|
||||
$user = $this->addCmsUserGblancoWithGroups(2);
|
||||
$this->_em->clear();
|
||||
|
||||
$user = $this->_em->find(get_class($user), $user->id);
|
||||
|
||||
$groups = $user->groups;
|
||||
self::assertFalse($user->groups->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$criteria = Criteria::create(true)->where(Criteria::expr()->in('name', ['Developers_1']));
|
||||
$result = $groups->matching($criteria);
|
||||
|
||||
self::assertCount(1, $result);
|
||||
self::assertEquals('Developers_1', $result[0]->name);
|
||||
|
||||
self::assertFalse($user->groups->isInitialized(), 'Post-condition: matching does not initialize collection');
|
||||
}
|
||||
|
||||
public function testMatchingWithNotInCondition(): void
|
||||
{
|
||||
$user = $this->addCmsUserGblancoWithGroups(2);
|
||||
$this->_em->clear();
|
||||
|
||||
$user = $this->_em->find(get_class($user), $user->id);
|
||||
|
||||
$groups = $user->groups;
|
||||
self::assertFalse($user->groups->isInitialized(), 'Pre-condition: lazy collection');
|
||||
|
||||
$criteria = Criteria::create(true)->where(Criteria::expr()->notIn('name', ['Developers_0']));
|
||||
$result = $groups->matching($criteria);
|
||||
|
||||
self::assertCount(1, $result);
|
||||
self::assertEquals('Developers_1', $result[0]->name);
|
||||
|
||||
self::assertFalse($user->groups->isInitialized(), 'Post-condition: matching does not initialize collection');
|
||||
}
|
||||
|
||||
private function removeTransactionCommandsFromQueryLog(): void
|
||||
{
|
||||
$log = $this->getQueryLog();
|
||||
|
||||
@@ -1394,6 +1394,168 @@ class NewOperatorTest extends OrmFunctionalTestCase
|
||||
self::assertSame($this->fixtures[2]->email->email, $result[2]->val2->val2);
|
||||
}
|
||||
|
||||
public function testOnlyObjectInNamedDto(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new named CmsUserDTOVariadicArg(
|
||||
a,
|
||||
new CmsDumbDTO(
|
||||
u.name,
|
||||
e.email
|
||||
) as dumb
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
LEFT JOIN
|
||||
u.email e
|
||||
LEFT JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsUserDTOVariadicArg::class, $result[0]);
|
||||
self::assertInstanceOf(CmsUserDTOVariadicArg::class, $result[1]);
|
||||
self::assertInstanceOf(CmsUserDTOVariadicArg::class, $result[2]);
|
||||
|
||||
self::assertInstanceOf(CmsAddress::class, $result[0]->otherProperties['a']);
|
||||
self::assertInstanceOf(CmsAddress::class, $result[1]->otherProperties['a']);
|
||||
self::assertInstanceOf(CmsAddress::class, $result[2]->otherProperties['a']);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->city, $result[0]->otherProperties['a']->city);
|
||||
self::assertSame($this->fixtures[1]->address->city, $result[1]->otherProperties['a']->city);
|
||||
self::assertSame($this->fixtures[2]->address->city, $result[2]->otherProperties['a']->city);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->country, $result[0]->otherProperties['a']->country);
|
||||
self::assertSame($this->fixtures[1]->address->country, $result[1]->otherProperties['a']->country);
|
||||
self::assertSame($this->fixtures[2]->address->country, $result[2]->otherProperties['a']->country);
|
||||
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[0]->otherProperties['dumb']);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[1]->otherProperties['dumb']);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[2]->otherProperties['dumb']);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->otherProperties['dumb']->val1);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->otherProperties['dumb']->val1);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->otherProperties['dumb']->val1);
|
||||
|
||||
self::assertSame($this->fixtures[0]->email->email, $result[0]->otherProperties['dumb']->val2);
|
||||
self::assertSame($this->fixtures[1]->email->email, $result[1]->otherProperties['dumb']->val2);
|
||||
self::assertSame($this->fixtures[2]->email->email, $result[2]->otherProperties['dumb']->val2);
|
||||
}
|
||||
|
||||
public function testOnlyObjectInNamedDtoWithAlias(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new named CmsUserDTOVariadicArg(
|
||||
a as addr,
|
||||
new CmsDumbDTO(
|
||||
u.name,
|
||||
e.email
|
||||
) as dumb
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
LEFT JOIN
|
||||
u.email e
|
||||
LEFT JOIN
|
||||
u.address a
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsUserDTOVariadicArg::class, $result[0]);
|
||||
self::assertInstanceOf(CmsUserDTOVariadicArg::class, $result[1]);
|
||||
self::assertInstanceOf(CmsUserDTOVariadicArg::class, $result[2]);
|
||||
|
||||
self::assertInstanceOf(CmsAddress::class, $result[0]->otherProperties['addr']);
|
||||
self::assertInstanceOf(CmsAddress::class, $result[1]->otherProperties['addr']);
|
||||
self::assertInstanceOf(CmsAddress::class, $result[2]->otherProperties['addr']);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->city, $result[0]->otherProperties['addr']->city);
|
||||
self::assertSame($this->fixtures[1]->address->city, $result[1]->otherProperties['addr']->city);
|
||||
self::assertSame($this->fixtures[2]->address->city, $result[2]->otherProperties['addr']->city);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->country, $result[0]->otherProperties['addr']->country);
|
||||
self::assertSame($this->fixtures[1]->address->country, $result[1]->otherProperties['addr']->country);
|
||||
self::assertSame($this->fixtures[2]->address->country, $result[2]->otherProperties['addr']->country);
|
||||
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[0]->otherProperties['dumb']);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[1]->otherProperties['dumb']);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[2]->otherProperties['dumb']);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->otherProperties['dumb']->val1);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->otherProperties['dumb']->val1);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->otherProperties['dumb']->val1);
|
||||
|
||||
self::assertSame($this->fixtures[0]->email->email, $result[0]->otherProperties['dumb']->val2);
|
||||
self::assertSame($this->fixtures[1]->email->email, $result[1]->otherProperties['dumb']->val2);
|
||||
self::assertSame($this->fixtures[2]->email->email, $result[2]->otherProperties['dumb']->val2);
|
||||
}
|
||||
|
||||
public function testOnlyObjectInNamedDtoWithSameNameAsTheProperties(): void
|
||||
{
|
||||
$dql = '
|
||||
SELECT
|
||||
new named CmsUserDTONamedArgs(
|
||||
addressEntity,
|
||||
new CmsDumbDTO(
|
||||
u.name,
|
||||
e.email
|
||||
) as dumb
|
||||
)
|
||||
FROM
|
||||
Doctrine\Tests\Models\CMS\CmsUser u
|
||||
LEFT JOIN
|
||||
u.email e
|
||||
LEFT JOIN
|
||||
u.address addressEntity
|
||||
ORDER BY
|
||||
u.name';
|
||||
|
||||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$result = $query->getResult();
|
||||
|
||||
self::assertCount(3, $result);
|
||||
|
||||
self::assertInstanceOf(CmsUserDTONamedArgs::class, $result[0]);
|
||||
self::assertInstanceOf(CmsUserDTONamedArgs::class, $result[1]);
|
||||
self::assertInstanceOf(CmsUserDTONamedArgs::class, $result[2]);
|
||||
|
||||
self::assertInstanceOf(CmsAddress::class, $result[0]->addressEntity);
|
||||
self::assertInstanceOf(CmsAddress::class, $result[1]->addressEntity);
|
||||
self::assertInstanceOf(CmsAddress::class, $result[2]->addressEntity);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->city, $result[0]->addressEntity->city);
|
||||
self::assertSame($this->fixtures[1]->address->city, $result[1]->addressEntity->city);
|
||||
self::assertSame($this->fixtures[2]->address->city, $result[2]->addressEntity->city);
|
||||
|
||||
self::assertSame($this->fixtures[0]->address->country, $result[0]->addressEntity->country);
|
||||
self::assertSame($this->fixtures[1]->address->country, $result[1]->addressEntity->country);
|
||||
self::assertSame($this->fixtures[2]->address->country, $result[2]->addressEntity->country);
|
||||
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[0]->dumb);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[1]->dumb);
|
||||
self::assertInstanceOf(CmsDumbDTO::class, $result[2]->dumb);
|
||||
|
||||
self::assertSame($this->fixtures[0]->name, $result[0]->dumb->val1);
|
||||
self::assertSame($this->fixtures[1]->name, $result[1]->dumb->val1);
|
||||
self::assertSame($this->fixtures[2]->name, $result[2]->dumb->val1);
|
||||
|
||||
self::assertSame($this->fixtures[0]->email->email, $result[0]->dumb->val2);
|
||||
self::assertSame($this->fixtures[1]->email->email, $result[1]->dumb->val2);
|
||||
self::assertSame($this->fixtures[2]->email->email, $result[2]->dumb->val2);
|
||||
}
|
||||
|
||||
public function testNamedArguments(): void
|
||||
{
|
||||
$dql = <<<'SQL'
|
||||
|
||||
@@ -164,14 +164,14 @@ class OneToManyBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
$product = $this->_em->find(ECommerceProduct::class, $this->product->getId());
|
||||
$features = $product->getFeatures();
|
||||
|
||||
$results = $features->matching(new Criteria(
|
||||
$results = $features->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('description', 'Model writing tutorial'),
|
||||
));
|
||||
|
||||
self::assertInstanceOf(Collection::class, $results);
|
||||
self::assertCount(1, $results);
|
||||
|
||||
$results = $features->matching(new Criteria());
|
||||
$results = $features->matching(Criteria::create(true));
|
||||
|
||||
self::assertInstanceOf(Collection::class, $results);
|
||||
self::assertCount(2, $results);
|
||||
@@ -190,7 +190,7 @@ class OneToManyBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
$features = $product->getFeatures();
|
||||
$features->add($thirdFeature);
|
||||
|
||||
$results = $features->matching(new Criteria(
|
||||
$results = $features->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('description', 'Model writing tutorial'),
|
||||
));
|
||||
|
||||
@@ -208,14 +208,14 @@ class OneToManyBidirectionalAssociationTest extends OrmFunctionalTestCase
|
||||
$thirdFeature->setDescription('Third feature');
|
||||
$product->addFeature($thirdFeature);
|
||||
|
||||
$results = $features->matching(new Criteria(
|
||||
$results = $features->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('description', 'Third feature'),
|
||||
));
|
||||
|
||||
self::assertInstanceOf(Collection::class, $results);
|
||||
self::assertCount(1, $results);
|
||||
|
||||
$results = $features->matching(new Criteria());
|
||||
$results = $features->matching(Criteria::create(true));
|
||||
|
||||
self::assertInstanceOf(Collection::class, $results);
|
||||
self::assertCount(3, $results);
|
||||
|
||||
@@ -14,9 +14,8 @@ use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use Generator;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\WithoutErrorHandler;
|
||||
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
|
||||
use ReflectionMethod;
|
||||
use Symfony\Component\VarExporter\Instantiator;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
|
||||
use function file_get_contents;
|
||||
@@ -35,7 +34,7 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
|
||||
|
||||
/** @param Closure(ParserResult): ParserResult $toSerializedAndBack */
|
||||
#[DataProvider('provideToSerializedAndBack')]
|
||||
#[WithoutErrorHandler]
|
||||
#[IgnoreDeprecations]
|
||||
public function testSerializeParserResultForQueryWithSqlWalker(Closure $toSerializedAndBack): void
|
||||
{
|
||||
$query = $this->_em
|
||||
@@ -78,11 +77,6 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
|
||||
},
|
||||
];
|
||||
|
||||
$instantiatorMethod = new ReflectionMethod(Instantiator::class, 'instantiate');
|
||||
if ($instantiatorMethod->getReturnType() === null) {
|
||||
self::markTestSkipped('symfony/var-exporter 5.4+ is required.');
|
||||
}
|
||||
|
||||
yield 'symfony/var-exporter' => [
|
||||
static function (ParserResult $parserResult): ParserResult {
|
||||
return eval('return ' . VarExporter::export($parserResult) . ';');
|
||||
@@ -131,7 +125,6 @@ class ParserResultSerializationTest extends OrmFunctionalTestCase
|
||||
private static function parseQuery(Query $query): ParserResult
|
||||
{
|
||||
$r = new ReflectionMethod($query, 'parse');
|
||||
$r->setAccessible(true);
|
||||
|
||||
return $r->invoke($query);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class PersistentCollectionCriteriaTest extends OrmFunctionalTestCase
|
||||
$repository = $this->_em->getRepository(User::class);
|
||||
|
||||
$user = $repository->findOneBy(['name' => 'ngal']);
|
||||
$tweets = $user->tweets->matching(new Criteria());
|
||||
$tweets = $user->tweets->matching(Criteria::create(true));
|
||||
|
||||
self::assertInstanceOf(LazyCriteriaCollection::class, $tweets);
|
||||
self::assertFalse($tweets->isInitialized());
|
||||
@@ -88,7 +88,7 @@ class PersistentCollectionCriteriaTest extends OrmFunctionalTestCase
|
||||
self::assertFalse($tweets->isInitialized());
|
||||
|
||||
// Make sure it works with constraints
|
||||
$tweets = $user->tweets->matching(new Criteria(
|
||||
$tweets = $user->tweets->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('content', 'Foo'),
|
||||
));
|
||||
|
||||
@@ -117,7 +117,7 @@ class PersistentCollectionCriteriaTest extends OrmFunctionalTestCase
|
||||
|
||||
$parent = $this->_em->find(OwningManyToManyExtraLazyEntity::class, $parent->id2);
|
||||
|
||||
$criteria = Criteria::create()->where(Criteria::expr()->eq('id1', 'Bob'));
|
||||
$criteria = Criteria::create(true)->where(Criteria::expr()->eq('id1', 'Bob'));
|
||||
|
||||
$result = $parent->associatedEntities->matching($criteria);
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class PersistentCollectionTest extends OrmFunctionalTestCase
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria = Criteria::create(true);
|
||||
|
||||
$collectionHolder = $this->_em->find(PersistentCollectionHolder::class, $collectionHolder->getId());
|
||||
$collectionHolder->getCollection()->matching($criteria);
|
||||
|
||||
93
tests/Tests/ORM/Functional/PrePersistEventTest.php
Normal file
93
tests/Tests/ORM/Functional/PrePersistEventTest.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Event\PrePersistEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
use function uniqid;
|
||||
|
||||
class PrePersistEventTest extends OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createSchemaForModels(
|
||||
EntityWithUnmappedEntity::class,
|
||||
EntityWithCascadeAssociation::class,
|
||||
);
|
||||
}
|
||||
|
||||
public function testCallingPersistInPrePersistHook(): void
|
||||
{
|
||||
$entityWithUnmapped = new EntityWithUnmappedEntity();
|
||||
$entityWithCascade = new EntityWithCascadeAssociation();
|
||||
|
||||
$entityWithUnmapped->unmapped = $entityWithCascade;
|
||||
$entityWithCascade->cascaded = $entityWithUnmapped;
|
||||
|
||||
$this->_em->getEventManager()->addEventListener(Events::prePersist, new PrePersistUnmappedPersistListener());
|
||||
$this->_em->persist($entityWithUnmapped);
|
||||
|
||||
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithCascade));
|
||||
$this->assertTrue($this->_em->getUnitOfWork()->isScheduledForInsert($entityWithUnmapped));
|
||||
}
|
||||
}
|
||||
|
||||
class PrePersistUnmappedPersistListener
|
||||
{
|
||||
public function prePersist(PrePersistEventArgs $args): void
|
||||
{
|
||||
$object = $args->getObject();
|
||||
|
||||
if ($object instanceof EntityWithUnmappedEntity) {
|
||||
$uow = $args->getObjectManager()->getUnitOfWork();
|
||||
|
||||
if ($object->unmapped && ! $uow->isInIdentityMap($object->unmapped) && ! $uow->isScheduledForInsert($object->unmapped)) {
|
||||
$args->getObjectManager()->persist($object->unmapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class EntityWithUnmappedEntity
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string', length: 255)]
|
||||
#[GeneratedValue(strategy: 'NONE')]
|
||||
public string $id;
|
||||
|
||||
public EntityWithCascadeAssociation|null $unmapped = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = uniqid(self::class, true);
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class EntityWithCascadeAssociation
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'string', length: 255)]
|
||||
#[GeneratedValue(strategy: 'NONE')]
|
||||
public string $id;
|
||||
|
||||
#[ManyToOne(targetEntity: EntityWithUnmappedEntity::class, cascade: ['persist'])]
|
||||
public EntityWithUnmappedEntity|null $cascaded = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = uniqid(self::class, true);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
|
||||
use Doctrine\ORM\Query\ParserResult;
|
||||
@@ -122,15 +123,15 @@ class QueryCacheTest extends OrmFunctionalTestCase
|
||||
|
||||
$query = $this->_em->createQuery('select ux from Doctrine\Tests\Models\CMS\CmsUser ux');
|
||||
|
||||
$sqlExecMock = $this->getMockBuilder(AbstractSqlExecutor::class)
|
||||
->getMockForAbstractClass();
|
||||
|
||||
$sqlExecMock->expects(self::once())
|
||||
->method('execute')
|
||||
->willReturn(10);
|
||||
$sqlExecutorStub = new class extends AbstractSqlExecutor {
|
||||
public function execute(Connection $conn, array $params, array $types): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
};
|
||||
|
||||
$parserResultMock = new ParserResult();
|
||||
$parserResultMock->setSqlExecutor($sqlExecMock);
|
||||
$parserResultMock->setSqlExecutor($sqlExecutorStub);
|
||||
|
||||
$cache = $this->createMock(CacheItemPoolInterface::class);
|
||||
|
||||
|
||||
@@ -403,13 +403,134 @@ class QueryTest extends OrmFunctionalTestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testToIterableWithMixedResultIsNotAllowed(): void
|
||||
public function testToIterableWithMixedResultEntityScalars(): void
|
||||
{
|
||||
$this->expectException(QueryException::class);
|
||||
$this->expectExceptionMessage('Iterating a query with mixed results (using scalars) is not supported.');
|
||||
$author = new CmsUser();
|
||||
$author->name = 'Ben';
|
||||
$author->username = 'beberlei';
|
||||
|
||||
$query = $this->_em->createQuery('select a, a.topic from ' . CmsArticle::class . ' a');
|
||||
$query->toIterable();
|
||||
$article1 = new CmsArticle();
|
||||
$article1->topic = 'Doctrine 2';
|
||||
$article1->text = 'This is an introduction to Doctrine 2.';
|
||||
$article1->setAuthor($author);
|
||||
|
||||
$article2 = new CmsArticle();
|
||||
$article2->topic = 'Symfony 2';
|
||||
$article2->text = 'This is an introduction to Symfony 2.';
|
||||
$article2->setAuthor($author);
|
||||
|
||||
$article3 = new CmsArticle();
|
||||
$article3->topic = 'lala 2';
|
||||
$article3->text = 'This is an introduction to Symfony 2.';
|
||||
$article3->setAuthor($author);
|
||||
|
||||
$this->_em->persist($article1);
|
||||
$this->_em->persist($article2);
|
||||
$this->_em->persist($article3);
|
||||
$this->_em->persist($author);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$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);
|
||||
$this->assertCount(3, $it);
|
||||
$this->assertEquals('Doctrine 2', $it[0]['topic']);
|
||||
$this->assertEquals('Doctrine 2', $it[0][0]->topic);
|
||||
$this->assertEquals('lala 2', $it[2]['topic']);
|
||||
$this->assertEquals('lala 2', $it[2][0]->topic);
|
||||
}
|
||||
|
||||
public function testToIterableWithMixedResultArbitraryJoinsScalars(): void
|
||||
{
|
||||
$author = new CmsUser();
|
||||
$author->name = 'Ben';
|
||||
$author->username = 'beberlei';
|
||||
|
||||
$article1 = new CmsArticle();
|
||||
$article1->topic = 'Doctrine 2';
|
||||
$article1->text = 'This is an introduction to Doctrine 2.';
|
||||
$article1->setAuthor($author);
|
||||
|
||||
$article2 = new CmsArticle();
|
||||
$article2->topic = 'Symfony 2';
|
||||
$article2->text = 'This is an introduction to Symfony 2.';
|
||||
$article2->setAuthor($author);
|
||||
|
||||
$article3 = new CmsArticle();
|
||||
$article3->topic = 'lala 2';
|
||||
$article3->text = 'This is an introduction to Symfony 2.';
|
||||
$article3->setAuthor($author);
|
||||
|
||||
$this->_em->persist($article1);
|
||||
$this->_em->persist($article2);
|
||||
$this->_em->persist($article3);
|
||||
$this->_em->persist($author);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$query = $this->_em->createQuery(
|
||||
'select a, u, a.topic, a.text from ' . CmsArticle::class . ' a, ' . CmsUser::class . ' u WHERE a.user = u order by a.id asc',
|
||||
);
|
||||
$result = $query->toIterable();
|
||||
|
||||
$it = iterator_to_array($result);
|
||||
|
||||
$this->assertCount(3, $it);
|
||||
$this->assertEquals('Doctrine 2', $it[0]['topic']);
|
||||
$this->assertEquals('Doctrine 2', $it[0][0]->topic);
|
||||
$this->assertEquals('beberlei', $it[0][1]->username);
|
||||
$this->assertEquals('lala 2', $it[2]['topic']);
|
||||
$this->assertEquals('lala 2', $it[2][0]->topic);
|
||||
$this->assertEquals('beberlei', $it[2][1]->username);
|
||||
}
|
||||
|
||||
public function testToIterableWithMixedResultScalarsOnly(): void
|
||||
{
|
||||
$author = new CmsUser();
|
||||
$author->name = 'Ben';
|
||||
$author->username = 'beberlei';
|
||||
|
||||
$article1 = new CmsArticle();
|
||||
$article1->topic = 'Doctrine 2';
|
||||
$article1->text = 'This is an introduction to Doctrine 2.';
|
||||
$article1->setAuthor($author);
|
||||
|
||||
$article2 = new CmsArticle();
|
||||
$article2->topic = 'Symfony 2';
|
||||
$article2->text = 'This is an introduction to Symfony 2.';
|
||||
$article2->setAuthor($author);
|
||||
|
||||
$article3 = new CmsArticle();
|
||||
$article3->topic = 'lala 2';
|
||||
$article3->text = 'This is an introduction to Symfony 2.';
|
||||
$article3->setAuthor($author);
|
||||
|
||||
$this->_em->persist($article1);
|
||||
$this->_em->persist($article2);
|
||||
$this->_em->persist($article3);
|
||||
$this->_em->persist($author);
|
||||
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$query = $this->_em->createQuery(
|
||||
'select a.topic, a.text from ' . CmsArticle::class . ' a order by a.id asc',
|
||||
);
|
||||
$result = $query->toIterable();
|
||||
|
||||
$it = iterator_to_array($result);
|
||||
|
||||
$this->assertEquals([
|
||||
['topic' => 'Doctrine 2', 'text' => 'This is an introduction to Doctrine 2.'],
|
||||
['topic' => 'Symfony 2', 'text' => 'This is an introduction to Symfony 2.'],
|
||||
['topic' => 'lala 2', 'text' => 'This is an introduction to Symfony 2.'],
|
||||
], $it);
|
||||
}
|
||||
|
||||
public function testIterateResultClearEveryCycle(): void
|
||||
@@ -428,7 +549,9 @@ class QueryTest extends OrmFunctionalTestCase
|
||||
$this->_em->flush();
|
||||
$this->_em->clear();
|
||||
|
||||
$query = $this->_em->createQuery('select a from Doctrine\Tests\Models\CMS\CmsArticle a');
|
||||
$query = $this->_em->createQuery(
|
||||
'select a from Doctrine\Tests\Models\CMS\CmsArticle a order by a.id asc',
|
||||
);
|
||||
|
||||
$articles = $query->toIterable();
|
||||
$iteratedCount = 0;
|
||||
|
||||
@@ -142,6 +142,8 @@ class MySqlSchemaToolTest extends OrmFunctionalTestCase
|
||||
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
|
||||
// DBAL 4.3 (see https://github.com/doctrine/dbal/pull/6864)
|
||||
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT(1) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
|
||||
// DBAL 4.4 (see https://github.com/doctrine/dbal/pull/7221)
|
||||
self::equalTo('CREATE TABLE boolean_model (id INT AUTO_INCREMENT NOT NULL, booleanField TINYINT NOT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
166
tests/Tests/ORM/Functional/SecondLevelCacheCountQueriesTest.php
Normal file
166
tests/Tests/ORM/Functional/SecondLevelCacheCountQueriesTest.php
Normal file
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\Tests\Models\Cache\Country;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function array_diff;
|
||||
use function array_filter;
|
||||
use function file_exists;
|
||||
use function rmdir;
|
||||
use function scandir;
|
||||
use function strpos;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
|
||||
/** @phpstan-type SupportedCacheUsage 0|ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE|ClassMetadata::CACHE_USAGE_READ_WRITE */
|
||||
#[Group('DDC-2183')]
|
||||
class SecondLevelCacheCountQueriesTest extends SecondLevelCacheFunctionalTestCase
|
||||
{
|
||||
/** @var string */
|
||||
private $tmpDir;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if ($this->tmpDir !== null && file_exists($this->tmpDir)) {
|
||||
foreach (array_diff(scandir($this->tmpDir), ['.', '..']) as $f) {
|
||||
rmdir($this->tmpDir . DIRECTORY_SEPARATOR . $f);
|
||||
}
|
||||
|
||||
rmdir($this->tmpDir);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
private function setupCountryModel(int $cacheUsage): void
|
||||
{
|
||||
$metadata = $this->_em->getClassMetaData(Country::class);
|
||||
|
||||
if ($cacheUsage === 0) {
|
||||
$metadataCacheReflection = new ReflectionProperty(ClassMetadata::class, 'cache');
|
||||
$metadataCacheReflection->setValue($metadata, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($cacheUsage === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
$this->tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . self::class;
|
||||
$this->secondLevelCacheFactory->setFileLockRegionDirectory($this->tmpDir);
|
||||
}
|
||||
|
||||
$metadata->enableCache(['usage' => $cacheUsage]);
|
||||
}
|
||||
|
||||
private function loadFixturesCountriesWithoutPostInsertIdentifier(): void
|
||||
{
|
||||
$metadata = $this->_em->getClassMetaData(Country::class);
|
||||
$metadata->setIdGenerator(new AssignedGenerator());
|
||||
|
||||
$c1 = new Country('Brazil');
|
||||
$c1->setId(10);
|
||||
$c2 = new Country('Germany');
|
||||
$c2->setId(20);
|
||||
|
||||
$this->countries[] = $c1;
|
||||
$this->countries[] = $c2;
|
||||
|
||||
$this->_em->persist($c1);
|
||||
$this->_em->persist($c2);
|
||||
$this->_em->flush();
|
||||
}
|
||||
|
||||
/** @param 'INSERT'|'UPDATE'|'DELETE' $type */
|
||||
private function assertQueryCountByType(string $type, int $expectedCount): void
|
||||
{
|
||||
$queries = array_filter($this->getQueryLog()->queries, static function (array $entry) use ($type): bool {
|
||||
return strpos($entry['sql'], $type) === 0;
|
||||
});
|
||||
|
||||
self::assertCount($expectedCount, $queries);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testInsertWithPostInsertIdentifier(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
|
||||
self::assertQueryCountByType('INSERT', 0);
|
||||
|
||||
$this->loadFixturesCountries();
|
||||
|
||||
self::assertCount(2, $this->countries);
|
||||
self::assertQueryCountByType('INSERT', 2);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testInsertWithoutPostInsertIdentifier(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
|
||||
self::assertQueryCountByType('INSERT', 0);
|
||||
|
||||
$this->loadFixturesCountriesWithoutPostInsertIdentifier();
|
||||
|
||||
self::assertCount(2, $this->countries);
|
||||
self::assertQueryCountByType('INSERT', 2);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testDelete(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
$this->loadFixturesCountries();
|
||||
|
||||
$c1 = $this->_em->find(Country::class, $this->countries[0]->getId());
|
||||
$c2 = $this->_em->find(Country::class, $this->countries[1]->getId());
|
||||
|
||||
$this->_em->remove($c1);
|
||||
$this->_em->remove($c2);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertQueryCountByType('DELETE', 2);
|
||||
}
|
||||
|
||||
/** @param SupportedCacheUsage $cacheUsage */
|
||||
#[DataProvider('cacheUsageProvider')]
|
||||
public function testUpdate(int $cacheUsage): void
|
||||
{
|
||||
$this->setupCountryModel($cacheUsage);
|
||||
$this->loadFixturesCountries();
|
||||
|
||||
$c1 = $this->_em->find(Country::class, $this->countries[0]->getId());
|
||||
$c2 = $this->_em->find(Country::class, $this->countries[1]->getId());
|
||||
|
||||
$c1->setName('Czech Republic');
|
||||
$c2->setName('Hungary');
|
||||
|
||||
$this->_em->persist($c1);
|
||||
$this->_em->persist($c2);
|
||||
$this->_em->flush();
|
||||
|
||||
self::assertQueryCountByType('UPDATE', 2);
|
||||
}
|
||||
|
||||
/** @return list<array{SupportedCacheUsage}> */
|
||||
public static function cacheUsageProvider(): array
|
||||
{
|
||||
return [
|
||||
[0],
|
||||
[ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE],
|
||||
[ClassMetadata::CACHE_USAGE_READ_WRITE],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
$repository = $this->_em->getRepository(Country::class);
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
$name = $this->countries[0]->getName();
|
||||
$result1 = $repository->matching(new Criteria(
|
||||
$result1 = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $name),
|
||||
));
|
||||
|
||||
@@ -41,7 +41,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$result2 = $repository->matching(new Criteria(
|
||||
$result2 = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $name),
|
||||
));
|
||||
|
||||
@@ -65,7 +65,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
|
||||
$repository = $this->_em->getRepository(Country::class);
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
$result1 = $repository->matching(new Criteria(
|
||||
$result1 = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $this->countries[0]->getName()),
|
||||
));
|
||||
|
||||
@@ -79,7 +79,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$result2 = $repository->matching(new Criteria(
|
||||
$result2 = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $this->countries[0]->getName()),
|
||||
));
|
||||
|
||||
@@ -94,7 +94,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertEquals($this->countries[0]->getId(), $result2[0]->getId());
|
||||
self::assertEquals($this->countries[0]->getName(), $result2[0]->getName());
|
||||
|
||||
$result3 = $repository->matching(new Criteria(
|
||||
$result3 = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $this->countries[1]->getName()),
|
||||
));
|
||||
|
||||
@@ -109,7 +109,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
self::assertEquals($this->countries[1]->getId(), $result3[0]->getId());
|
||||
self::assertEquals($this->countries[1]->getName(), $result3[0]->getName());
|
||||
|
||||
$result4 = $repository->matching(new Criteria(
|
||||
$result4 = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $this->countries[1]->getName()),
|
||||
));
|
||||
|
||||
@@ -134,7 +134,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
$itemName = $this->states[0]->getCities()->get(0)->getName();
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
$collection = $entity->getCities();
|
||||
$matching = $collection->matching(new Criteria(
|
||||
$matching = $collection->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $itemName),
|
||||
));
|
||||
|
||||
@@ -147,7 +147,7 @@ class SecondLevelCacheCriteriaTest extends SecondLevelCacheFunctionalTestCase
|
||||
$entity = $this->_em->find(State::class, $this->states[0]->getId());
|
||||
$this->getQueryLog()->reset()->enable();
|
||||
$collection = $entity->getCities();
|
||||
$matching = $collection->matching(new Criteria(
|
||||
$matching = $collection->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('name', $itemName),
|
||||
));
|
||||
|
||||
|
||||
@@ -354,13 +354,13 @@ class SingleTableInheritanceTest extends OrmFunctionalTestCase
|
||||
$this->loadFullFixture();
|
||||
|
||||
$repository = $this->_em->getRepository(CompanyContract::class);
|
||||
$contracts = $repository->matching(new Criteria(
|
||||
$contracts = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('salesPerson', $this->salesPerson),
|
||||
));
|
||||
self::assertCount(3, $contracts);
|
||||
|
||||
$repository = $this->_em->getRepository(CompanyFixContract::class);
|
||||
$contracts = $repository->matching(new Criteria(
|
||||
$contracts = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('salesPerson', $this->salesPerson),
|
||||
));
|
||||
self::assertCount(1, $contracts);
|
||||
@@ -376,7 +376,7 @@ class SingleTableInheritanceTest extends OrmFunctionalTestCase
|
||||
$this->expectException(MatchingAssociationFieldRequiresObject::class);
|
||||
$this->expectExceptionMessage('annot match on Doctrine\Tests\Models\Company\CompanyContract::salesPerson with a non-object value.');
|
||||
|
||||
$contracts = $repository->matching(new Criteria(
|
||||
$contracts = $repository->matching(Criteria::create(true)->where(
|
||||
Criteria::expr()->eq('salesPerson', $this->salesPerson->getId()),
|
||||
));
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class DDC2106Test extends OrmFunctionalTestCase
|
||||
$entityWithoutId = new DDC2106Entity();
|
||||
$this->_em->persist($entityWithoutId);
|
||||
|
||||
$criteria = Criteria::create()->where(Criteria::expr()->eq('parent', $entityWithoutId));
|
||||
$criteria = Criteria::create(true)->where(Criteria::expr()->eq('parent', $entityWithoutId));
|
||||
|
||||
self::assertCount(0, $entity->children->matching($criteria));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraintEditor;
|
||||
use Doctrine\DBAL\Schema\Name\UnqualifiedName;
|
||||
@@ -20,9 +21,9 @@ use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use Doctrine\Tests\ORM\Functional\Ticket\Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
|
||||
|
||||
use function array_map;
|
||||
use function assert;
|
||||
@@ -31,10 +32,15 @@ use function reset;
|
||||
|
||||
class DDC2138Test extends OrmFunctionalTestCase
|
||||
{
|
||||
/**
|
||||
* With this test, we will create the same foreign key twice which is fine, but we will tap a deprecation
|
||||
* in DBAL 4.4. This has to be fixed during the validation of metadata. For now, we will simply ignore that
|
||||
* deprecation.
|
||||
*/
|
||||
#[Group('DDC-2138')]
|
||||
#[IgnoreDeprecations]
|
||||
public function testForeignKeyOnSTIWithMultipleMapping(): void
|
||||
{
|
||||
$em = $this->_em;
|
||||
$schema = $this->getSchemaForModels(
|
||||
DDC2138User::class,
|
||||
DDC2138Structure::class,
|
||||
@@ -86,15 +92,13 @@ class DDC2138Test extends OrmFunctionalTestCase
|
||||
#[Entity]
|
||||
class DDC2138Structure
|
||||
{
|
||||
/** @var int */
|
||||
#[Id]
|
||||
#[Column(type: 'integer')]
|
||||
#[Column]
|
||||
#[GeneratedValue(strategy: 'AUTO')]
|
||||
protected $id;
|
||||
protected int|null $id = null;
|
||||
|
||||
/** @var string */
|
||||
#[Column(type: 'string', length: 32, nullable: true)]
|
||||
protected $name;
|
||||
#[Column(length: 32, nullable: true)]
|
||||
protected string|null $name = null;
|
||||
}
|
||||
|
||||
#[Table(name: 'users_followed_objects')]
|
||||
@@ -104,19 +108,10 @@ class DDC2138Structure
|
||||
#[DiscriminatorMap([4 => 'DDC2138UserFollowedUser', 3 => 'DDC2138UserFollowedStructure'])]
|
||||
abstract class DDC2138UserFollowedObject
|
||||
{
|
||||
/** @var int $id */
|
||||
#[Column(name: 'id', type: 'integer')]
|
||||
#[Id]
|
||||
#[GeneratedValue(strategy: 'AUTO')]
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
public int|null $id = null;
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
@@ -126,27 +121,14 @@ class DDC2138UserFollowedStructure extends DDC2138UserFollowedObject
|
||||
* Construct a UserFollowedStructure entity
|
||||
*/
|
||||
public function __construct(
|
||||
#[ManyToOne(targetEntity: 'DDC2138User', inversedBy: 'followedStructures')]
|
||||
#[ManyToOne(inversedBy: 'followedStructures')]
|
||||
#[JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: false)]
|
||||
protected User $user,
|
||||
#[ManyToOne(targetEntity: 'DDC2138Structure')]
|
||||
public DDC2138User $user,
|
||||
#[ManyToOne]
|
||||
#[JoinColumn(name: 'object_id', referencedColumnName: 'id', nullable: false)]
|
||||
private Structure $followedStructure,
|
||||
public DDC2138Structure $followedStructure,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets followed structure
|
||||
*/
|
||||
public function getFollowedStructure(): Structure
|
||||
{
|
||||
return $this->followedStructure;
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
@@ -156,92 +138,39 @@ class DDC2138UserFollowedUser extends DDC2138UserFollowedObject
|
||||
* Construct a UserFollowedUser entity
|
||||
*/
|
||||
public function __construct(
|
||||
#[ManyToOne(targetEntity: 'DDC2138User', inversedBy: 'followedUsers')]
|
||||
#[ManyToOne(inversedBy: 'followedUsers')]
|
||||
#[JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: false)]
|
||||
protected User $user,
|
||||
#[ManyToOne(targetEntity: 'DDC2138User')]
|
||||
public DDC2138User $user,
|
||||
#[ManyToOne]
|
||||
#[JoinColumn(name: 'object_id', referencedColumnName: 'id', nullable: false)]
|
||||
private User $followedUser,
|
||||
public DDC2138User $followedUser,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets followed user
|
||||
*/
|
||||
public function getFollowedUser(): User
|
||||
{
|
||||
return $this->followedUser;
|
||||
}
|
||||
}
|
||||
|
||||
#[Table(name: 'users')]
|
||||
#[Entity]
|
||||
class DDC2138User
|
||||
{
|
||||
/** @var int */
|
||||
#[Id]
|
||||
#[Column(type: 'integer')]
|
||||
#[Column]
|
||||
#[GeneratedValue(strategy: 'AUTO')]
|
||||
protected $id;
|
||||
public int|null $id = null;
|
||||
|
||||
/** @var string */
|
||||
#[Column(type: 'string', length: 32, nullable: true)]
|
||||
protected $name;
|
||||
#[Column(length: 32, nullable: true)]
|
||||
public string|null $name = null;
|
||||
|
||||
/** @var ArrayCollection $followedUsers */
|
||||
#[OneToMany(targetEntity: 'DDC2138UserFollowedUser', mappedBy: 'user', cascade: ['persist'], orphanRemoval: true)]
|
||||
protected $followedUsers;
|
||||
/** @var Collection<int, DDC2138UserFollowedUser> */
|
||||
#[OneToMany(targetEntity: DDC2138UserFollowedUser::class, mappedBy: 'user', cascade: ['persist'], orphanRemoval: true)]
|
||||
public Collection $followedUsers;
|
||||
|
||||
/** @var ArrayCollection $followedStructures */
|
||||
#[OneToMany(targetEntity: 'DDC2138UserFollowedStructure', mappedBy: 'user', cascade: ['persist'], orphanRemoval: true)]
|
||||
protected $followedStructures;
|
||||
/** @var Collection<int, DDC2138UserFollowedStructure> */
|
||||
#[OneToMany(targetEntity: DDC2138UserFollowedStructure::class, mappedBy: 'user', cascade: ['persist'], orphanRemoval: true)]
|
||||
public Collection $followedStructures;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->followedUsers = new ArrayCollection();
|
||||
$this->followedStructures = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function addFollowedUser(UserFollowedUser $followedUsers): User
|
||||
{
|
||||
$this->followedUsers[] = $followedUsers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeFollowedUser(UserFollowedUser $followedUsers): User
|
||||
{
|
||||
$this->followedUsers->removeElement($followedUsers);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFollowedUsers(): Collection
|
||||
{
|
||||
return $this->followedUsers;
|
||||
}
|
||||
|
||||
public function addFollowedStructure(UserFollowedStructure $followedStructures): User
|
||||
{
|
||||
$this->followedStructures[] = $followedStructures;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeFollowedStructure(UserFollowedStructure $followedStructures): User
|
||||
{
|
||||
$this->followedStructures->removeElement($followedStructures);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFollowedStructures(): Collection
|
||||
{
|
||||
return $this->followedStructures;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
|
||||
use function substr_count;
|
||||
|
||||
#[Group('DDC-3042')]
|
||||
class DDC3042Test extends OrmFunctionalTestCase
|
||||
{
|
||||
@@ -23,14 +25,18 @@ class DDC3042Test extends OrmFunctionalTestCase
|
||||
|
||||
public function testSQLGenerationDoesNotProvokeAliasCollisions(): void
|
||||
{
|
||||
self::assertStringNotMatchesFormat(
|
||||
'%sfield11%sfield11%s',
|
||||
$this
|
||||
self::assertSame(
|
||||
1,
|
||||
substr_count(
|
||||
$this
|
||||
->_em
|
||||
->createQuery(
|
||||
'SELECT f, b FROM ' . __NAMESPACE__ . '\DDC3042Foo f JOIN ' . __NAMESPACE__ . '\DDC3042Bar b WITH 1 = 1',
|
||||
)
|
||||
->getSQL(),
|
||||
'field_11',
|
||||
),
|
||||
'The alias "field11" should only appear once in the SQL query.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class DDC3719Test extends OrmFunctionalTestCase
|
||||
$contracts = $manager->managedContracts;
|
||||
self::assertCount(2, $contracts);
|
||||
|
||||
$criteria = Criteria::create();
|
||||
$criteria = Criteria::create(true);
|
||||
$criteria->where(Criteria::expr()->eq('completed', true));
|
||||
|
||||
$completedContracts = $contracts->matching($criteria);
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket\GH10049;
|
||||
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
|
||||
|
||||
class GH10049Test extends OrmFunctionalTestCase
|
||||
{
|
||||
@@ -18,7 +19,7 @@ class GH10049Test extends OrmFunctionalTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/** @doesNotPerformAssertions */
|
||||
#[DoesNotPerformAssertions]
|
||||
public function testInheritedReadOnlyPropertyValueCanBeSet(): void
|
||||
{
|
||||
$child = new ReadOnlyPropertyInheritor(10049);
|
||||
|
||||
@@ -30,15 +30,6 @@ class GH10450Test extends OrmTestCase
|
||||
yield 'Entity class that redeclares a protected field inherited from a base entity' => [GH10450EntityChildProtected::class];
|
||||
yield 'Entity class that redeclares a protected field inherited from a mapped superclass' => [GH10450MappedSuperclassChildProtected::class];
|
||||
}
|
||||
|
||||
public function testFieldsOfTransientClassesAreNotConsideredDuplicate(): void
|
||||
{
|
||||
$em = $this->getTestEntityManager();
|
||||
|
||||
$metadata = $em->getClassMetadata(GH10450Cat::class);
|
||||
|
||||
self::assertArrayHasKey('id', $metadata->fieldMappings);
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
@@ -122,26 +113,3 @@ class GH10450MappedSuperclassChildProtected extends GH10450BaseMappedSuperclassP
|
||||
#[ORM\Column(type: 'text', name: 'child')]
|
||||
protected string $field;
|
||||
}
|
||||
|
||||
abstract class GH10450AbstractEntity
|
||||
{
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
protected int $id;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\InheritanceType('SINGLE_TABLE')]
|
||||
#[ORM\DiscriminatorMap(['cat' => GH10450Cat::class])]
|
||||
#[ORM\DiscriminatorColumn(name: 'type')]
|
||||
abstract class GH10450Animal extends GH10450AbstractEntity
|
||||
{
|
||||
#[ORM\Column(type: 'text', name: 'base')]
|
||||
private string $field;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH10450Cat extends GH10450Animal
|
||||
{
|
||||
}
|
||||
|
||||
80
tests/Tests/ORM/Functional/Ticket/GH12174Test.php
Normal file
80
tests/Tests/ORM/Functional/Ticket/GH12174Test.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional\Ticket;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\ManyToOne;
|
||||
use Doctrine\ORM\Mapping\OneToMany;
|
||||
use Doctrine\ORM\Tools\ResolveTargetEntityListener;
|
||||
use Doctrine\Tests\OrmFunctionalTestCase;
|
||||
|
||||
class GH12174Test extends OrmFunctionalTestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/pull/10431'); // make test fail on 2.x; in 3.x, it would even throw
|
||||
|
||||
$resolveTargetEntity = new ResolveTargetEntityListener();
|
||||
|
||||
$resolveTargetEntity->addResolveTargetEntity(GH12174Smurf::class, GH12174BlueSmurf::class, []);
|
||||
|
||||
$this->_em->getEventManager()->addEventSubscriber($resolveTargetEntity);
|
||||
|
||||
$this->createSchemaForModels(
|
||||
GH12174Smurf::class,
|
||||
GH12174BlueSmurf::class,
|
||||
GH12174PapaSmurf::class,
|
||||
);
|
||||
}
|
||||
|
||||
public function testMappedSuperclassNameCanBeUsedToResolveTargetEntityClass(): void
|
||||
{
|
||||
$smurf = $this->_em->getClassMetadata(GH12174Smurf::class);
|
||||
self::assertTrue($smurf->isMappedSuperclass);
|
||||
self::assertSame(GH12174Smurf::class, $smurf->getName());
|
||||
self::assertSame(GH12174BlueSmurf::class, $smurf->getAssociationMapping('children')['targetEntity']);
|
||||
|
||||
$blue = $this->_em->getClassMetadata(GH12174BlueSmurf::class);
|
||||
self::assertFalse($blue->isMappedSuperclass);
|
||||
self::assertSame(GH12174BlueSmurf::class, $blue->getName());
|
||||
|
||||
$papa = $this->_em->getClassMetadata(GH12174PapaSmurf::class);
|
||||
self::assertFalse($papa->isMappedSuperclass);
|
||||
self::assertSame(GH12174PapaSmurf::class, $papa->getName());
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\MappedSuperclass]
|
||||
class GH12174Smurf
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: Types::INTEGER)]
|
||||
public int $id;
|
||||
|
||||
#[ManyToOne(inversedBy: 'children', targetEntity: self::class)]
|
||||
private GH12174Smurf $parent;
|
||||
|
||||
/** @var Collection<self::class> */
|
||||
#[OneToMany(targetEntity: self::class, mappedBy: 'parent')]
|
||||
private Collection $children;
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH12174BlueSmurf extends GH12174Smurf
|
||||
{
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
class GH12174PapaSmurf extends GH12174Smurf
|
||||
{
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user