mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 06:52:09 +01:00
Compare commits
381 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4262eb495b | ||
|
|
d3b47d2cbb | ||
|
|
026f5bfe1b | ||
|
|
0b0f2f4d86 | ||
|
|
0bd839a720 | ||
|
|
b65004fc26 | ||
|
|
d2418ab074 | ||
|
|
39a05e31c9 | ||
|
|
ab156a551c | ||
|
|
2148940290 | ||
|
|
d3538095fd | ||
|
|
0c1bf14729 | ||
|
|
3b8c23c51d | ||
|
|
60d4ea694a | ||
|
|
e923bbc932 | ||
|
|
8cbd34c666 | ||
|
|
8bdefef6d1 | ||
|
|
0f8730a6e5 | ||
|
|
62477b5d42 | ||
|
|
12116aa3c2 | ||
|
|
0aeddd0592 | ||
|
|
2491c4b20d | ||
|
|
08d6167243 | ||
|
|
d4e9276e79 | ||
|
|
cee74faa97 | ||
|
|
9ae2181185 | ||
|
|
3e25efd72b | ||
|
|
47496ed882 | ||
|
|
492745d710 | ||
|
|
67419cf951 | ||
|
|
1237f5c909 | ||
|
|
609e616f2d | ||
|
|
4016d6ba4b | ||
|
|
dcdd46251e | ||
|
|
3d98b43561 | ||
|
|
9f3f70944a | ||
|
|
05e07c0ae0 | ||
|
|
fea42ab984 | ||
|
|
7c347b85c1 | ||
|
|
458b040d93 | ||
|
|
396636a2c2 | ||
|
|
78dd074266 | ||
|
|
ff22a00fcf | ||
|
|
02e8ff9663 | ||
|
|
01fd55e9ea | ||
|
|
2e75a7f1c1 | ||
|
|
152b0e3d65 | ||
|
|
9d11fdd3da | ||
|
|
87f1ba74e0 | ||
|
|
f357a33d23 | ||
|
|
ee70178314 | ||
|
|
ab148d3d9d | ||
|
|
3924c38fab | ||
|
|
9814078a2c | ||
|
|
6de5684fd9 | ||
|
|
c142503a52 | ||
|
|
01c178b297 | ||
|
|
ffa50a777f | ||
|
|
649048f745 | ||
|
|
15537bc218 | ||
|
|
bc95c7c08d | ||
|
|
6982c8ab9d | ||
|
|
3df11d518c | ||
|
|
c1becd54e6 | ||
|
|
e4d7df29c2 | ||
|
|
608705427e | ||
|
|
f0562f4120 | ||
|
|
9f19310f27 | ||
|
|
e38278bfca | ||
|
|
62f2cff218 | ||
|
|
cdd774906b | ||
|
|
96776e091d | ||
|
|
f7470d8a3f | ||
|
|
2c41cc7f1c | ||
|
|
f18de9d569 | ||
|
|
37f76a8381 | ||
|
|
a6c1e63a60 | ||
|
|
b62292256a | ||
|
|
b138395194 | ||
|
|
6881cdff4c | ||
|
|
dede2d775a | ||
|
|
c502190712 | ||
|
|
5bff0919a7 | ||
|
|
9ef0f5301b | ||
|
|
4989ca6f15 | ||
|
|
32d1e97ce7 | ||
|
|
ca8147b148 | ||
|
|
c8ebea77f0 | ||
|
|
23f22860f1 | ||
|
|
b24586b1b5 | ||
|
|
9e5442a892 | ||
|
|
7d8e51c934 | ||
|
|
2f8f1cfcb8 | ||
|
|
fe5ee705db | ||
|
|
01774c035c | ||
|
|
0511a9f790 | ||
|
|
0e3d5e8c82 | ||
|
|
72ffb3bfbf | ||
|
|
2e9a1adc23 | ||
|
|
6f83166266 | ||
|
|
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 | ||
|
|
cb8a76ba3a | ||
|
|
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 | ||
|
|
64444dcfd5 | ||
|
|
8584da8fdc | ||
|
|
eb2cd5375c | ||
|
|
de7140e105 | ||
|
|
07bb0def60 | ||
|
|
39e35fc06c | ||
|
|
7f061c3870 | ||
|
|
74495711fb | ||
|
|
97a7cb8d2f | ||
|
|
85d66de9df | ||
|
|
1b98be31ce | ||
|
|
61f2752a80 | ||
|
|
f3371e1773 | ||
|
|
6476894dc4 | ||
|
|
8cf161d8bc | ||
|
|
e0052390e1 | ||
|
|
8c6419e0e0 | ||
|
|
6f5ce1aca2 | ||
|
|
98e7a53b42 | ||
|
|
3aaaf37dfb | ||
|
|
154a4652ee | ||
|
|
ae7489ff19 | ||
|
|
0f32569a7a | ||
|
|
d99f74c704 | ||
|
|
a2990e1a0a | ||
|
|
d355c4a990 | ||
|
|
88c395c488 | ||
|
|
256d6cb0d7 | ||
|
|
62ca8424d8 | ||
|
|
3f2209a571 | ||
|
|
1ee01f4473 | ||
|
|
8a9ed138a8 | ||
|
|
e714b1a2fc | ||
|
|
ec0bf05853 | ||
|
|
dc58aa3ea1 | ||
|
|
23b74e4f8b | ||
|
|
d2b699e6f5 | ||
|
|
0338d69324 | ||
|
|
a1fdc6eb6e | ||
|
|
d583460d63 | ||
|
|
2c01dac173 | ||
|
|
137ecb491a | ||
|
|
79d4cfdce8 | ||
|
|
f38ee09082 | ||
|
|
6ab858a5c5 | ||
|
|
3dca27ce0d | ||
|
|
e19704e1f8 | ||
|
|
41ea59ac66 | ||
|
|
e605e6d569 | ||
|
|
9437675d3b | ||
|
|
5301b99533 | ||
|
|
63409d638c | ||
|
|
d68c1dcd6d | ||
|
|
00c7b70211 | ||
|
|
9b9160b206 | ||
|
|
6deec3655b | ||
|
|
7f40422d21 | ||
|
|
e67fa5388b | ||
|
|
80053336c9 | ||
|
|
dddcc507ef | ||
|
|
b41d9da88d | ||
|
|
c04bfb78b7 | ||
|
|
8a5dfc86d4 | ||
|
|
79e103c07e | ||
|
|
5afadf163a | ||
|
|
edfaa37228 | ||
|
|
ea056e98ba | ||
|
|
bab5771e98 | ||
|
|
ee919d6231 | ||
|
|
04c390693a | ||
|
|
49293c4d48 | ||
|
|
8d9e2e7d4e | ||
|
|
ef607f26c2 | ||
|
|
ed543a205c | ||
|
|
de1c28bb16 | ||
|
|
60ff966d54 | ||
|
|
33684253c3 | ||
|
|
b4ca0cd5fb | ||
|
|
a49c1beb93 | ||
|
|
76852cfef3 | ||
|
|
3bd89caf36 | ||
|
|
eb2e7d959c | ||
|
|
a4b20356f4 | ||
|
|
2550b2d1de | ||
|
|
e94e1ab126 | ||
|
|
6307b4fa7d | ||
|
|
19e1a64a91 | ||
|
|
082e776e91 | ||
|
|
92e2f6db83 | ||
|
|
aa624f64c1 | ||
|
|
e1675eb371 | ||
|
|
cc2b6385a1 | ||
|
|
a64bed9bbb | ||
|
|
3272e1c0af | ||
|
|
69da22d517 | ||
|
|
06109f360f | ||
|
|
06a9ef1127 | ||
|
|
5d21bb158b | ||
|
|
bbde41f712 | ||
|
|
8c0994f35f | ||
|
|
3d390bc053 | ||
|
|
16f1be7f10 | ||
|
|
c74df3fab3 | ||
|
|
f2c902ee03 | ||
|
|
4e5e3c5e50 | ||
|
|
da697f218f | ||
|
|
4f47a80deb | ||
|
|
1334162a56 | ||
|
|
ab89517093 | ||
|
|
48a51d8470 | ||
|
|
ab11244f08 | ||
|
|
68ec3ebaa3 | ||
|
|
4f4ed2f242 | ||
|
|
a1c2be140d | ||
|
|
79cc70a62f | ||
|
|
4e6b5a1b0b | ||
|
|
21b144fff9 | ||
|
|
658940de38 | ||
|
|
ad487370f5 | ||
|
|
259f83b549 | ||
|
|
4a24860dcf | ||
|
|
116cdf8661 | ||
|
|
528b8837e1 | ||
|
|
9bf407f336 | ||
|
|
067ad51b3f | ||
|
|
00c77213fb | ||
|
|
c68b8f90b3 | ||
|
|
aa4f9ce9e9 | ||
|
|
d96fc23327 | ||
|
|
4fb044d5f6 | ||
|
|
2a953c5e2b | ||
|
|
abc6a40ccb | ||
|
|
73e68f3c7d | ||
|
|
73777d0bd4 | ||
|
|
ec6d1b9f72 | ||
|
|
d809fed52a | ||
|
|
0e4786dfa8 | ||
|
|
c429262f02 | ||
|
|
f4fdcbcdcb | ||
|
|
b0806469d5 | ||
|
|
e89b58a13f | ||
|
|
2b94ec18b9 | ||
|
|
2a662149f4 |
@@ -12,35 +12,17 @@
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.4",
|
||||
"branchName": "3.4.x",
|
||||
"slug": "3.4",
|
||||
"name": "3.7",
|
||||
"branchName": "3.7.x",
|
||||
"slug": "3.7",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.3",
|
||||
"branchName": "3.3.x",
|
||||
"slug": "3.3",
|
||||
"name": "3.6",
|
||||
"branchName": "3.6.x",
|
||||
"slug": "3.6",
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "3.2",
|
||||
"branchName": "3.2.x",
|
||||
"slug": "3.2",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.1",
|
||||
"branchName": "3.1.x",
|
||||
"slug": "3.1",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "3.0.x",
|
||||
"slug": "3.0",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.21",
|
||||
"branchName": "2.21.x",
|
||||
@@ -55,63 +37,33 @@
|
||||
},
|
||||
{
|
||||
"name": "2.19",
|
||||
"branchName": "2.19.x",
|
||||
"slug": "2.19",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.18",
|
||||
"branchName": "2.18.x",
|
||||
"slug": "2.18",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.17",
|
||||
"branchName": "2.17.x",
|
||||
"slug": "2.17",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.16",
|
||||
"branchName": "2.16.x",
|
||||
"slug": "2.16",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.15",
|
||||
"branchName": "2.15.x",
|
||||
"slug": "2.15",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
"slug": "2.14",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.13",
|
||||
"branchName": "2.13.x",
|
||||
"slug": "2.13",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.12",
|
||||
"branchName": "2.12.x",
|
||||
"slug": "2.12",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.11",
|
||||
"branchName": "2.11.x",
|
||||
"slug": "2.11",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.10",
|
||||
"branchName": "2.10.x",
|
||||
"slug": "2.10",
|
||||
"maintained": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -11,11 +11,10 @@ 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
|
||||
phpstan-baseline.neon export-ignore
|
||||
phpstan-dbal2.neon export-ignore
|
||||
phpstan-dbal3.neon export-ignore
|
||||
phpstan-params.neon export-ignore
|
||||
phpstan-persistence2.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.1.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.1.0"
|
||||
171
.github/workflows/continuous-integration.yml
vendored
171
.github/workflows/continuous-integration.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Continuous Integration"
|
||||
name: "CI: PHPUnit"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -25,7 +25,14 @@ env:
|
||||
|
||||
jobs:
|
||||
phpunit-smoke-check:
|
||||
name: "PHPUnit with SQLite"
|
||||
name: >
|
||||
SQLite -
|
||||
${{ format('PHP {0} - DBAL {1} - ext. {2} - proxy {3}',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø',
|
||||
matrix.proxy || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
@@ -35,6 +42,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -43,31 +51,43 @@ jobs:
|
||||
- "pdo_sqlite"
|
||||
deps:
|
||||
- "highest"
|
||||
lazy_proxy:
|
||||
stability:
|
||||
- "stable"
|
||||
native_lazy:
|
||||
- "0"
|
||||
include:
|
||||
- php-version: "8.2"
|
||||
dbal-version: "4@dev"
|
||||
extension: "pdo_sqlite"
|
||||
lazy_proxy: "0"
|
||||
stability: "stable"
|
||||
native_lazy: "0"
|
||||
- php-version: "8.2"
|
||||
dbal-version: "4@dev"
|
||||
extension: "sqlite3"
|
||||
lazy_proxy: "0"
|
||||
stability: "stable"
|
||||
native_lazy: "0"
|
||||
- php-version: "8.1"
|
||||
dbal-version: "default"
|
||||
deps: "lowest"
|
||||
extension: "pdo_sqlite"
|
||||
lazy_proxy: "0"
|
||||
stability: "stable"
|
||||
native_lazy: "0"
|
||||
- php-version: "8.4"
|
||||
dbal-version: "default"
|
||||
deps: "highest"
|
||||
extension: "pdo_sqlite"
|
||||
lazy_proxy: "1"
|
||||
stability: "stable"
|
||||
native_lazy: "1"
|
||||
- php-version: "8.4"
|
||||
dbal-version: "default"
|
||||
deps: "highest"
|
||||
extension: "sqlite3"
|
||||
stability: "dev"
|
||||
native_lazy: "1"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -79,10 +99,22 @@ jobs:
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Allow dev dependencies"
|
||||
run: |
|
||||
composer config minimum-stability dev
|
||||
composer remove --no-update --dev phpbench/phpbench phpdocumentor/guides-cli
|
||||
composer require --no-update symfony/console:^8 symfony/var-exporter:^8 doctrine/dbal:^4.4
|
||||
composer require --dev --no-update symfony/cache:^8
|
||||
if: "${{ matrix.stability == 'dev' }}"
|
||||
|
||||
- name: "Require specific DBAL version"
|
||||
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:
|
||||
@@ -93,23 +125,79 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
ENABLE_LAZY_PROXY: ${{ matrix.lazy_proxy }}
|
||||
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_LAZY_PROXY: ${{ matrix.lazy_proxy }}
|
||||
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@v6"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.lazy_proxy }}-coverage"
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.deps }}-${{ matrix.stability }}-${{ 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"
|
||||
name: >
|
||||
${{ format('PostgreSQL {0} - PHP {1} - DBAL {2} - ext. {3}',
|
||||
matrix.postgres-version || 'Ø',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
@@ -119,6 +207,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -151,7 +240,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -176,14 +265,20 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
uses: "actions/upload-artifact@v6"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
phpunit-mariadb:
|
||||
name: "PHPUnit with MariaDB"
|
||||
name: >
|
||||
${{ format('MariaDB {0} - PHP {1} - DBAL {2} - ext. {3}',
|
||||
matrix.mariadb-version || 'Ø',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
@@ -193,6 +288,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -218,7 +314,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -243,14 +339,20 @@ jobs:
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v4"
|
||||
uses: "actions/upload-artifact@v6"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
phpunit-mysql:
|
||||
name: "PHPUnit with MySQL"
|
||||
name: >
|
||||
${{ format('MySQL {0} - PHP {1} - DBAL {2} - ext. {3}',
|
||||
matrix.mysql-version || 'Ø',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
@@ -260,6 +362,7 @@ jobs:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3.7"
|
||||
@@ -293,7 +396,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v4"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -319,13 +422,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@v6"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -343,12 +460,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@v7"
|
||||
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.1.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.1.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
|
||||
|
||||
27
README.md
27
README.md
@@ -1,7 +1,7 @@
|
||||
| [4.0.x][4.0] | [3.4.x][3.4] | [3.3.x][3.3] | [2.21.x][2.21] | [2.20.x][2.20] |
|
||||
| [4.0.x][4.0] | [3.7.x][3.7] | [3.6.x][3.6] | [2.21.x][2.21] | [2.20.x][2.20] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0] | [![Build status][3.4 image]][3.4] | [![Build status][3.3 image]][3.3] | [![Build status][2.21 image]][2.21] | [![Build status][2.20 image]][2.20] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.4 coverage image]][3.4 coverage] | [![Coverage Status][3.3 coverage image]][3.3 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
|
||||
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.7 image]][3.7 workflow] | [![Build status][3.6 image]][3.6 workflow] | [![Build status][2.21 image]][2.21 workflow] | [![Build status][2.20 image]][2.20 workflow] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.7 coverage image]][3.7 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
|
||||
|
||||
Doctrine ORM is an object-relational mapper for PHP 8.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
@@ -18,21 +18,26 @@ without requiring unnecessary code duplication.
|
||||
|
||||
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
|
||||
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
|
||||
[4.0 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A4.0.x
|
||||
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
|
||||
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
|
||||
[3.4 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.4.x
|
||||
[3.4]: https://github.com/doctrine/orm/tree/3.4.x
|
||||
[3.4 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.4.x/graph/badge.svg
|
||||
[3.4 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.4.x
|
||||
[3.3 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.3.x
|
||||
[3.3]: https://github.com/doctrine/orm/tree/3.3.x
|
||||
[3.3 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.3.x/graph/badge.svg
|
||||
[3.3 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.3.x
|
||||
[3.7 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.7.x
|
||||
[3.7]: https://github.com/doctrine/orm/tree/3.7.x
|
||||
[3.7 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.7.x
|
||||
[3.7 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.7.x/graph/badge.svg
|
||||
[3.7 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.7.x
|
||||
[3.6 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.6.x
|
||||
[3.6]: https://github.com/doctrine/orm/tree/3.6.x
|
||||
[3.6 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.6.x
|
||||
[3.6 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.6.x/graph/badge.svg
|
||||
[3.6 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.6.x
|
||||
[2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x
|
||||
[2.21]: https://github.com/doctrine/orm/tree/2.21.x
|
||||
[2.21 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.21.x
|
||||
[2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
|
||||
[2.21 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.21.x
|
||||
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
|
||||
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
|
||||
[2.20 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.20.x
|
||||
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
|
||||
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
|
||||
|
||||
268
UPGRADE.md
268
UPGRADE.md
@@ -1,5 +1,233 @@
|
||||
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.6
|
||||
|
||||
## Deprecate using string expression for default values in mappings
|
||||
|
||||
Using a string expression for default values in field mappings is deprecated.
|
||||
Use `Doctrine\DBAL\Schema\DefaultExpression` instances instead.
|
||||
|
||||
Here is how to address this deprecation when mapping entities using PHP attributes:
|
||||
|
||||
```diff
|
||||
use DateTime;
|
||||
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentDate;
|
||||
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTime;
|
||||
+use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
final class TimeEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
public int $id;
|
||||
|
||||
- #[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'], insertable: false, updatable: false)]
|
||||
+ #[ORM\Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
|
||||
public DateTime $createdAt;
|
||||
|
||||
- #[ORM\Column(options: ['default' => 'CURRENT_TIME'], insertable: false, updatable: false)]
|
||||
+ #[ORM\Column(options: ['default' => new CurrentTime()], insertable: false, updatable: false)]
|
||||
public DateTime $createdTime;
|
||||
|
||||
- #[ORM\Column(options: ['default' => 'CURRENT_DATE'], insertable: false, updatable: false)]
|
||||
+ #[ORM\Column(options: ['default' => new CurrentDate()], insertable: false, updatable: false)]
|
||||
public DateTime $createdDate;
|
||||
}
|
||||
```
|
||||
|
||||
Here is how to do the same when mapping entities using XML:
|
||||
|
||||
```diff
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Functional\XmlTimeEntity">
|
||||
<id name="id" type="integer" column="id">
|
||||
<generator strategy="AUTO"/>
|
||||
</id>
|
||||
|
||||
<field name="createdAt" type="datetime" insertable="false" updatable="false">
|
||||
<options>
|
||||
- <option name="default">CURRENT_TIMESTAMP</option>
|
||||
+ <option name="default">
|
||||
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
|
||||
+ </option>
|
||||
</options>
|
||||
</field>
|
||||
|
||||
<field name="createdAtImmutable" type="datetime_immutable" insertable="false" updatable="false">
|
||||
<options>
|
||||
- <option name="default">CURRENT_TIMESTAMP</option>
|
||||
+ <option name="default">
|
||||
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
|
||||
+ </option>
|
||||
</options>
|
||||
</field>
|
||||
|
||||
<field name="createdTime" type="time" insertable="false" updatable="false">
|
||||
<options>
|
||||
- <option name="default">CURRENT_TIME</option>
|
||||
+ <option name="default">
|
||||
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTime"/>
|
||||
+ </option>
|
||||
</options>
|
||||
</field>
|
||||
<field name="createdDate" type="date" insertable="false" updatable="false">
|
||||
<options>
|
||||
- <option name="default">CURRENT_DATE</option>
|
||||
+ <option name="default">
|
||||
+ <object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentDate"/>
|
||||
+ </option>
|
||||
</options>
|
||||
</field>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
```
|
||||
|
||||
|
||||
## Deprecate `FieldMapping::$default`
|
||||
|
||||
The `default` property of `Doctrine\ORM\Mapping\FieldMapping` is deprecated and
|
||||
will be removed in 4.0. Instead, use `FieldMapping::$options['default']`.
|
||||
|
||||
## Deprecate specifying `nullable` on columns that end up being used in a primary key
|
||||
|
||||
Specifying `nullable` on join columns that are part of a primary key is
|
||||
deprecated and will be an error in 4.0.
|
||||
|
||||
This can happen when using a join column mapping together with an id mapping,
|
||||
or when using a join column mapping or an inverse join column mapping on a
|
||||
many-to-many relationship.
|
||||
|
||||
```diff
|
||||
class User
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\ManyToOne(targetEntity: Family::class, inversedBy: 'users')]
|
||||
- #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id', nullable: true)]
|
||||
+ #[ORM\JoinColumn(name: 'family_id', referencedColumnName: 'id')]
|
||||
private ?Family $family;
|
||||
|
||||
#[ORM\ManyToMany(targetEntity: Group::class)]
|
||||
#[ORM\JoinTable(name: 'user_group')]
|
||||
- #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true)]
|
||||
- #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id', nullable: true)]
|
||||
+ #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id')]
|
||||
+ #[ORM\InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
|
||||
private Collection $groups;
|
||||
}
|
||||
```
|
||||
|
||||
## Deprecate `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
|
||||
|
||||
Using `Doctrine\ORM\QueryBuilder::add('join', ...)` with a list of join parts
|
||||
is deprecated in favor of using an associative array of join parts with the
|
||||
root alias as key.
|
||||
|
||||
## Deprecate using the `WITH` keyword for arbitrary DQL joins
|
||||
|
||||
Using the `WITH` keyword to specify the condition for an arbitrary DQL join is
|
||||
deprecated in favor of using the `ON` keyword (similar to the SQL syntax for
|
||||
joins).
|
||||
The `WITH` keyword is now meant to be used only for filtering conditions in
|
||||
association joins.
|
||||
|
||||
# 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
|
||||
possible in 4.0.
|
||||
|
||||
You can enable them through configuration:
|
||||
|
||||
```php
|
||||
$config->enableNativeLazyObjects(true);
|
||||
```
|
||||
|
||||
As a consequence, methods, parameters and commands related to userland lazy
|
||||
objects have been deprecated on PHP 8.4+:
|
||||
|
||||
- `Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand`
|
||||
- `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()`
|
||||
- `Doctrine\ORM\Configuration::getProxyDir()`
|
||||
- `Doctrine\ORM\Configuration::getProxyNamespace()`
|
||||
- `Doctrine\ORM\Configuration::setAutoGenerateProxyClasses()`
|
||||
- `Doctrine\ORM\Configuration::setProxyDir()`
|
||||
- `Doctrine\ORM\Configuration::setProxyNamespace()`
|
||||
- Passing more than one argument to `Doctrine\ORM\Proxy\ProxyFactory::__construct()`
|
||||
|
||||
Additionally, some methods of ORMSetup have been deprecated in favor of a new
|
||||
counterpart.
|
||||
|
||||
- `Doctrine\ORM\ORMSetup::createAttributeMetadataConfiguration()` is deprecated in favor of
|
||||
`Doctrine\ORM\ORMSetup::createAttributeMetadataConfig()`
|
||||
- `Doctrine\ORM\ORMSetup::createXMLMetadataConfiguration()` is deprecated in favor of
|
||||
`Doctrine\ORM\ORMSetup::createXMLMetadataConfig()`
|
||||
- `Doctrine\ORM\ORMSetup::createConfiguration()` is deprecated in favor of
|
||||
`Doctrine\ORM\ORMSetup::createConfig()`
|
||||
|
||||
## Deprecate methods for configuring no longer configurable features
|
||||
|
||||
Since 3.0, lazy ghosts are enabled unconditionally, and so is rejecting ID
|
||||
collisions in the identity map.
|
||||
|
||||
As a consequence, the following methods are deprecated and will be removed in 4.0:
|
||||
* `Doctrine\ORM\Configuration::setLazyGhostObjectEnabled()`
|
||||
* `Doctrine\ORM\Configuration::isLazyGhostObjectEnabled()`
|
||||
* `Doctrine\ORM\Configuration::setRejectIdCollisionInIdentityMap()`
|
||||
* `Doctrine\ORM\Configuration::isRejectIdCollisionInIdentityMapEnabled()`
|
||||
|
||||
# Upgrade to 3.4.1
|
||||
|
||||
## BC BREAK: You can no longer use the `.*` notation to get all fields of an entity in a DTO
|
||||
|
||||
This feature was introduced in 3.4.0, and introduces several issues, so we
|
||||
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.
|
||||
@@ -16,8 +244,12 @@ that implements `ArrayAccess`.
|
||||
Use the new `Doctrine\ORM\Mapping\PropertyAccessors\PropertyAccessor` API and access
|
||||
through `Doctrine\ORM\Mapping\ClassMetadata::$propertyAccessors` instead.
|
||||
|
||||
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.
|
||||
@@ -26,7 +258,7 @@ The class `Doctrine\ORM\Mapping\Driver\DatabaseDriver` is deprecated without rep
|
||||
|
||||
Output walkers should implement the new `\Doctrine\ORM\Query\OutputWalker` interface and create
|
||||
`Doctrine\ORM\Query\Exec\SqlFinalizer` instances instead of `Doctrine\ORM\Query\Exec\AbstractSqlExecutor`s.
|
||||
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
|
||||
The output walker must not base its workings on the query `firstResult`/`maxResult` values, so that the
|
||||
`SqlFinalizer` can be kept in the query cache and used regardless of the actual `firstResult`/`maxResult` values.
|
||||
Any operation dependent on `firstResult`/`maxResult` should take place within the `SqlFinalizer::createExecutor()`
|
||||
method. Details can be found at https://github.com/doctrine/orm/pull/11188.
|
||||
@@ -34,6 +266,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.
|
||||
@@ -60,6 +294,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.
|
||||
@@ -83,6 +319,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
|
||||
@@ -108,6 +346,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
|
||||
@@ -120,6 +361,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
|
||||
|
||||
@@ -137,7 +399,7 @@ WARNING: This was relaxed in ORM 3.2 when partial was re-allowed for array-hydra
|
||||
`Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD` are removed.
|
||||
- `Doctrine\ORM\EntityManager*::getPartialReference()` is removed.
|
||||
|
||||
## BC BREAK: Enforce ArrayCollection Type on `\Doctrine\ORM\QueryBuilder::setParameters(ArrayCollection $parameters)`
|
||||
## BC BREAK: Enforce ArrayCollection Type on `\Doctrine\ORM\QueryBuilder::setParameters(ArrayCollection $parameters)`
|
||||
|
||||
The argument $parameters can no longer be a key=>value array. Only ArrayCollection types are allowed.
|
||||
|
||||
@@ -2039,7 +2301,7 @@ from 2.0 have to configure the annotation driver if they don't use `Configuratio
|
||||
|
||||
## Scalar mappings can now be omitted from DQL result
|
||||
|
||||
You are now allowed to mark scalar SELECT expressions as HIDDEN an they are not hydrated anymore.
|
||||
You are now allowed to mark scalar SELECT expressions as HIDDEN and they are not hydrated anymore.
|
||||
Example:
|
||||
|
||||
SELECT u, SUM(a.id) AS HIDDEN numArticles FROM User u LEFT JOIN u.Articles a ORDER BY numArticles DESC HAVING numArticles > 10
|
||||
|
||||
@@ -1,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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ Now this is all awfully technical, so let me come to some use-cases
|
||||
fast to keep you motivated. Using walker implementation you can for
|
||||
example:
|
||||
|
||||
|
||||
- Modify the Output walker to get the raw SQL via ``Query->getSQL()``
|
||||
with interpolated parameters.
|
||||
- Modify the AST to generate a Count Query to be used with a
|
||||
paginator for any given DQL query.
|
||||
- Modify the Output Walker to generate vendor-specific SQL
|
||||
@@ -50,7 +51,7 @@ example:
|
||||
- Modify the Output walker to pretty print the SQL for debugging
|
||||
purposes.
|
||||
|
||||
In this cookbook-entry I will show examples of the first two
|
||||
In this cookbook-entry I will show examples of the first three
|
||||
points. There are probably much more use-cases.
|
||||
|
||||
Generic count query for pagination
|
||||
@@ -101,8 +102,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 +120,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:
|
||||
@@ -215,3 +224,39 @@ huge benefits with using vendor specific features. This would still
|
||||
allow you write DQL queries instead of NativeQueries to make use of
|
||||
vendor specific features.
|
||||
|
||||
Modifying the Output Walker to get the raw SQL with interpolated parameters
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Sometimes we may want to log or trace the raw SQL being generated from its DQL
|
||||
for profiling slow queries afterwards or audit queries that changed many rows
|
||||
``$query->getSQL()`` will give us the prepared statement being passed to database
|
||||
with all values of SQL parameters being replaced by positional ``?`` or named ``:name``
|
||||
as parameters are interpolated into prepared statements by the database while executing the SQL.
|
||||
``$query->getParameters()`` will give us details about SQL parameters that we've provided.
|
||||
So we can create an output walker to interpolate all SQL parameters that will be
|
||||
passed into prepared statement in PHP before database handle them internally:
|
||||
|
||||
.. literalinclude:: dql-custom-walkers/InterpolateParametersSQLOutputWalker.php
|
||||
:language: php
|
||||
|
||||
Then you may get the raw SQL with this output walker:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query
|
||||
->where('t.int IN (:ints)')->setParameter(':ints', [1, 2])
|
||||
->orWhere('t.string IN (?0)')->setParameter(0, ['3', '4'])
|
||||
->orWhere("t.bool = ?1")->setParameter('?1', true)
|
||||
->orWhere("t.string = :string")->setParameter(':string', 'ABC')
|
||||
->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, InterpolateParametersSQLOutputWalker::class)
|
||||
->getSQL();
|
||||
|
||||
The where clause of the returned SQL should be like:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
WHERE t0_.int IN (1, 2)
|
||||
OR t0_.string IN ('3', '4')
|
||||
OR t0_.bool = 1
|
||||
OR t0_.string = 'ABC'
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\DBAL\Types\BooleanType;
|
||||
use Doctrine\DBAL\Types\Exception\ValueNotConvertible;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\ORM\Query\AST;
|
||||
use Doctrine\ORM\Query\SqlOutputWalker;
|
||||
|
||||
class InterpolateParametersSQLOutputWalker extends SqlOutputWalker
|
||||
{
|
||||
/** {@inheritdoc} */
|
||||
public function walkInputParameter(AST\InputParameter $inputParam): string
|
||||
{
|
||||
$parameter = $this->getQuery()->getParameter($inputParam->name);
|
||||
if ($parameter === null) {
|
||||
return '?';
|
||||
}
|
||||
|
||||
$value = $parameter->getValue();
|
||||
/** @var ParameterType|ArrayParameterType|int|string $typeName */
|
||||
/** @see \Doctrine\ORM\Query\ParameterTypeInferer::inferType() */
|
||||
$typeName = $parameter->getType();
|
||||
$platform = $this->getConnection()->getDatabasePlatform();
|
||||
$processParameterType = static fn(ParameterType $type) => static fn($value): string =>
|
||||
(match ($type) { /** @see Type::getBindingType() */
|
||||
ParameterType::NULL => 'NULL',
|
||||
ParameterType::INTEGER => $value,
|
||||
ParameterType::BOOLEAN => (new BooleanType())->convertToDatabaseValue($value, $platform),
|
||||
ParameterType::STRING, ParameterType::ASCII => $platform->quoteStringLiteral($value),
|
||||
default => throw new ValueNotConvertible($value, $type->name)
|
||||
});
|
||||
|
||||
if (is_string($typeName) && Type::hasType($typeName)) {
|
||||
return Type::getType($typeName)->convertToDatabaseValue($value, $platform);
|
||||
}
|
||||
if ($typeName instanceof ParameterType) {
|
||||
return $processParameterType($typeName)($value);
|
||||
}
|
||||
if ($typeName instanceof ArrayParameterType && is_array($value)) {
|
||||
$type = ArrayParameterType::toElementParameterType($typeName);
|
||||
return implode(', ', array_map($processParameterType($type), $value));
|
||||
}
|
||||
|
||||
throw new ValueNotConvertible($value, $typeName);
|
||||
}
|
||||
}
|
||||
@@ -46,17 +46,18 @@ entities:
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
public const STATUS_VISIBLE = 'visible';
|
||||
public const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
#[Column(type: "string")]
|
||||
private $status;
|
||||
|
||||
public function setStatus($status)
|
||||
public function setStatus(string $status): void
|
||||
{
|
||||
if (!in_array($status, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||
if (!in_array($status, [self::STATUS_VISIBLE, self::STATUS_INVISIBLE], true)) {
|
||||
throw new \InvalidArgumentException("Invalid status");
|
||||
}
|
||||
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
@@ -92,37 +93,33 @@ For example for the previous enum type:
|
||||
|
||||
class EnumVisibilityType extends Type
|
||||
{
|
||||
const ENUM_VISIBILITY = 'enumvisibility';
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
private const ENUM_VISIBILITY = 'enumvisibility';
|
||||
private const STATUS_VISIBLE = 'visible';
|
||||
private const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
|
||||
{
|
||||
return "ENUM('visible', 'invisible')";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): string
|
||||
{
|
||||
if (!in_array($value, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||
if (!in_array($value, [self::STATUS_VISIBLE, self::STATUS_INVISIBLE], true)) {
|
||||
throw new \InvalidArgumentException("Invalid status");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return self::ENUM_VISIBILITY;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
|
||||
@@ -151,37 +148,33 @@ You can generalize this approach easily to create a base class for enums:
|
||||
abstract class EnumType extends Type
|
||||
{
|
||||
protected $name;
|
||||
protected $values = array();
|
||||
protected $values = [];
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
|
||||
{
|
||||
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
|
||||
$values = array_map(fn($val) => "'".$val."'", $this->values);
|
||||
|
||||
return "ENUM(".implode(", ", $values).")";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed
|
||||
{
|
||||
if (!in_array($value, $this->values)) {
|
||||
if (!in_array($value, $this->values, true)) {
|
||||
throw new \InvalidArgumentException("Invalid '".$this->name."' value.");
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
With this base class you can define an enum as easily as:
|
||||
@@ -194,5 +187,5 @@ With this base class you can define an enum as easily as:
|
||||
class EnumVisibilityType extends EnumType
|
||||
{
|
||||
protected $name = 'enumvisibility';
|
||||
protected $values = array('visible', 'invisible');
|
||||
protected $values = ['visible', 'invisible'];
|
||||
}
|
||||
|
||||
@@ -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>`_
|
||||
|
||||
@@ -29,7 +29,7 @@ steps of configuration.
|
||||
|
||||
$config = new Configuration;
|
||||
$config->setMetadataCache($metadataCache);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCache($queryCache);
|
||||
|
||||
@@ -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**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -154,15 +156,59 @@ The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
The path information to the entities is required for the attribute
|
||||
driver, because otherwise mass-operations on all entities through
|
||||
the console could not work correctly. All of metadata drivers
|
||||
accept either a single directory as a string or an array of
|
||||
directories. With this feature a single driver can support multiple
|
||||
directories of Entities.
|
||||
the console could not work correctly. Metadata drivers can accept either
|
||||
a single directory as a string or an array of directories.
|
||||
|
||||
AttributeDriver also accepts ``Doctrine\Persistence\Mapping\Driver\ClassLocator``,
|
||||
allowing one to customize file discovery logic. You may choose to use Symfony Finder, or
|
||||
utilize directory scan with ``FileClassLocator::createFromDirectories()``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
use Doctrine\Persistence\Mapping\Driver\FileClassLocator;
|
||||
|
||||
$paths = ['/path/to/lib/MyProject/Entities'];
|
||||
$classLocator = FileClassLocator::createFromDirectories($paths);
|
||||
|
||||
$driverImpl = new AttributeDriver($classLocator);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
With this feature, you're empowered to provide a fine-grained iterator of only necessary
|
||||
files to the Driver. For example, if you are using Vertical Slice architecture, you can
|
||||
exclude ``*Test.php``, ``*Controller.php``, ``*Service.php``, etc.:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
$finder = new Finder()->files()->in($paths)
|
||||
->name('*.php')
|
||||
->notName(['*Test.php', '*Controller.php', '*Service.php']);
|
||||
|
||||
$classLocator = new FileClassLocator($finder);
|
||||
|
||||
If you know the list of class names you want to track, use
|
||||
``Doctrine\Persistence\Mapping\Driver\ClassNames``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Persistence\Mapping\Driver\ClassNames;
|
||||
use App\Entity\{Article, Book};
|
||||
|
||||
$entityClasses = [Article::class, Book::class];
|
||||
$classLocator = new ClassNames($entityClasses);
|
||||
|
||||
$driverImpl = new AttributeDriver($classLocator);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
Metadata Cache (**RECOMMENDED**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -302,7 +348,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.
|
||||
|
||||
@@ -175,6 +175,10 @@ Optional parameters:
|
||||
- **unique**: Boolean value to determine if the value of the column
|
||||
should be unique across all rows of the underlying entities table.
|
||||
|
||||
- **index**: Boolean value to generate an index for this column.
|
||||
For more advanced usages, take a look at :ref:`#[Index] <attrref_index>`.
|
||||
If not specified, default value is ``false``.
|
||||
|
||||
- **nullable**: Determines if NULL values allowed for this column.
|
||||
If not specified, default value is ``false``.
|
||||
|
||||
@@ -245,6 +249,9 @@ Examples:
|
||||
#[Column(type: "string", length: 32, unique: true, nullable: false)]
|
||||
protected $username;
|
||||
|
||||
#[Column(type: "string", index: true)]
|
||||
protected $firstName;
|
||||
|
||||
#[Column(type: "string", columnDefinition: "CHAR(2) NOT NULL")]
|
||||
protected $country;
|
||||
|
||||
@@ -661,11 +668,6 @@ and in the Context of a :ref:`#[ManyToMany] <attrref_manytomany>`. If this attri
|
||||
are missing they will be computed considering the field's name and the current
|
||||
:doc:`naming strategy <namingstrategy>`.
|
||||
|
||||
The ``#[InverseJoinColumn]`` is the same as ``#[JoinColumn]`` and is used in the context
|
||||
of a ``#[ManyToMany]`` attribute declaration to specifiy the details of the join table's
|
||||
column information used for the join to the inverse entity. This is only required
|
||||
on PHP 8.0, where nested attributes are not yet supported.
|
||||
|
||||
Optional parameters:
|
||||
|
||||
- **name**: Column name that holds the foreign key identifier for
|
||||
|
||||
@@ -182,6 +182,37 @@ 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.
|
||||
|
||||
When using XML, you can specify object instances using the ``<object>``
|
||||
element:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="createdAt" type="datetime" insertable="false" updatable="false">
|
||||
<options>
|
||||
<option name="default">
|
||||
<object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
|
||||
</option>
|
||||
</options>
|
||||
</field>
|
||||
|
||||
The ``<object>`` element requires a ``class`` attribute specifying the
|
||||
fully qualified class name to instantiate.
|
||||
|
||||
.. 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 +420,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
|
||||
|
||||
20
docs/en/reference/basic-mapping/DefaultValues.php
Normal file
20
docs/en/reference/basic-mapping/DefaultValues.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
#[Entity]
|
||||
class Message
|
||||
{
|
||||
#[Column(options: ['default' => 'Hello World!'])]
|
||||
private string $text;
|
||||
|
||||
#[Column(options: ['default' => new CurrentTimestamp()], insertable: false, updatable: false)]
|
||||
private DateTime $createdAt;
|
||||
}
|
||||
16
docs/en/reference/basic-mapping/default-values.xml
Normal file
16
docs/en/reference/basic-mapping/default-values.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<doctrine-mapping>
|
||||
<entity name="Message">
|
||||
<field name="text">
|
||||
<options>
|
||||
<option name="default">Hello World!</option>
|
||||
</options>
|
||||
</field>
|
||||
<field name="createdAt" insertable="false" updatable="false">
|
||||
<options>
|
||||
<option name="default">
|
||||
<object class="Doctrine\DBAL\Schema\DefaultExpression\CurrentTimestamp"/>
|
||||
</option>
|
||||
</options>
|
||||
</field>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -56,7 +56,8 @@ access point to ORM functionality provided by Doctrine.
|
||||
'dbname' => 'foo',
|
||||
];
|
||||
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createAttributeMetadataConfig($paths, $isDevMode);
|
||||
// on PHP < 8.4, use ORMSetup::createAttributeMetadataConfiguration() instead
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
@@ -66,7 +67,8 @@ Or if you prefer XML:
|
||||
|
||||
<?php
|
||||
$paths = ['/path/to/xml-mappings'];
|
||||
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createXMLMetadataConfig($paths, $isDevMode);
|
||||
// on PHP < 8.4, use ORMSetup::createXMLMetadataConfiguration() instead
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@ where you can generate an arbitrary join with the following syntax:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b WITH u.email = b.email');
|
||||
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b ON u.email = b.email');
|
||||
|
||||
With an arbitrary join the result differs from the joins using a mapped property.
|
||||
The result of an arbitrary join is an one dimensional array with a mix of the entity from the ``SELECT``
|
||||
@@ -513,13 +513,15 @@ it loads all the related ``Banlist`` objects corresponding to this ``User``. Thi
|
||||
when the DQL is switched to an arbitrary join.
|
||||
|
||||
.. note::
|
||||
The differences between WHERE, WITH and HAVING clauses may be
|
||||
The differences between WHERE, WITH, ON and HAVING clauses may be
|
||||
confusing.
|
||||
|
||||
- WHERE is applied to the results of an entire query
|
||||
- WITH is applied to a join as an additional condition. For
|
||||
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
|
||||
the WITH is required, even if it is 1 = 1
|
||||
- ON is applied to arbitrary joins as the join condition. For
|
||||
arbitrary joins (SELECT f, b FROM Foo f, Bar b ON f.id = b.id)
|
||||
the ON is required, even if it is 1 = 1. WITH is also
|
||||
supported as alternative keyword for that case for BC reasons.
|
||||
- WITH is applied to an association join as an additional condition.
|
||||
- HAVING is applied to the results of a query after
|
||||
aggregation (GROUP BY)
|
||||
|
||||
@@ -588,7 +590,7 @@ And then use the ``NEW`` DQL keyword :
|
||||
$query = $em->createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c');
|
||||
$users = $query->getResult(); // array of CustomerDTO
|
||||
|
||||
You can also nest several DTO :
|
||||
You can also nest several DTO :
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -684,17 +686,6 @@ You can hydrate an entity nested in a DTO :
|
||||
|
||||
// CustomerDTO => {name : 'DOE', email: null, address : {city: 'New York', zip: '10011', address: 'Abbey Road'}
|
||||
|
||||
In a Dto, if you want add all fields of an entity, you can use ``.*`` :
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT NEW NAMED CustomerDTO(c.name, NEW NAMED AddressDTO(a.*) AS address) FROM Customer c JOIN c.address a');
|
||||
$users = $query->getResult(); // array of CustomerDTO
|
||||
|
||||
// CustomerDTO => {name : 'DOE', email: null, city: null, address: {id: 18, city: 'New York', zip: '10011'}}
|
||||
|
||||
It's recommended to use named arguments DTOs with the ``.*`` notation because argument order is not guaranteed.
|
||||
|
||||
Using INDEX BY
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1420,8 +1411,7 @@ Result Cache API:
|
||||
|
||||
$query->setResultCacheDriver(new ApcCache());
|
||||
|
||||
$query->useResultCache(true)
|
||||
->setResultCacheLifeTime(3600);
|
||||
$query->enableResultCache(3600);
|
||||
|
||||
$result = $query->getResult(); // cache miss
|
||||
|
||||
@@ -1431,8 +1421,8 @@ Result Cache API:
|
||||
$query->setResultCacheId('my_query_result');
|
||||
$result = $query->getResult(); // saved in given result cache id.
|
||||
|
||||
// or call useResultCache() with all parameters:
|
||||
$query->useResultCache(true, 3600, 'my_query_result');
|
||||
// or call enableResultCache() with all parameters:
|
||||
$query->enableResultCache(3600, 'my_query_result');
|
||||
$result = $query->getResult(); // cache hit!
|
||||
|
||||
// Introspection
|
||||
@@ -1710,22 +1700,26 @@ From, Join and Index by
|
||||
SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
|
||||
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
||||
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
|
||||
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
|
||||
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
|
||||
IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
|
||||
|
||||
.. note::
|
||||
Using the ``WITH`` keyword for the ``ConditionalExpression`` of a
|
||||
``RangeVariableDeclaration`` is deprecated and will be removed in
|
||||
ORM 4.0. Use the ``ON`` keyword instead.
|
||||
|
||||
Select Expressions
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
|
||||
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression) [["AS"] ["HIDDEN"] AliasResultVariable]
|
||||
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
|
||||
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
|
||||
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
|
||||
NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression | EntityAsDtoArgumentExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
|
||||
NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression | EntityAsDtoArgumentExpression) ["AS" AliasResultVariable]
|
||||
EntityAsDtoArgumentExpression ::= IdentificationVariable
|
||||
AllFieldsExpression ::= IdentificationVariable ".*"
|
||||
|
||||
Conditional Expressions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -208,6 +208,22 @@ Example:
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Person" inheritance-type="SINGLE_TABLE">
|
||||
<discriminator-column name="discr" type="string" />
|
||||
<discriminator-map>
|
||||
<discriminator-mapping value="person" class="MyProject\Model\Person"/>
|
||||
<discriminator-mapping value="employee" class="MyProject\Model\Employee"/>
|
||||
</discriminator-map>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Employee">
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
In this example, the ``#[DiscriminatorMap]`` specifies that in the
|
||||
discriminator column, a value of "person" identifies a row as being of type
|
||||
|
||||
@@ -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
|
||||
------------
|
||||
|
||||
|
||||
@@ -209,6 +209,7 @@ Field & Association Getters
|
||||
|
||||
- ``isUniqueField($fieldName)``
|
||||
- ``isNullable($fieldName)``
|
||||
- ``isIndexed($fieldName)``
|
||||
- ``getColumnName($fieldName)``
|
||||
- ``getFieldMapping($fieldName)``
|
||||
- ``getAssociationMapping($fieldName)``
|
||||
|
||||
@@ -344,10 +344,10 @@ the Query object which can be retrieved from ``EntityManager#createQuery()``.
|
||||
Executing a Query
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The QueryBuilder is a builder object only - it has no means of actually
|
||||
executing the Query. Additionally a set of parameters such as query hints
|
||||
cannot be set on the QueryBuilder itself. This is why you always have to convert
|
||||
a querybuilder instance into a Query object:
|
||||
The QueryBuilder is only a builder object - it has no means of actually
|
||||
executing the Query. Additional functionality, such as enabling the result cache,
|
||||
cannot be set on the QueryBuilder itself. This is why you must always convert
|
||||
a QueryBuilder instance into a Query object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -355,9 +355,8 @@ a querybuilder instance into a Query object:
|
||||
// $qb instanceof QueryBuilder
|
||||
$query = $qb->getQuery();
|
||||
|
||||
// Set additional Query options
|
||||
$query->setQueryHint('foo', 'bar');
|
||||
$query->useResultCache('my_cache_id');
|
||||
// Enable the result cache
|
||||
$query->enableResultCache(3600, 'my_custom_id');
|
||||
|
||||
// Execute Query
|
||||
$result = $query->getResult();
|
||||
@@ -555,6 +554,24 @@ using ``addCriteria``:
|
||||
$qb->addCriteria($criteria);
|
||||
// then execute your query like normal
|
||||
|
||||
Adding hints to a Query
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also set query hints to a QueryBuilder by using ``setHint``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// ...
|
||||
|
||||
// $qb instanceof QueryBuilder
|
||||
$qb->setHint('hintName', 'hintValue');
|
||||
// then execute your query like normal
|
||||
|
||||
The query hint can hold anything the usual query hints can hold
|
||||
except null. Those hints will be applied to the query when the
|
||||
query is created.
|
||||
|
||||
Low Level API
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -133,7 +133,7 @@ Caching mode
|
||||
* Read Write cache employs locks before update/delete.
|
||||
* Use if data needs to be updated.
|
||||
* Slowest strategy.
|
||||
* To use it a the cache region implementation must support locking.
|
||||
* To use it the cache region implementation must support locking.
|
||||
|
||||
|
||||
Built-in cached persisters
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ have to register them yourself.
|
||||
All the commands of the Doctrine Console require access to the
|
||||
``EntityManager``. You have to inject it into the console application.
|
||||
|
||||
Here is an example of a the project-specific ``bin/doctrine`` binary.
|
||||
Here is an example of a project-specific ``bin/doctrine`` binary.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -84,7 +84,7 @@ The following Commands are currently available:
|
||||
- ``orm:clear-cache:result`` Clear result cache of the various
|
||||
cache drivers.
|
||||
- ``orm:generate-proxies`` Generates proxy classes for entity
|
||||
classes.
|
||||
classes. Deprecated in favor of using native lazy objects.
|
||||
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
|
||||
line.
|
||||
- ``orm:schema-tool:create`` Processes the schema and either
|
||||
@@ -96,6 +96,10 @@ The following Commands are currently available:
|
||||
- ``orm:schema-tool:update`` Processes the schema and either
|
||||
update the database schema of EntityManager Storage Connection or
|
||||
generate the SQL output.
|
||||
- ``orm:debug:event-manager`` Lists event listeners for an entity
|
||||
manager, optionally filtered by event name.
|
||||
- ``orm:debug:entity-listeners`` Lists entity listeners for a given
|
||||
entity, optionally filtered by event name.
|
||||
|
||||
The following alias is defined:
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@ of several common elements:
|
||||
|
||||
<indexes>
|
||||
<index name="name_idx" columns="name"/>
|
||||
<index columns="user_email"/>
|
||||
</indexes>
|
||||
|
||||
<unique-constraints>
|
||||
@@ -131,7 +130,7 @@ of several common elements:
|
||||
</id>
|
||||
|
||||
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
|
||||
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
|
||||
<field name="email" column="user_email" type="string" index="true" column-definition="CHAR(32) NOT NULL" />
|
||||
|
||||
<one-to-one field="address" target-entity="Address" inversed-by="user">
|
||||
<cascade><cascade-remove /></cascade>
|
||||
@@ -255,6 +254,8 @@ Optional attributes:
|
||||
only.
|
||||
- unique - Should this field contain a unique value across the
|
||||
table? Defaults to false.
|
||||
- index - Should an index be created for this column? Defaults to
|
||||
false.
|
||||
- nullable - Should this field allow NULL as a value? Defaults to
|
||||
false.
|
||||
- insertable - Should this field be inserted? Defaults to true.
|
||||
|
||||
@@ -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
|
||||
-----------------------------
|
||||
@@ -138,12 +139,12 @@ step:
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Attributes
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration(
|
||||
$config = ORMSetup::createAttributeMetadataConfig( // on PHP < 8.4, use ORMSetup::createAttributeMetadataConfiguration()
|
||||
paths: [__DIR__ . '/src'],
|
||||
isDevMode: true,
|
||||
);
|
||||
// or if you prefer XML
|
||||
// $config = ORMSetup::createXMLMetadataConfiguration(
|
||||
// $config = ORMSetup::createXMLMetadataConfig( // on PHP < 8.4, use ORMSetup::createXMLMetadataConfiguration()
|
||||
// paths: [__DIR__ . '/config/xml'],
|
||||
// isDevMode: true,
|
||||
//);
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -155,10 +155,20 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="object">
|
||||
<xs:attribute name="class" type="xs:string" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="option" mixed="true">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="option" type="orm:option"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
<xs:choice minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="object" type="orm:object"/>
|
||||
<xs:sequence>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="option" type="orm:option"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
</xs:choice>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
@@ -243,6 +253,7 @@
|
||||
<xs:attribute name="length" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="unique" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="nullable" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="index" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="insertable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="updatable" type="xs:boolean" default="true" />
|
||||
<xs:attribute name="generated" type="orm:generated-type" default="NEVER" />
|
||||
|
||||
@@ -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
|
||||
@@ -637,7 +619,7 @@ parameters:
|
||||
path: src/EntityRepository.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\>&Doctrine\\Common\\Collections\\Selectable\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
|
||||
message: '#^Method Doctrine\\ORM\\EntityRepository\:\:matching\(\) should return Doctrine\\Common\\Collections\\AbstractLazyCollection\<int, T of object\> but returns Doctrine\\ORM\\LazyCriteriaCollection\<\(int\|string\), object\>\.$#'
|
||||
identifier: return.type
|
||||
count: 1
|
||||
path: src/EntityRepository.php
|
||||
@@ -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
|
||||
@@ -1333,69 +1327,45 @@ parameters:
|
||||
path: src/Mapping/Driver/AttributeDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedColumn…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedTableName\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencedTableN…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencingColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\ForeignKeyConstraint'' and ''getReferencingColum…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Index\:\:getIndexedColumns\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:getPrimaryKeyConstraint\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Table'' and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnName\(\) on an unknown class Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Index and ''getType'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnNames\(\) on an unknown class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Call to method toString\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 5
|
||||
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: '#^Class Doctrine\\DBAL\\Schema\\Index\\IndexType not found\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint not found\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 2
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
@@ -1428,36 +1398,24 @@ 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
|
||||
count: 2
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$array of function sort contains unresolvable type\.$#'
|
||||
identifier: argument.unresolvableType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$columnName of method Doctrine\\ORM\\Mapping\\Driver\\DatabaseDriver\:\:getFieldNameForColumn\(\) expects string, string\|false given\.$#'
|
||||
identifier: argument.type
|
||||
count: 4
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$indexedColumn of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$name of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 5
|
||||
path: src/Mapping/Driver/DatabaseDriver.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Mapping\\Driver\\SimplifiedXmlDriver\:\:__construct\(\) has parameter \$fileExtension with no type specified\.$#'
|
||||
identifier: missingType.parameter
|
||||
@@ -1483,7 +1441,7 @@ parameters:
|
||||
path: src/Mapping/Driver/XmlDriver.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columnDef of method Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:setDiscriminatorColumn\(\) expects array\{name\: string\|null, fieldName\?\: string\|null, type\?\: string\|null, length\?\: int\|null, columnDefinition\?\: string\|null, enumType\?\: class\-string\<BackedEnum\>\|null, options\?\: array\<string, mixed\>\|null\}\|Doctrine\\ORM\\Mapping\\DiscriminatorColumnMapping\|null, array\{name\: string\|null, type\: string, length\: int, columnDefinition\: string\|null, enumType\: string\|null, options\?\: array\<int\|string, array\<int\|string, mixed\>\|bool\|string\>\} given\.$#'
|
||||
message: '#^Parameter \#1 \$columnDef of method Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:setDiscriminatorColumn\(\) expects array\{name\: string\|null, fieldName\?\: string\|null, type\?\: string\|null, length\?\: int\|null, columnDefinition\?\: string\|null, enumType\?\: class\-string\<BackedEnum\>\|null, options\?\: array\<string, mixed\>\|null\}\|Doctrine\\ORM\\Mapping\\DiscriminatorColumnMapping\|null, array\{name\: string\|null, type\: string, length\: int, columnDefinition\: string\|null, enumType\: string\|null, options\?\: array\<int\|string, array\<int\|string, mixed\>\|bool\|object\|string\>\} given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Mapping/Driver/XmlDriver.php
|
||||
@@ -1513,7 +1471,7 @@ parameters:
|
||||
path: src/Mapping/Driver/XmlDriver.php
|
||||
|
||||
-
|
||||
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:\$table \(array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\?\: array\<string, mixed\>, quoted\?\: bool\}\) does not accept array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\: array\<int\|string, array\<int\|string, mixed\>\|bool\|string\>, quoted\?\: bool\}\.$#'
|
||||
message: '#^Property Doctrine\\ORM\\Mapping\\ClassMetadata\<T of object\>\:\:\$table \(array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\?\: array\<string, mixed\>, quoted\?\: bool\}\) does not accept array\{name\: string, schema\?\: string, indexes\?\: array, uniqueConstraints\?\: array, options\: array\<int\|string, array\<int\|string, mixed\>\|bool\|object\|string\>, quoted\?\: bool\}\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
path: src/Mapping/Driver/XmlDriver.php
|
||||
@@ -1572,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,6 +1986,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Persisters/Collection/OneToManyPersister.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\<array\{name\: string, type\: Doctrine\\DBAL\\Types\\Type, default\: mixed, notnull\?\: bool, autoincrement\: bool, columnDefinition\: non\-empty\-string\|null, comment\: string, charset\?\: non\-empty\-string\|null, \.\.\.\}\>, array\<string, array\{name\: string, notnull\: true, type\: Doctrine\\DBAL\\Types\\Type\}\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Persisters/Collection/OneToManyPersister.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Persisters\\Entity\\AbstractEntityInheritancePersister\:\:getSelectColumnSQL\(\) has parameter \$class with generic class Doctrine\\ORM\\Mapping\\ClassMetadata but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
@@ -2052,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
|
||||
@@ -2076,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
|
||||
@@ -2124,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
|
||||
@@ -2142,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
|
||||
@@ -2208,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
|
||||
@@ -2610,6 +2532,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableDeleteExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\<array\{name\: string, type\: Doctrine\\DBAL\\Types\\Type, default\: mixed, notnull\?\: bool, autoincrement\: bool, columnDefinition\: non\-empty\-string\|null, comment\: string, charset\?\: non\-empty\-string\|null, \.\.\.\}\>, array\<string, array\{name\: string, notnull\: true, type\: Doctrine\\DBAL\\Types\\Type\}\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableDeleteExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Argument of an invalid type list\<string\>\|string supplied for foreach, only iterables are supported\.$#'
|
||||
identifier: foreach.nonIterable
|
||||
@@ -2623,7 +2551,13 @@ parameters:
|
||||
path: src/Query/Exec/MultiTableUpdateExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\<int\<0, max\>\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, list\<Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|int\|string\> given\.$#'
|
||||
message: '#^Parameter \#1 \$columns of method Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:getColumnDeclarationListSQL\(\) expects list\<array\{name\: string, type\: Doctrine\\DBAL\\Types\\Type, default\: mixed, notnull\?\: bool, autoincrement\: bool, columnDefinition\: non\-empty\-string\|null, comment\: string, charset\?\: non\-empty\-string\|null, \.\.\.\}\>, array\<string, array\{name\: string, notnull\: true, type\: Doctrine\\DBAL\\Types\\Type\}\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableUpdateExecutor.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$types of method Doctrine\\DBAL\\Connection\:\:executeStatement\(\) expects array\<int\<0, max\>\|string, Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|Doctrine\\DBAL\\Types\\Type\|string\>, 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\|Doctrine\\DBAL\\Types\\Type\|int\|string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Query/Exec/MultiTableUpdateExecutor.php
|
||||
@@ -2670,12 +2604,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Query/Expr/Select.php
|
||||
|
||||
-
|
||||
message: '#^Property Doctrine\\ORM\\Query\\Filter\\SQLFilter\:\:\$parameters \(array\<string, array\{type\: string, value\: mixed, is_list\: bool\}\>\) does not accept non\-empty\-array\<string, array\{value\: mixed, type\: Doctrine\\DBAL\\ArrayParameterType\|Doctrine\\DBAL\\ParameterType\|int\|string, is_list\: bool\}\>\.$#'
|
||||
identifier: assign.propertyType
|
||||
count: 1
|
||||
path: src/Query/Filter/SQLFilter.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns int so it can be removed from the return type\.$#'
|
||||
identifier: return.unusedType
|
||||
@@ -2811,7 +2739,7 @@ parameters:
|
||||
-
|
||||
message: '#^Cannot assign new offset to list\<string\>\|string\.$#'
|
||||
identifier: offsetAssign.dimType
|
||||
count: 3
|
||||
count: 2
|
||||
path: src/Query/SqlWalker.php
|
||||
|
||||
-
|
||||
@@ -2946,12 +2874,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
|
||||
@@ -3108,12 +3030,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
|
||||
@@ -3121,32 +3037,71 @@ parameters:
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getColumns\(\) of class Doctrine\\DBAL\\Schema\\Index\:
|
||||
Use \{@see getIndexedColumns\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencedTableName\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getForeignColumns\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
|
||||
Use \{@see getReferencedColumnNames\(\)\} instead\.
|
||||
|
||||
Returns the names of the referenced table columns
|
||||
the foreign key constraint is associated with\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:\:getReferencingColumnNames\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getForeignTableName\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
|
||||
Use \{@see getReferencedTableName\(\)\} instead\.
|
||||
|
||||
Returns the name of the referenced table
|
||||
the foreign key constraint is associated with\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Index\:\:getIndexedColumns\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getLocalColumns\(\) of class Doctrine\\DBAL\\Schema\\ForeignKeyConstraint\:
|
||||
Use \{@see getReferencingColumnNames\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Doctrine\\DBAL\\Schema\\Table\:\:dropForeignKey\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
message: '''
|
||||
#^Call to deprecated method getPrimaryKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
|
||||
Use \{@see getPrimaryKeyConstraint\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Call to deprecated method removeForeignKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
|
||||
Use \{@link dropForeignKey\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '''
|
||||
#^Call to deprecated method setPrimaryKey\(\) of class Doctrine\\DBAL\\Schema\\Table\:
|
||||
Use \{@see addPrimaryKeyConstraint\(\)\} instead\.$#
|
||||
'''
|
||||
identifier: method.deprecated
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
@@ -3157,32 +3112,14 @@ parameters:
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnName\(\) on an unknown class Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
message: '#^Call to function method_exists\(\) with ''Doctrine\\\\DBAL\\\\Schema\\\\Index'' and ''getIndexedColumns'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to method getColumnNames\(\) on an unknown class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Call to method toString\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 3
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Class Doctrine\\DBAL\\Schema\\PrimaryKeyConstraint not found\.$#'
|
||||
identifier: class.notFound
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Method Doctrine\\DBAL\\Schema\\AbstractSchemaManager\<Doctrine\\DBAL\\Platforms\\AbstractPlatform\>\:\:createComparator\(\) invoked with 1 parameter, 0 required\.$#'
|
||||
identifier: arguments.count
|
||||
message: '#^Call to function method_exists\(\) with Doctrine\\DBAL\\Schema\\Table and ''getPrimaryKeyConstr…'' will always evaluate to true\.$#'
|
||||
identifier: function.alreadyNarrowedType
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
@@ -3228,6 +3165,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
|
||||
@@ -3289,15 +3232,45 @@ parameters:
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$indexedColumn of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Index\\IndexedColumn\.$#'
|
||||
identifier: class.notFound
|
||||
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addIndex\(\) expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \$name of anonymous function has invalid type Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName\.$#'
|
||||
identifier: class.notFound
|
||||
count: 3
|
||||
message: '#^Parameter \#1 \$columnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addUniqueIndex\(\) expects non\-empty\-list\<string\>, array\<string\> 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
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$localColumnNames of method Doctrine\\DBAL\\Schema\\Table\:\:addForeignKeyConstraint\(\) expects non\-empty\-list\<string\>, list\<string\> given\.$#'
|
||||
identifier: argument.type
|
||||
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
|
||||
count: 1
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
@@ -3366,6 +3339,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
|
||||
@@ -3381,7 +3360,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
|
||||
|
||||
-
|
||||
@@ -3450,12 +3429,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @phpstan\-assert\-if\-true for \$obj contains generic interface Doctrine\\ORM\\Proxy\\InternalProxy but does not specify its types\: T$#'
|
||||
identifier: missingType.generics
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$assoc of method Doctrine\\ORM\\PersistentCollection\<\(int\|string\),mixed\>\:\:setOwner\(\) expects Doctrine\\ORM\\Mapping\\AssociationMapping&Doctrine\\ORM\\Mapping\\ToManyAssociationMapping, Doctrine\\ORM\\Mapping\\ManyToManyInverseSideMapping\|Doctrine\\ORM\\Mapping\\ManyToManyOwningSideMapping\|Doctrine\\ORM\\Mapping\\ManyToOneAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping\|Doctrine\\ORM\\Mapping\\OneToOneInverseSideMapping\|Doctrine\\ORM\\Mapping\\OneToOneOwningSideMapping given\.$#'
|
||||
identifier: argument.type
|
||||
@@ -3498,6 +3471,12 @@ parameters:
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Unable to resolve the template type T in call to method static method Symfony\\Component\\VarExporter\\Hydrator\:\:hydrate\(\)$#'
|
||||
identifier: argument.templateType
|
||||
count: 1
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Doctrine\\Persistence\\Mapping\\ClassMetadata\:\:\$name\.$#'
|
||||
identifier: property.notFound
|
||||
@@ -3558,6 +3537,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
|
||||
@@ -3569,3 +3560,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,8 +34,81 @@ 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
|
||||
|
||||
-
|
||||
message: '~createComparator~'
|
||||
identifier: arguments.count
|
||||
|
||||
-
|
||||
message: '~UnqualifiedName~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~IndexedColumn~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~PrimaryKeyConstraint~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~IndexType~'
|
||||
identifier: class.notFound
|
||||
|
||||
-
|
||||
message: '~dropForeignKey~'
|
||||
identifier: method.notFound
|
||||
|
||||
-
|
||||
message: '~getIndexedColumns~'
|
||||
identifier: method.notFound
|
||||
|
||||
-
|
||||
message: '~getPrimaryKeyConstraint~'
|
||||
identifier: method.notFound
|
||||
|
||||
-
|
||||
message: '~PrimaryKeyConstraint~'
|
||||
identifier: class.notFound
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
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\.#'
|
||||
@@ -55,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~'
|
||||
|
||||
24
phpstan.neon
24
phpstan.neon
@@ -10,27 +10,19 @@ 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
|
||||
|
||||
# The return type is already narrow enough.
|
||||
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns ''[a-z_]+'' so it can be removed from the return type\.$~'
|
||||
- '~^Method Doctrine\\ORM\\Query\\ParameterTypeInferer\:\:inferType\(\) never returns Doctrine\\DBAL\\(?:Array)?ParameterType\:\:[A-Z_]+ so it can be removed from the return type\.$~'
|
||||
|
||||
# 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: '~^Call to static method unquoted\(\) on an unknown class Doctrine\\DBAL\\Schema\\Name\\Identifier\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Instantiated class Doctrine\\DBAL\\Schema\\Name\\UnqualifiedName not found\.$~'
|
||||
path: src/Tools/SchemaTool.php
|
||||
|
||||
-
|
||||
message: '~^Call to an undefined method Doctrine\\DBAL\\Schema\\Table::addPrimaryKeyConstraint\(\)\.$~'
|
||||
path: src/Tools/SchemaTool.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
|
||||
@@ -45,7 +37,7 @@ parameters:
|
||||
path: src/UnitOfWork.php
|
||||
|
||||
-
|
||||
message: '~^Parameter #1 \$command of method Symfony\\Component\\Console\\Application::add\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
|
||||
message: '~^Parameter #2 \$command of static method Doctrine\\ORM\\Tools\\Console\\ConsoleRunner::addCommandToApplication\(\) expects Symfony\\Component\\Console\\Command\\Command, Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand given\.$~'
|
||||
path: src/Tools/Console/ConsoleRunner.php
|
||||
|
||||
-
|
||||
|
||||
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);
|
||||
@@ -1070,7 +1065,7 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query and returns a the resulting Statement object.
|
||||
* Executes the query and returns the resulting Statement object.
|
||||
*
|
||||
* @return Result|int The executed database statement that holds
|
||||
* the results, or an integer indicating how
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\ORM\Exception\InvalidEntityRepository;
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
@@ -63,6 +64,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setProxyDir(string $dir): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->attributes['proxyDir'] = $dir;
|
||||
}
|
||||
|
||||
@@ -71,6 +81,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getProxyDir(): string|null
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->attributes['proxyDir'] ?? null;
|
||||
}
|
||||
|
||||
@@ -81,6 +100,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getAutoGenerateProxyClasses(): int
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->attributes['autoGenerateProxyClasses'] ?? ProxyFactory::AUTOGENERATE_ALWAYS;
|
||||
}
|
||||
|
||||
@@ -91,6 +119,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setAutoGenerateProxyClasses(bool|int $autoGenerate): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
|
||||
}
|
||||
|
||||
@@ -99,6 +136,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getProxyNamespace(): string|null
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
return $this->attributes['proxyNamespace'] ?? null;
|
||||
}
|
||||
|
||||
@@ -107,6 +153,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setProxyNamespace(string $ns): void
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Calling %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->attributes['proxyNamespace'] = $ns;
|
||||
}
|
||||
|
||||
@@ -602,7 +657,15 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
|
||||
public function enableNativeLazyObjects(bool $nativeLazyObjects): void
|
||||
{
|
||||
if (PHP_VERSION_ID < 80400) {
|
||||
if (PHP_VERSION_ID >= 80400 && ! $nativeLazyObjects) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Disabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
|
||||
);
|
||||
}
|
||||
|
||||
if (PHP_VERSION_ID < 80400 && $nativeLazyObjects) {
|
||||
throw new LogicException('Lazy loading proxies require PHP 8.4 or higher.');
|
||||
}
|
||||
|
||||
@@ -610,7 +673,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* To be deprecated in 3.1.0
|
||||
* @deprecated lazy ghost objects are always enabled
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
@@ -619,7 +682,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
return true;
|
||||
}
|
||||
|
||||
/** To be deprecated in 3.1.0 */
|
||||
/** @deprecated lazy ghost objects cannot be disabled */
|
||||
public function setLazyGhostObjectEnabled(bool $flag): void
|
||||
{
|
||||
if (! $flag) {
|
||||
@@ -630,7 +693,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
}
|
||||
|
||||
/** To be deprecated in 3.1.0 */
|
||||
/** @deprecated rejecting ID collisions in the identity map cannot be disabled */
|
||||
public function setRejectIdCollisionInIdentityMap(bool $flag): void
|
||||
{
|
||||
if (! $flag) {
|
||||
@@ -642,7 +705,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* To be deprecated in 3.1.0
|
||||
* @deprecated rejecting ID collisions in the identity map is always enabled
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ use function method_exists;
|
||||
*
|
||||
* $paths = ['/path/to/entity/mapping/files'];
|
||||
*
|
||||
* $config = ORMSetup::createAttributeMetadataConfiguration($paths);
|
||||
* $config = ORMSetup::createAttributeMetadataConfig($paths);
|
||||
* $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
|
||||
* $entityManager = new EntityManager($connection, $config);
|
||||
*
|
||||
@@ -134,12 +134,16 @@ class EntityManager implements EntityManagerInterface
|
||||
|
||||
$this->repositoryFactory = $config->getRepositoryFactory();
|
||||
$this->unitOfWork = new UnitOfWork($this);
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this,
|
||||
$config->getProxyDir(),
|
||||
$config->getProxyNamespace(),
|
||||
$config->getAutoGenerateProxyClasses(),
|
||||
);
|
||||
if ($config->isNativeLazyObjectsEnabled()) {
|
||||
$this->proxyFactory = new ProxyFactory($this);
|
||||
} else {
|
||||
$this->proxyFactory = new ProxyFactory(
|
||||
$this,
|
||||
$config->getProxyDir(),
|
||||
$config->getProxyNamespace(),
|
||||
$config->getAutoGenerateProxyClasses(),
|
||||
);
|
||||
}
|
||||
|
||||
if ($config->isSecondLevelCacheEnabled()) {
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
|
||||
@@ -17,15 +17,18 @@ use Doctrine\ORM\UnitOfWork;
|
||||
use Generator;
|
||||
use LogicException;
|
||||
use ReflectionClass;
|
||||
use ReflectionEnum;
|
||||
|
||||
use function array_key_exists;
|
||||
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 +129,10 @@ abstract class AbstractHydrator
|
||||
} else {
|
||||
yield from $result;
|
||||
}
|
||||
} else {
|
||||
} elseif (is_object(current($result))) {
|
||||
yield $result;
|
||||
} else {
|
||||
yield array_merge(...$result);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -593,13 +598,18 @@ abstract class AbstractHydrator
|
||||
*/
|
||||
final protected function buildEnum(mixed $value, string $enumType): BackedEnum|array
|
||||
{
|
||||
$reflection = new ReflectionEnum($enumType);
|
||||
$isIntBacked = $reflection->isBacked() && $reflection->getBackingType()->getName() === 'int';
|
||||
|
||||
if (is_array($value)) {
|
||||
return array_map(
|
||||
static fn ($value) => $enumType::from($value),
|
||||
static fn ($value) => $enumType::from($isIntBacked ? (int) $value : $value),
|
||||
$value,
|
||||
);
|
||||
}
|
||||
|
||||
$value = $isIntBacked ? (int) $value : $value;
|
||||
|
||||
return $enumType::from($value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Doctrine\ORM\Internal\Hydration;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use Doctrine\ORM\Exception\MultipleSelectorsFoundException;
|
||||
|
||||
use function array_column;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
@@ -27,8 +26,6 @@ final class ScalarColumnHydrator extends AbstractHydrator
|
||||
throw MultipleSelectorsFoundException::create($this->resultSetMapping()->fieldMappings);
|
||||
}
|
||||
|
||||
$result = $this->statement()->fetchAllNumeric();
|
||||
|
||||
return array_column($result, 0);
|
||||
return $this->statement()->fetchFirstColumn();
|
||||
}
|
||||
}
|
||||
|
||||
79
src/Internal/UnitOfWork/InsertBatch.php
Normal file
79
src/Internal/UnitOfWork/InsertBatch.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Internal\UnitOfWork;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Id\AssignedGenerator;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* An {@see InsertBatch} represents a set of entities that are safe to be batched
|
||||
* together in a single query.
|
||||
*
|
||||
* These entities are only those that have all fields already assigned, including the
|
||||
* identifier field(s).
|
||||
*
|
||||
* This data structure only exists for internal {@see UnitOfWork} optimisations, and
|
||||
* should not be relied upon outside the ORM.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @template TEntity of object
|
||||
*/
|
||||
final class InsertBatch
|
||||
{
|
||||
/**
|
||||
* @param ClassMetadata<TEntity> $class
|
||||
* @param non-empty-list<TEntity> $entities
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly ClassMetadata $class,
|
||||
public array $entities,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Code in here is procedural/ugly due to it being in a hot path of the {@see UnitOfWork}
|
||||
*
|
||||
* This method will batch the given entity set by type, preserving their order. For example,
|
||||
* given an input [A1, A2, A3, B1, B2, A4, A5], it will create an [[A1, A2, A3], [B1, B2], [A4, A5]] batch.
|
||||
*
|
||||
* Entities for which the identifier needs to be generated or fetched by a sequence are put as single
|
||||
* items in a batch of their own, since it is unsafe to batch-insert them.
|
||||
*
|
||||
* @param list<TEntities> $entities
|
||||
*
|
||||
* @return list<self<TEntities>>
|
||||
*
|
||||
* @template TEntities of object
|
||||
*/
|
||||
public static function batchByEntityType(
|
||||
EntityManagerInterface $entityManager,
|
||||
array $entities,
|
||||
): array {
|
||||
$currentClass = null;
|
||||
$batches = [];
|
||||
$batchIndex = -1;
|
||||
|
||||
foreach ($entities as $entity) {
|
||||
$entityClass = $entityManager->getClassMetadata($entity::class);
|
||||
|
||||
if (
|
||||
$currentClass?->name !== $entityClass->name
|
||||
|| ! $entityClass->idGenerator instanceof AssignedGenerator
|
||||
) {
|
||||
$currentClass = $entityClass;
|
||||
$batches[] = new InsertBatch($entityClass, [$entity]);
|
||||
$batchIndex += 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$batches[$batchIndex]->entities[] = $entity;
|
||||
}
|
||||
|
||||
return $batches;
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,10 @@ class AssociationBuilder
|
||||
string|null $onDelete = null,
|
||||
string|null $columnDef = null,
|
||||
): static {
|
||||
if ($this->mapping['id'] ?? false) {
|
||||
$nullable = null;
|
||||
}
|
||||
|
||||
$this->joinColumns[] = [
|
||||
'name' => $columnName,
|
||||
'referencedColumnName' => $referencedColumnName,
|
||||
@@ -133,6 +137,9 @@ class AssociationBuilder
|
||||
public function makePrimaryKey(): static
|
||||
{
|
||||
$this->mapping['id'] = true;
|
||||
foreach ($this->joinColumns ?? [] as $i => $joinColumn) {
|
||||
$this->joinColumns[$i]['nullable'] = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,18 @@ class FieldBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets indexed.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function index(bool $flag = true): static
|
||||
{
|
||||
$this->mapping['index'] = $flag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets column name.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,30 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Join Columns.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addJoinColumn(
|
||||
string $columnName,
|
||||
string $referencedColumnName,
|
||||
bool $nullable = true,
|
||||
bool $unique = false,
|
||||
string|null $onDelete = null,
|
||||
string|null $columnDef = null,
|
||||
): static {
|
||||
$this->joinColumns[] = [
|
||||
'name' => $columnName,
|
||||
'referencedColumnName' => $referencedColumnName,
|
||||
'unique' => $unique,
|
||||
'onDelete' => $onDelete,
|
||||
'columnDefinition' => $columnDef,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Inverse Join Columns.
|
||||
*
|
||||
@@ -40,7 +64,6 @@ class ManyToManyAssociationBuilder extends OneToManyAssociationBuilder
|
||||
$this->inverseJoinColumns[] = [
|
||||
'name' => $columnName,
|
||||
'referencedColumnName' => $referencedColumnName,
|
||||
'nullable' => $nullable,
|
||||
'unique' => $unique,
|
||||
'onDelete' => $onDelete,
|
||||
'columnDefinition' => $columnDef,
|
||||
|
||||
@@ -400,13 +400,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
public DiscriminatorColumnMapping|null $discriminatorColumn = null;
|
||||
|
||||
/**
|
||||
* READ-ONLY: The primary table definition. The definition is an array with the
|
||||
* following entries:
|
||||
* READ-ONLY: The primary table definition.
|
||||
*
|
||||
* name => <tableName>
|
||||
* schema => <schemaName>
|
||||
* indexes => array
|
||||
* uniqueConstraints => array
|
||||
* "quoted" indicates whether the table name is quoted (with backticks) or not
|
||||
*
|
||||
* @var mixed[]
|
||||
* @phpstan-var array{
|
||||
@@ -544,11 +540,13 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
/**
|
||||
* The ReflectionProperty instances of the mapped class.
|
||||
*
|
||||
* @deprecated Use $propertyAccessors instead.
|
||||
*
|
||||
* @var LegacyReflectionFields|array<string, ReflectionProperty>
|
||||
*/
|
||||
public LegacyReflectionFields|array $reflFields = [];
|
||||
|
||||
/** @var array<string, PropertyAccessors\PropertyAccessor> */
|
||||
/** @var array<string, PropertyAccessor> */
|
||||
public array $propertyAccessors = [];
|
||||
|
||||
private InstantiatorInterface|null $instantiator = null;
|
||||
@@ -573,6 +571,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
/**
|
||||
* Gets the ReflectionProperties of the mapped class.
|
||||
*
|
||||
* @deprecated Use getPropertyAccessors() instead.
|
||||
*
|
||||
* @return LegacyReflectionFields|ReflectionProperty[] An array of ReflectionProperty instances.
|
||||
* @phpstan-return LegacyReflectionFields|array<string, ReflectionProperty>
|
||||
*/
|
||||
@@ -584,7 +584,7 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
/**
|
||||
* Gets the ReflectionProperties of the mapped class.
|
||||
*
|
||||
* @return PropertyAccessor[] An array of PropertyAccessor instances.
|
||||
* @return array<string, PropertyAccessor> An array of PropertyAccessor instances by name.
|
||||
*/
|
||||
public function getPropertyAccessors(): array
|
||||
{
|
||||
@@ -593,6 +593,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
|
||||
/**
|
||||
* Gets a ReflectionProperty for a specific field of the mapped class.
|
||||
*
|
||||
* @deprecated Use getPropertyAccessor() instead.
|
||||
*/
|
||||
public function getReflectionProperty(string $name): ReflectionProperty|null
|
||||
{
|
||||
@@ -604,7 +606,11 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
return $this->propertyAccessors[$name] ?? null;
|
||||
}
|
||||
|
||||
/** @throws BadMethodCallException If the class has a composite identifier. */
|
||||
/**
|
||||
* @deprecated Use getPropertyAccessor() instead.
|
||||
*
|
||||
* @throws BadMethodCallException If the class has a composite identifier.
|
||||
*/
|
||||
public function getSingleIdReflectionProperty(): ReflectionProperty|null
|
||||
{
|
||||
if ($this->isIdentifierComposite) {
|
||||
@@ -818,7 +824,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
public function wakeupReflection(ReflectionService $reflService): void
|
||||
{
|
||||
// Restore ReflectionClass and properties
|
||||
$this->reflClass = $reflService->getClass($this->name);
|
||||
$this->reflClass = $reflService->getClass($this->name);
|
||||
/** @phpstan-ignore property.deprecated */
|
||||
$this->reflFields = new LegacyReflectionFields($this, $reflService);
|
||||
$this->instantiator = $this->instantiator ?: new Instantiator();
|
||||
|
||||
@@ -1056,6 +1063,13 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
return $mapping !== false && isset($mapping->nullable) && $mapping->nullable;
|
||||
}
|
||||
|
||||
public function isIndexed(string $fieldName): bool
|
||||
{
|
||||
$mapping = $this->getFieldMapping($fieldName);
|
||||
|
||||
return isset($mapping->index) && $mapping->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a column name for a field name.
|
||||
* If the column name for the field cannot be found, the given field name
|
||||
@@ -2190,6 +2204,20 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
|
||||
}
|
||||
|
||||
if (isset($columnDef['enumType'])) {
|
||||
if (! enum_exists($columnDef['enumType'])) {
|
||||
throw MappingException::nonEnumTypeMapped($this->name, $columnDef['fieldName'], $columnDef['enumType']);
|
||||
}
|
||||
|
||||
if (
|
||||
defined('Doctrine\DBAL\Types\Types::ENUM')
|
||||
&& $columnDef['type'] === Types::ENUM
|
||||
&& ! isset($columnDef['options']['values'])
|
||||
) {
|
||||
$columnDef['options']['values'] = array_column($columnDef['enumType']::cases(), 'value');
|
||||
}
|
||||
}
|
||||
|
||||
$this->discriminatorColumn = DiscriminatorColumnMapping::fromMappingArray($columnDef);
|
||||
}
|
||||
}
|
||||
@@ -2208,6 +2236,8 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
* Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
|
||||
*
|
||||
* @param array<int|string, string> $map
|
||||
*
|
||||
* @throws MappingException
|
||||
*/
|
||||
public function setDiscriminatorMap(array $map): void
|
||||
{
|
||||
@@ -2227,6 +2257,16 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
);
|
||||
}
|
||||
|
||||
$values = $this->discriminatorColumn->options['values'] ?? null;
|
||||
|
||||
if ($values !== null) {
|
||||
$diff = array_diff(array_keys($map), $values);
|
||||
|
||||
if ($diff !== []) {
|
||||
throw MappingException::invalidEntriesInDiscriminatorMap(array_values($diff), $this->name, $this->discriminatorColumn->enumType);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($map as $value => $className) {
|
||||
$this->addDiscriminatorMapClass($value, $className);
|
||||
}
|
||||
@@ -2440,9 +2480,9 @@ class ClassMetadata implements PersistenceClassMetadata, Stringable
|
||||
|
||||
if (! isset($mapping['default'])) {
|
||||
if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'], true)) {
|
||||
$mapping['default'] = 1;
|
||||
$mapping['options']['default'] = 1;
|
||||
} elseif ($mapping['type'] === 'datetime') {
|
||||
$mapping['default'] = 'CURRENT_TIMESTAMP';
|
||||
$mapping['options']['default'] = 'CURRENT_TIMESTAMP';
|
||||
} else {
|
||||
throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,9 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
|
||||
|
||||
public function setEntityManager(EntityManagerInterface $em): void
|
||||
{
|
||||
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
|
||||
if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
parent::setProxyClassNameResolver(new DefaultProxyClassNameResolver());
|
||||
}
|
||||
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ final class Column implements MappingAttribute
|
||||
public readonly array $options = [],
|
||||
public readonly string|null $columnDefinition = null,
|
||||
public readonly string|null $generated = null,
|
||||
public readonly bool $index = false,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,11 @@ use Doctrine\ORM\Internal\SQLResultCasing;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function is_numeric;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
@@ -38,7 +41,13 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
$tableName = $class->table['name'];
|
||||
|
||||
if (! empty($class->table['schema'])) {
|
||||
$tableName = $class->table['schema'] . '.' . $class->table['name'];
|
||||
return isset($class->table['quoted'])
|
||||
? sprintf(
|
||||
'%s.%s',
|
||||
$platform->quoteSingleIdentifier($class->table['schema']),
|
||||
$platform->quoteSingleIdentifier($tableName),
|
||||
)
|
||||
: $class->table['schema'] . '.' . $class->table['name'];
|
||||
}
|
||||
|
||||
return isset($class->table['quoted'])
|
||||
@@ -52,7 +61,10 @@ class DefaultQuoteStrategy implements QuoteStrategy
|
||||
public function getSequenceName(array $definition, ClassMetadata $class, AbstractPlatform $platform): string
|
||||
{
|
||||
return isset($definition['quoted'])
|
||||
? $platform->quoteSingleIdentifier($definition['sequenceName'])
|
||||
? implode('.', array_map(
|
||||
static fn (string $part) => $platform->quoteSingleIdentifier($part),
|
||||
explode('.', $definition['sequenceName']),
|
||||
))
|
||||
: $definition['sequenceName'];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use BackedEnum;
|
||||
use BcMath\Number;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
@@ -40,7 +41,12 @@ final class DefaultTypedFieldMapper implements TypedFieldMapper
|
||||
/** @param array<class-string|ScalarName, class-string<Type>|string> $typedFieldMappings */
|
||||
public function __construct(array $typedFieldMappings = [])
|
||||
{
|
||||
$this->typedFieldMappings = array_merge(self::DEFAULT_TYPED_FIELD_MAPPINGS, $typedFieldMappings);
|
||||
$defaultMappings = self::DEFAULT_TYPED_FIELD_MAPPINGS;
|
||||
if (defined(Types::class . '::NUMBER')) { // DBAL 4.3+
|
||||
$defaultMappings[Number::class] = Types::NUMBER;
|
||||
}
|
||||
|
||||
$this->typedFieldMappings = array_merge($defaultMappings, $typedFieldMappings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
|
||||
use Doctrine\Persistence\Mapping\Driver\ColocatedMappingDriver;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use InvalidArgumentException;
|
||||
@@ -35,10 +36,10 @@ class AttributeDriver implements MappingDriver
|
||||
private readonly AttributeReader $reader;
|
||||
|
||||
/**
|
||||
* @param array<string> $paths
|
||||
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
|
||||
* @param string[]|ClassLocator $paths a ClassLocator, or an array of directories.
|
||||
* @param true $reportFieldsWhereDeclared no-op, to be removed in 4.0
|
||||
*/
|
||||
public function __construct(array $paths, bool $reportFieldsWhereDeclared = true)
|
||||
public function __construct(array|ClassLocator $paths, bool $reportFieldsWhereDeclared = true)
|
||||
{
|
||||
if (! $reportFieldsWhereDeclared) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
@@ -48,7 +49,12 @@ class AttributeDriver implements MappingDriver
|
||||
}
|
||||
|
||||
$this->reader = new AttributeReader();
|
||||
$this->addPaths($paths);
|
||||
|
||||
if ($paths instanceof ClassLocator) {
|
||||
$this->classLocator = $paths;
|
||||
} else {
|
||||
$this->addPaths($paths);
|
||||
}
|
||||
}
|
||||
|
||||
public function isTransient(string $className): bool
|
||||
@@ -710,6 +716,7 @@ class AttributeDriver implements MappingDriver
|
||||
* length: int,
|
||||
* unique: bool,
|
||||
* nullable: bool,
|
||||
* index: bool,
|
||||
* precision: int,
|
||||
* enumType?: class-string,
|
||||
* options?: mixed[],
|
||||
@@ -726,6 +733,7 @@ class AttributeDriver implements MappingDriver
|
||||
'length' => $column->length,
|
||||
'unique' => $column->unique,
|
||||
'nullable' => $column->nullable,
|
||||
'index' => $column->index,
|
||||
'precision' => $column->precision,
|
||||
];
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\Order;
|
||||
use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\MappingException;
|
||||
@@ -18,10 +16,10 @@ use LogicException;
|
||||
use SimpleXMLElement;
|
||||
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function constant;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function enum_exists;
|
||||
use function explode;
|
||||
use function extension_loaded;
|
||||
use function file_get_contents;
|
||||
@@ -137,7 +135,6 @@ class XmlDriver extends FileDriver
|
||||
];
|
||||
|
||||
if (isset($discrColumn['options'])) {
|
||||
assert($discrColumn['options'] instanceof SimpleXMLElement);
|
||||
$columnDef['options'] = $this->parseOptions($discrColumn['options']->children());
|
||||
}
|
||||
|
||||
@@ -410,10 +407,7 @@ class XmlDriver extends FileDriver
|
||||
if (isset($oneToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($oneToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
// @phpstan-ignore classConstant.deprecated
|
||||
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
|
||||
$orderBy[(string) $orderByField['name']] = (string) ($orderByField['direction'] ?? 'ASC');
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
@@ -539,10 +533,7 @@ class XmlDriver extends FileDriver
|
||||
if (isset($manyToManyElement->{'order-by'})) {
|
||||
$orderBy = [];
|
||||
foreach ($manyToManyElement->{'order-by'}->{'order-by-field'} ?? [] as $orderByField) {
|
||||
$orderBy[(string) $orderByField['name']] = isset($orderByField['direction'])
|
||||
? (string) $orderByField['direction']
|
||||
// @phpstan-ignore classConstant.deprecated
|
||||
: (enum_exists(Order::class) ? Order::Ascending->value : Criteria::ASC);
|
||||
$orderBy[(string) $orderByField['name']] = (string) ($orderByField['direction'] ?? 'ASC');
|
||||
}
|
||||
|
||||
$mapping['orderBy'] = $orderBy;
|
||||
@@ -666,15 +657,30 @@ class XmlDriver extends FileDriver
|
||||
* Parses (nested) option elements.
|
||||
*
|
||||
* @return mixed[] The options array.
|
||||
* @phpstan-return array<int|string, array<int|string, mixed|string>|bool|string>
|
||||
* @phpstan-return array<int|string, array<int|string, mixed|string>|bool|string|object>
|
||||
*/
|
||||
private function parseOptions(SimpleXMLElement|null $options): array
|
||||
{
|
||||
$array = [];
|
||||
|
||||
foreach ($options ?? [] as $option) {
|
||||
$value = null;
|
||||
if ($option->count()) {
|
||||
$value = $this->parseOptions($option->children());
|
||||
// Check if this option contains an <object> element
|
||||
$children = $option->children();
|
||||
$hasObjectElement = false;
|
||||
|
||||
foreach ($children as $child) {
|
||||
if ($child->getName() === 'object') {
|
||||
$value = $this->parseObjectElement($child);
|
||||
$hasObjectElement = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $hasObjectElement) {
|
||||
$value = $this->parseOptions($children);
|
||||
}
|
||||
} else {
|
||||
$value = (string) $option;
|
||||
}
|
||||
@@ -694,6 +700,33 @@ class XmlDriver extends FileDriver
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an <object> element and returns the instantiated object.
|
||||
*
|
||||
* @param SimpleXMLElement $objectElement The XML element.
|
||||
*
|
||||
* @return object The instantiated object.
|
||||
*
|
||||
* @throws MappingException If the object specification is invalid.
|
||||
* @throws InvalidArgumentException If the class does not exist.
|
||||
*/
|
||||
private function parseObjectElement(SimpleXMLElement $objectElement): object
|
||||
{
|
||||
$attributes = $objectElement->attributes();
|
||||
|
||||
if (! isset($attributes->class)) {
|
||||
throw MappingException::missingRequiredOption('object', 'class');
|
||||
}
|
||||
|
||||
$className = (string) $attributes->class;
|
||||
|
||||
if (! class_exists($className)) {
|
||||
throw new InvalidArgumentException(sprintf('Class "%s" does not exist', $className));
|
||||
}
|
||||
|
||||
return new $className();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a joinColumn mapping array based on the information
|
||||
* found in the given SimpleXMLElement.
|
||||
@@ -754,6 +787,7 @@ class XmlDriver extends FileDriver
|
||||
* scale?: int,
|
||||
* unique?: bool,
|
||||
* nullable?: bool,
|
||||
* index?: bool,
|
||||
* notInsertable?: bool,
|
||||
* notUpdatable?: bool,
|
||||
* enumType?: string,
|
||||
@@ -792,6 +826,10 @@ class XmlDriver extends FileDriver
|
||||
$mapping['unique'] = $this->evaluateBoolean($fieldMapping['unique']);
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['index'])) {
|
||||
$mapping['index'] = $this->evaluateBoolean($fieldMapping['index']);
|
||||
}
|
||||
|
||||
if (isset($fieldMapping['nullable'])) {
|
||||
$mapping['nullable'] = $this->evaluateBoolean($fieldMapping['nullable']);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ final class FieldMapping implements ArrayAccess
|
||||
public int|null $scale = null;
|
||||
/** Whether a unique constraint should be generated for the column. */
|
||||
public bool|null $unique = null;
|
||||
/** Whether an index should be generated for the column. */
|
||||
public bool|null $index = null;
|
||||
/**
|
||||
* @var class-string|null This is set when the field is inherited by this
|
||||
* class from another (inheritance) parent <em>entity</em> class. The value
|
||||
@@ -69,7 +71,9 @@ final class FieldMapping implements ArrayAccess
|
||||
public string|null $declaredField = null;
|
||||
public array|null $options = null;
|
||||
public bool|null $version = null;
|
||||
public string|int|null $default = null;
|
||||
|
||||
/** @deprecated Use options with 'default' key instead */
|
||||
public string|int|null $default = null;
|
||||
|
||||
/**
|
||||
* @param string $type The type name of the mapped field. Can be one of
|
||||
@@ -93,6 +97,7 @@ final class FieldMapping implements ArrayAccess
|
||||
* length?: int|null,
|
||||
* id?: bool|null,
|
||||
* nullable?: bool|null,
|
||||
* index?: bool|null,
|
||||
* notInsertable?: bool|null,
|
||||
* notUpdatable?: bool|null,
|
||||
* columnDefinition?: string|null,
|
||||
@@ -137,7 +142,7 @@ final class FieldMapping implements ArrayAccess
|
||||
{
|
||||
$serialized = ['type', 'fieldName', 'columnName'];
|
||||
|
||||
foreach (['nullable', 'notInsertable', 'notUpdatable', 'id', 'unique', 'version', 'quoted'] as $boolKey) {
|
||||
foreach (['nullable', 'notInsertable', 'notUpdatable', 'id', 'unique', 'version', 'quoted', 'index'] as $boolKey) {
|
||||
if ($this->$boolKey) {
|
||||
$serialized[] = $boolKey;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -84,9 +84,14 @@ final class JoinTableMapping implements ArrayAccess
|
||||
/** @return mixed[] */
|
||||
public function toArray(): array
|
||||
{
|
||||
$array = (array) $this;
|
||||
$array = (array) $this;
|
||||
$toArray = static function (JoinColumnMapping $column) {
|
||||
$array = (array) $column;
|
||||
|
||||
$toArray = static fn (JoinColumnMapping $column): array => (array) $column;
|
||||
unset($array['nullable']);
|
||||
|
||||
return $array;
|
||||
};
|
||||
$array['joinColumns'] = array_map($toArray, $array['joinColumns']);
|
||||
$array['inverseJoinColumns'] = array_map($toArray, $array['inverseJoinColumns']);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
|
||||
@@ -127,6 +129,22 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
$mapping->joinTableColumns = [];
|
||||
|
||||
foreach ($mapping->joinTable->joinColumns as $joinColumn) {
|
||||
if ($joinColumn->nullable !== null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12126',
|
||||
<<<'DEPRECATION'
|
||||
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
|
||||
The ORM will always set it to false.
|
||||
Doing so is deprecated and will be an error in 4.0.
|
||||
DEPRECATION,
|
||||
$mapping->sourceEntity,
|
||||
$mapping->fieldName,
|
||||
);
|
||||
}
|
||||
|
||||
$joinColumn->nullable = false;
|
||||
|
||||
if (empty($joinColumn->referencedColumnName)) {
|
||||
$joinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
|
||||
}
|
||||
@@ -150,6 +168,22 @@ final class ManyToManyOwningSideMapping extends ToManyOwningSideMapping implemen
|
||||
}
|
||||
|
||||
foreach ($mapping->joinTable->inverseJoinColumns as $inverseJoinColumn) {
|
||||
if ($inverseJoinColumn->nullable !== null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12126',
|
||||
<<<'DEPRECATION'
|
||||
Specifying the "nullable" attribute for join columns in many-to-many associations (here, %s::$%s) is a no-op.
|
||||
The ORM will always set it to false.
|
||||
Doing so is deprecated and will be an error in 4.0.
|
||||
DEPRECATION,
|
||||
$mapping->targetEntity,
|
||||
$mapping->fieldName,
|
||||
);
|
||||
}
|
||||
|
||||
$inverseJoinColumn->nullable = false;
|
||||
|
||||
if (empty($inverseJoinColumn->referencedColumnName)) {
|
||||
$inverseJoinColumn->referencedColumnName = $namingStrategy->referenceColumnName();
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
|
||||
|
||||
public static function joinTableRequired(string $fieldName): self
|
||||
{
|
||||
return new self(sprintf("The mapping of field '%s' requires an the 'joinTable' attribute.", $fieldName));
|
||||
return new self(sprintf("The mapping of field '%s' requires the 'joinTable' attribute.", $fieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,6 +329,24 @@ class MappingException extends PersistenceMappingException implements ORMExcepti
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an exception that indicates that discriminator entries used in a discriminator map
|
||||
* does not exist in the backed enum provided by enumType option.
|
||||
*
|
||||
* @param array<int,int|string> $entries The discriminator entries that could not be found.
|
||||
* @param string $owningClass The class that declares the discriminator map.
|
||||
* @param string $enumType The enum that entries were checked against.
|
||||
*/
|
||||
public static function invalidEntriesInDiscriminatorMap(array $entries, string $owningClass, string $enumType): self
|
||||
{
|
||||
return new self(sprintf(
|
||||
"The entries %s in the discriminator map of class '%s' do not correspond to enum cases of '%s'.",
|
||||
implode(', ', array_map(static fn ($entry): string => sprintf("'%s'", $entry), $entries)),
|
||||
$owningClass,
|
||||
$enumType,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an exception that indicates that a class used in a discriminator map does not exist.
|
||||
* An example would be an outdated (maybe renamed) classname.
|
||||
|
||||
@@ -10,6 +10,8 @@ use ReflectionProperty;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/** @internal */
|
||||
class ReadonlyAccessor implements PropertyAccessor
|
||||
{
|
||||
@@ -26,7 +28,12 @@ class ReadonlyAccessor implements PropertyAccessor
|
||||
|
||||
public function setValue(object $object, mixed $value): void
|
||||
{
|
||||
if (! $this->reflectionProperty->isInitialized($object)) {
|
||||
/* For lazy properties, skip the isInitialized() check
|
||||
because it would trigger the initialization of the whole object. */
|
||||
if (
|
||||
PHP_VERSION_ID >= 80400 && $this->reflectionProperty->isLazy($object)
|
||||
|| ! $this->reflectionProperty->isInitialized($object)
|
||||
) {
|
||||
$this->parent->setValue($object, $value);
|
||||
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use RuntimeException;
|
||||
|
||||
use function array_flip;
|
||||
@@ -130,6 +131,26 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
|
||||
$uniqueConstraintColumns = [];
|
||||
|
||||
foreach ($mapping->joinColumns as $joinColumn) {
|
||||
if ($mapping->id) {
|
||||
if ($joinColumn->nullable !== null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12126',
|
||||
<<<'DEPRECATION'
|
||||
Specifying the "nullable" attribute for join columns in to-one associations (here, %s::$%s) that are part of the identifier is a no-op.
|
||||
The ORM will always set it to false.
|
||||
Doing so is deprecated and will be an error in 4.0.
|
||||
DEPRECATION,
|
||||
$mapping->sourceEntity,
|
||||
$mapping->fieldName,
|
||||
);
|
||||
}
|
||||
|
||||
$joinColumn->nullable = false;
|
||||
} elseif ($joinColumn->nullable === null) {
|
||||
$joinColumn->nullable = true;
|
||||
}
|
||||
|
||||
if ($mapping->isOneToOne() && ! $isInheritanceTypeSingleTable) {
|
||||
if (count($mapping->joinColumns) === 1) {
|
||||
if (empty($mapping->id)) {
|
||||
@@ -194,7 +215,12 @@ abstract class ToOneOwningSideMapping extends OwningSideMapping implements ToOne
|
||||
|
||||
$joinColumns = [];
|
||||
foreach ($array['joinColumns'] as $column) {
|
||||
$joinColumns[] = (array) $column;
|
||||
$columnArray = (array) $column;
|
||||
if ($this->id) {
|
||||
unset($columnArray['nullable']);
|
||||
}
|
||||
|
||||
$joinColumns[] = $columnArray;
|
||||
}
|
||||
|
||||
$array['joinColumns'] = $joinColumns;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -4,8 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
use Doctrine\ORM\Mapping\Driver\XmlDriver;
|
||||
use Doctrine\Persistence\Mapping\Driver\ClassLocator;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Redis;
|
||||
use RuntimeException;
|
||||
@@ -20,25 +22,54 @@ use function extension_loaded;
|
||||
use function md5;
|
||||
use function sys_get_temp_dir;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
final class ORMSetup
|
||||
{
|
||||
/**
|
||||
* Creates a configuration with an attribute metadata driver.
|
||||
*
|
||||
* @param string[] $paths
|
||||
* @param string[]|ClassLocator $paths
|
||||
*/
|
||||
public static function createAttributeMetadataConfiguration(
|
||||
array $paths,
|
||||
array|ClassLocator $paths,
|
||||
bool $isDevMode = false,
|
||||
string|null $proxyDir = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'%s is deprecated in favor of %s, and will be removed in 4.0.',
|
||||
__METHOD__,
|
||||
self::class . '::createAttributeMetadataConfig()',
|
||||
);
|
||||
}
|
||||
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new AttributeDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an attribute metadata driver.
|
||||
*
|
||||
* @param string[]|ClassLocator $paths
|
||||
*/
|
||||
public static function createAttributeMetadataConfig(
|
||||
array|ClassLocator $paths,
|
||||
bool $isDevMode = false,
|
||||
string|null $cacheNamespaceSeed = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
$config = self::createConfig($isDevMode, $cacheNamespaceSeed, $cache);
|
||||
$config->setMetadataDriverImpl(new AttributeDriver($paths));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an XML metadata driver.
|
||||
*
|
||||
@@ -51,12 +82,44 @@ final class ORMSetup
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
bool $isXsdValidationEnabled = true,
|
||||
): Configuration {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'%s is deprecated in favor of %s, and will be removed in 4.0.',
|
||||
__METHOD__,
|
||||
self::class . '::createXMLMetadataConfig()',
|
||||
);
|
||||
}
|
||||
|
||||
$config = self::createConfiguration($isDevMode, $proxyDir, $cache);
|
||||
$config->setMetadataDriverImpl(new XmlDriver($paths, XmlDriver::DEFAULT_FILE_EXTENSION, $isXsdValidationEnabled));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration with an XML metadata driver.
|
||||
*
|
||||
* @param string[] $paths
|
||||
*/
|
||||
public static function createXMLMetadataConfig(
|
||||
array $paths,
|
||||
bool $isDevMode = false,
|
||||
string|null $cacheNamespaceSeed = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
bool $isXsdValidationEnabled = true,
|
||||
): Configuration {
|
||||
$config = self::createConfig($isDevMode, $cacheNamespaceSeed, $cache);
|
||||
$config->setMetadataDriverImpl(new XmlDriver(
|
||||
$paths,
|
||||
XmlDriver::DEFAULT_FILE_EXTENSION,
|
||||
$isXsdValidationEnabled,
|
||||
));
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration without a metadata driver.
|
||||
*/
|
||||
@@ -65,6 +128,16 @@ final class ORMSetup
|
||||
string|null $proxyDir = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
if (PHP_VERSION_ID >= 80400 && $proxyDir !== null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'%s is deprecated in favor of %s, and will be removed in 4.0.',
|
||||
__METHOD__,
|
||||
self::class . '::createConfig()',
|
||||
);
|
||||
}
|
||||
|
||||
$proxyDir = $proxyDir ?: sys_get_temp_dir();
|
||||
|
||||
$cache = self::createCacheInstance($isDevMode, $proxyDir, $cache);
|
||||
@@ -81,9 +154,23 @@ final class ORMSetup
|
||||
return $config;
|
||||
}
|
||||
|
||||
public static function createConfig(
|
||||
bool $isDevMode = false,
|
||||
string|null $cacheNamespaceSeed = null,
|
||||
CacheItemPoolInterface|null $cache = null,
|
||||
): Configuration {
|
||||
$cache = self::createCacheInstance($isDevMode, $cacheNamespaceSeed, $cache);
|
||||
$config = new Configuration();
|
||||
$config->setMetadataCache($cache);
|
||||
$config->setQueryCache($cache);
|
||||
$config->setResultCache($cache);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private static function createCacheInstance(
|
||||
bool $isDevMode,
|
||||
string $proxyDir,
|
||||
string|null $cacheNamespaceSeed,
|
||||
CacheItemPoolInterface|null $cache,
|
||||
): CacheItemPoolInterface {
|
||||
if ($cache !== null) {
|
||||
@@ -101,7 +188,7 @@ final class ORMSetup
|
||||
return new ArrayAdapter();
|
||||
}
|
||||
|
||||
$namespace = 'dc2_' . md5($proxyDir);
|
||||
$namespace = 'dc2_' . md5($cacheNamespaceSeed ?? 'default');
|
||||
|
||||
if (extension_loaded('apcu') && apcu_enabled()) {
|
||||
return new ApcuAdapter($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;
|
||||
@@ -693,11 +685,11 @@ class BasicEntityPersister implements EntityPersister
|
||||
$targetColumn = $joinColumn->referencedColumnName;
|
||||
$quotedColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $this->class, $this->platform);
|
||||
|
||||
$this->quotedColumns[$sourceColumn] = $quotedColumn;
|
||||
$this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
|
||||
$result[$owningTable][$sourceColumn] = $newValId
|
||||
? $newValId[$targetClass->getFieldForColumn($targetColumn)]
|
||||
: null;
|
||||
$this->quotedColumns[$sourceColumn] = $quotedColumn;
|
||||
$this->columnTypes[$sourceColumn] = PersisterHelper::getTypeOfColumn($targetColumn, $targetClass, $this->em);
|
||||
|
||||
$newValue = $newValId ? $newValId[$targetClass->getFieldForColumn($targetColumn)] : null;
|
||||
$result[$owningTable][$sourceColumn] = $newValue instanceof BackedEnum ? $newValue->value : $newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,129 +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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function file_exists;
|
||||
use function ltrim;
|
||||
@@ -15,6 +16,7 @@ use function strlen;
|
||||
use function substr;
|
||||
|
||||
use const DIRECTORY_SEPARATOR;
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/**
|
||||
* Special Autoloader for Proxy classes, which are not PSR-0 compliant.
|
||||
@@ -34,6 +36,15 @@ final class Autoloader
|
||||
*/
|
||||
public static function resolveFile(string $proxyDir, string $proxyNamespace, string $className): string
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
if (! str_starts_with($className, $proxyNamespace)) {
|
||||
throw new NotAProxyClass($className, $proxyNamespace);
|
||||
}
|
||||
@@ -59,6 +70,15 @@ final class Autoloader
|
||||
string $proxyNamespace,
|
||||
Closure|null $notFoundCallback = null,
|
||||
): Closure {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
$proxyNamespace = ltrim($proxyNamespace, '\\');
|
||||
|
||||
$autoloader = /** @param class-string $className */ static function (string $className) use ($proxyDir, $proxyNamespace, $notFoundCallback): void {
|
||||
|
||||
@@ -4,12 +4,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\Persistence\Mapping\ProxyClassNameResolver;
|
||||
use Doctrine\Persistence\Proxy;
|
||||
|
||||
use function strrpos;
|
||||
use function substr;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/**
|
||||
* Class-related functionality for objects that might or not be proxy objects
|
||||
* at the moment.
|
||||
@@ -18,6 +21,15 @@ final class DefaultProxyClassNameResolver implements ProxyClassNameResolver
|
||||
{
|
||||
public function resolveClassName(string $className): string
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
$pos = strrpos($className, '\\' . Proxy::MARKER . '\\');
|
||||
|
||||
if ($pos === false) {
|
||||
@@ -30,6 +42,15 @@ final class DefaultProxyClassNameResolver implements ProxyClassNameResolver
|
||||
/** @return class-string */
|
||||
public static function getClass(object $object): string
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Class "%s" is deprecated. Use native lazy objects instead.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
return (new self())->resolveClassName($object::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Doctrine\ORM\Proxy;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use Doctrine\ORM\ORMInvalidArgumentException;
|
||||
@@ -30,11 +31,13 @@ use function dirname;
|
||||
use function file_exists;
|
||||
use function file_put_contents;
|
||||
use function filemtime;
|
||||
use function func_num_args;
|
||||
use function is_bool;
|
||||
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;
|
||||
@@ -132,6 +135,9 @@ EOPHP;
|
||||
/** @var array<class-string, Closure> */
|
||||
private array $proxyFactories = [];
|
||||
|
||||
private readonly string $proxyDir;
|
||||
private readonly string $proxyNs;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the <tt>ProxyFactory</tt> class that is
|
||||
* connected to the given <tt>EntityManager</tt>.
|
||||
@@ -143,22 +149,55 @@ EOPHP;
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly string $proxyDir,
|
||||
private readonly string $proxyNs,
|
||||
string|null $proxyDir = null,
|
||||
string|null $proxyNs = null,
|
||||
bool|int $autoGenerate = self::AUTOGENERATE_NEVER,
|
||||
) {
|
||||
if (! $proxyDir && ! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
throw ORMInvalidArgumentException::proxyDirectoryRequired();
|
||||
}
|
||||
if (! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Not enabling native lazy objects is deprecated and will be impossible in Doctrine ORM 4.0.',
|
||||
);
|
||||
}
|
||||
|
||||
if (! $proxyNs && ! $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
throw ORMInvalidArgumentException::proxyNamespaceRequired();
|
||||
// @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();
|
||||
}
|
||||
|
||||
if (! $proxyNs) {
|
||||
throw ORMInvalidArgumentException::proxyNamespaceRequired();
|
||||
}
|
||||
} elseif (PHP_VERSION_ID >= 80400 && func_num_args() > 1) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Passing more than just the EntityManager to the %s is deprecated and will not be possible in Doctrine ORM 4.0.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
if (is_int($autoGenerate) ? $autoGenerate < 0 || $autoGenerate > 4 : ! is_bool($autoGenerate)) {
|
||||
throw ORMInvalidArgumentException::invalidAutoGenerateMode($autoGenerate);
|
||||
}
|
||||
|
||||
if ($proxyDir === null && $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
$proxyDir = '';
|
||||
}
|
||||
|
||||
if ($proxyNs === null && $em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
$proxyNs = '';
|
||||
}
|
||||
|
||||
$this->proxyDir = $proxyDir;
|
||||
$this->proxyNs = $proxyNs;
|
||||
|
||||
$this->uow = $em->getUnitOfWork();
|
||||
$this->autoGenerate = (int) $autoGenerate;
|
||||
$this->identifierFlattener = new IdentifierFlattener($this->uow, $em->getMetadataFactory());
|
||||
@@ -171,11 +210,23 @@ EOPHP;
|
||||
public function getProxy(string $className, array $identifier): object
|
||||
{
|
||||
if ($this->em->getConfiguration()->isNativeLazyObjectsEnabled()) {
|
||||
$classMetadata = $this->em->getClassMetadata($className);
|
||||
$entityPersister = $this->uow->getEntityPersister($className);
|
||||
$classMetadata = $this->em->getClassMetadata($className);
|
||||
$entityPersister = $this->uow->getEntityPersister($className);
|
||||
$identifierFlattener = $this->identifierFlattener;
|
||||
|
||||
$proxy = $classMetadata->reflClass->newLazyGhost(static function (object $object) use ($identifier, $entityPersister): void {
|
||||
$entityPersister->loadById($identifier, $object);
|
||||
$proxy = $classMetadata->reflClass->newLazyGhost(static function (object $object) use (
|
||||
$identifier,
|
||||
$entityPersister,
|
||||
$identifierFlattener,
|
||||
$classMetadata,
|
||||
): void {
|
||||
$original = $entityPersister->loadById($identifier, $object);
|
||||
if ($original === null) {
|
||||
throw EntityNotFoundException::fromClassNameAndIdentifier(
|
||||
$classMetadata->getName(),
|
||||
$identifierFlattener->flattenIdentifier($classMetadata, $identifier),
|
||||
);
|
||||
}
|
||||
}, ReflectionClass::SKIP_INITIALIZATION_ON_SERIALIZE);
|
||||
|
||||
foreach ($identifier as $idField => $value) {
|
||||
@@ -419,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}"));
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query\AST;
|
||||
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* AllFieldsExpression ::= u.*
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
*/
|
||||
class AllFieldsExpression extends Node
|
||||
{
|
||||
public string $field = '';
|
||||
|
||||
public function __construct(
|
||||
public string|null $identificationVariable,
|
||||
) {
|
||||
$this->field = $this->identificationVariable . '.*';
|
||||
}
|
||||
|
||||
public function dispatch(SqlWalker $walker, int|string $parent = '', int|string $argIndex = '', int|null &$aliasGap = null): string
|
||||
{
|
||||
return $walker->walkAllEntityFieldsExpression($this, $parent, $argIndex, $aliasGap);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -20,7 +20,7 @@ class NewObjectExpression extends Node
|
||||
* @param class-string $className
|
||||
* @param mixed[] $args
|
||||
*/
|
||||
public function __construct(public string $className, public array $args, public bool $hasNamedArgs = false)
|
||||
public function __construct(public string $className, public array $args)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -6,18 +6,28 @@ namespace Doctrine\ORM\Query\Exec;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Query\AST\SelectStatement;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* Executor that executes the SQL statement for simple DQL SELECT statements.
|
||||
*
|
||||
* @deprecated This class is no longer needed by the ORM and will be removed in 4.0.
|
||||
*
|
||||
* @link www.doctrine-project.org
|
||||
*/
|
||||
class SingleSelectExecutor extends AbstractSqlExecutor
|
||||
{
|
||||
public function __construct(SelectStatement $AST, SqlWalker $sqlWalker)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11188/',
|
||||
'The %s is no longer needed by the ORM and will be removed in 4.0',
|
||||
self::class,
|
||||
);
|
||||
|
||||
$this->sqlStatements = $sqlWalker->walkSelectStatement($AST);
|
||||
}
|
||||
|
||||
|
||||
20
src/Query/Filter/Parameter.php
Normal file
20
src/Query/Filter/Parameter.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query\Filter;
|
||||
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
|
||||
/** @internal */
|
||||
final class Parameter
|
||||
{
|
||||
/** @param ParameterType::*|ArrayParameterType::*|string $type */
|
||||
public function __construct(
|
||||
public readonly mixed $value,
|
||||
public readonly ParameterType|ArrayParameterType|int|string $type,
|
||||
public readonly bool $isList,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ abstract class SQLFilter implements Stringable
|
||||
/**
|
||||
* Parameters for the filter.
|
||||
*
|
||||
* @phpstan-var array<string,array{type: string, value: mixed, is_list: bool}>
|
||||
* @phpstan-var array<string, Parameter>
|
||||
*/
|
||||
private array $parameters = [];
|
||||
|
||||
@@ -49,7 +49,7 @@ abstract class SQLFilter implements Stringable
|
||||
*/
|
||||
final public function setParameterList(string $name, array $values, string $type = Types::STRING): static
|
||||
{
|
||||
$this->parameters[$name] = ['value' => $values, 'type' => $type, 'is_list' => true];
|
||||
$this->parameters[$name] = new Parameter(value: $values, type: $type, isList: true);
|
||||
|
||||
// Keep the parameters sorted for the hash
|
||||
ksort($this->parameters);
|
||||
@@ -71,11 +71,11 @@ abstract class SQLFilter implements Stringable
|
||||
*/
|
||||
final public function setParameter(string $name, mixed $value, string|null $type = null): static
|
||||
{
|
||||
if ($type === null) {
|
||||
$type = ParameterTypeInferer::inferType($value);
|
||||
}
|
||||
|
||||
$this->parameters[$name] = ['value' => $value, 'type' => $type, 'is_list' => false];
|
||||
$this->parameters[$name] = new Parameter(
|
||||
value: $value,
|
||||
type: $type ?? ParameterTypeInferer::inferType($value),
|
||||
isList: false,
|
||||
);
|
||||
|
||||
// Keep the parameters sorted for the hash
|
||||
ksort($this->parameters);
|
||||
@@ -102,11 +102,11 @@ abstract class SQLFilter implements Stringable
|
||||
throw new InvalidArgumentException("Parameter '" . $name . "' does not exist.");
|
||||
}
|
||||
|
||||
if ($this->parameters[$name]['is_list']) {
|
||||
if ($this->parameters[$name]->isList) {
|
||||
throw FilterException::cannotConvertListParameterIntoSingleValue($name);
|
||||
}
|
||||
|
||||
return $this->em->getConnection()->quote((string) $this->parameters[$name]['value']);
|
||||
return $this->em->getConnection()->quote((string) $this->parameters[$name]->value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +124,7 @@ abstract class SQLFilter implements Stringable
|
||||
throw new InvalidArgumentException("Parameter '" . $name . "' does not exist.");
|
||||
}
|
||||
|
||||
if ($this->parameters[$name]['is_list'] === false) {
|
||||
if (! $this->parameters[$name]->isList) {
|
||||
throw FilterException::cannotConvertSingleParameterIntoListValue($name);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ abstract class SQLFilter implements Stringable
|
||||
|
||||
$quoted = array_map(
|
||||
static fn (mixed $value): string => $connection->quote((string) $value),
|
||||
$param['value'],
|
||||
$param->value,
|
||||
);
|
||||
|
||||
return implode(',', $quoted);
|
||||
@@ -152,7 +152,14 @@ abstract class SQLFilter implements Stringable
|
||||
*/
|
||||
final public function __toString(): string
|
||||
{
|
||||
return serialize($this->parameters);
|
||||
return serialize(array_map(
|
||||
static fn (Parameter $value): array => [
|
||||
'value' => $value->value,
|
||||
'type' => $value->type,
|
||||
'is_list' => $value->isList,
|
||||
],
|
||||
$this->parameters,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,9 +25,9 @@ use function is_int;
|
||||
final class ParameterTypeInferer
|
||||
{
|
||||
/**
|
||||
* Infers type of a given value, returning a compatible constant:
|
||||
* - Type (\Doctrine\DBAL\Types\Type::*)
|
||||
* - Connection (\Doctrine\DBAL\Connection::PARAM_*)
|
||||
* Infers the type of a given value
|
||||
*
|
||||
* @return ParameterType::*|ArrayParameterType::*|Types::*
|
||||
*/
|
||||
public static function inferType(mixed $value): ParameterType|ArrayParameterType|int|string
|
||||
{
|
||||
|
||||
@@ -1036,7 +1036,6 @@ final class Parser
|
||||
assert($this->lexer->token !== null);
|
||||
if ($this->lexer->isNextToken(TokenType::T_DOT)) {
|
||||
$this->match(TokenType::T_DOT);
|
||||
|
||||
$this->match(TokenType::T_IDENTIFIER);
|
||||
|
||||
$field = $this->lexer->token->value;
|
||||
@@ -1148,21 +1147,7 @@ final class Parser
|
||||
];
|
||||
}
|
||||
|
||||
return new AST\EntityAsDtoArgumentExpression($expression, $identVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
* AllFieldsExpression ::= IdentificationVariable
|
||||
*/
|
||||
public function AllFieldsExpression(): AST\AllFieldsExpression
|
||||
{
|
||||
$identVariable = $this->IdentificationVariable();
|
||||
assert($this->lexer->token !== null);
|
||||
|
||||
$this->match(TokenType::T_DOT);
|
||||
$this->match(TokenType::T_MULTIPLY);
|
||||
|
||||
return new AST\AllFieldsExpression($identVariable);
|
||||
return new AST\EntityAsDtoArgumentExpression($expression, $identVariable, $aliasResultVariable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1479,7 +1464,7 @@ final class Parser
|
||||
|
||||
assert($this->lexer->lookahead !== null);
|
||||
$expr = match (true) {
|
||||
$this->isMathOperator($peek) => $this->SimpleArithmeticExpression(),
|
||||
$this->isMathOperator($peek) || $this->isMathOperator($glimpse) => $this->SimpleArithmeticExpression(),
|
||||
$glimpse !== null && $glimpse->type === TokenType::T_DOT => $this->SingleValuedPathExpression(),
|
||||
$this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()) => $this->ScalarExpression(),
|
||||
$this->lexer->lookahead->type === TokenType::T_CASE => $this->CaseExpression(),
|
||||
@@ -1624,8 +1609,7 @@ final class Parser
|
||||
|
||||
/**
|
||||
* Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
|
||||
* (JoinAssociationDeclaration | RangeVariableDeclaration)
|
||||
* ["WITH" ConditionalExpression]
|
||||
* (JoinAssociationDeclaration ["WITH" ConditionalExpression] | RangeVariableDeclaration [("ON" | "WITH") ConditionalExpression])
|
||||
*/
|
||||
public function Join(): AST\Join
|
||||
{
|
||||
@@ -1659,21 +1643,31 @@ final class Parser
|
||||
|
||||
$next = $this->lexer->glimpse();
|
||||
assert($next !== null);
|
||||
$joinDeclaration = $next->type === TokenType::T_DOT ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
|
||||
$adhocConditions = $this->lexer->isNextToken(TokenType::T_WITH);
|
||||
$join = new AST\Join($joinType, $joinDeclaration);
|
||||
$conditionalExpression = null;
|
||||
|
||||
// Describe non-root join declaration
|
||||
if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
|
||||
if ($next->type === TokenType::T_DOT) {
|
||||
$joinDeclaration = $this->JoinAssociationDeclaration();
|
||||
|
||||
if ($this->lexer->isNextToken(TokenType::T_WITH)) {
|
||||
$this->match(TokenType::T_WITH);
|
||||
$conditionalExpression = $this->ConditionalExpression();
|
||||
}
|
||||
} else {
|
||||
$joinDeclaration = $this->RangeVariableDeclaration();
|
||||
$joinDeclaration->isRoot = false;
|
||||
|
||||
if ($this->lexer->isNextToken(TokenType::T_ON)) {
|
||||
$this->match(TokenType::T_ON);
|
||||
$conditionalExpression = $this->ConditionalExpression();
|
||||
} elseif ($this->lexer->isNextToken(TokenType::T_WITH)) {
|
||||
$this->match(TokenType::T_WITH);
|
||||
$conditionalExpression = $this->ConditionalExpression();
|
||||
Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/issues/12192', 'Using WITH for the join condition of arbitrary joins is deprecated. Use ON instead.');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ad-hoc Join conditions
|
||||
if ($adhocConditions) {
|
||||
$this->match(TokenType::T_WITH);
|
||||
|
||||
$join->conditionalExpression = $this->ConditionalExpression();
|
||||
}
|
||||
$join = new AST\Join($joinType, $joinDeclaration);
|
||||
$join->conditionalExpression = $conditionalExpression;
|
||||
|
||||
return $join;
|
||||
}
|
||||
@@ -1841,7 +1835,7 @@ final class Parser
|
||||
|
||||
$this->match(TokenType::T_CLOSE_PARENTHESIS);
|
||||
|
||||
$expression = new AST\NewObjectExpression($className, $args, $useNamedArguments);
|
||||
$expression = new AST\NewObjectExpression($className, $args);
|
||||
|
||||
// Defer NewObjectExpression validation
|
||||
$this->deferredNewObjectExpressions[] = [
|
||||
@@ -1888,7 +1882,7 @@ final class Parser
|
||||
}
|
||||
|
||||
/**
|
||||
* NewObjectArg ::= ((ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]) | AllFieldsExpression
|
||||
* NewObjectArg ::= (ScalarExpression | "(" Subselect ")" | NewObjectExpression) ["AS" AliasResultVariable]
|
||||
*/
|
||||
public function NewObjectArg(string|null &$fieldAlias = null): mixed
|
||||
{
|
||||
@@ -1910,6 +1904,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();
|
||||
}
|
||||
@@ -2000,14 +1995,10 @@ final class Parser
|
||||
// it is no function, so it must be a field path
|
||||
case $lookahead === TokenType::T_IDENTIFIER:
|
||||
$this->lexer->peek(); // lookahead => '.'
|
||||
$token = $this->lexer->peek(); // lookahead => token after '.'
|
||||
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
|
||||
$this->lexer->peek(); // lookahead => token after '.'
|
||||
$peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
|
||||
$this->lexer->resetPeek();
|
||||
|
||||
if ($token->value === '*') {
|
||||
return $this->AllFieldsExpression();
|
||||
}
|
||||
|
||||
if ($this->isMathOperator($peek)) {
|
||||
return $this->SimpleArithmeticExpression();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Query;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
|
||||
use Doctrine\ORM\Query\Exec\SqlFinalizer;
|
||||
@@ -71,20 +72,36 @@ class ParserResult
|
||||
/**
|
||||
* Sets the SQL executor that should be used for this ParserResult.
|
||||
*
|
||||
* @deprecated
|
||||
* @deprecated The SqlExecutor will be removed from ParserResult in 4.0. Provide a SqlFinalizer instead that can create the executor.
|
||||
*/
|
||||
public function setSqlExecutor(AbstractSqlExecutor $executor): void
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11188',
|
||||
'The SqlExecutor will be removed from %s in 4.0. Provide a %s instead that can create the executor.',
|
||||
self::class,
|
||||
SqlFinalizer::class,
|
||||
);
|
||||
|
||||
$this->sqlExecutor = $executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SQL executor used by this ParserResult.
|
||||
*
|
||||
* @deprecated
|
||||
* @deprecated The SqlExecutor will be removed from ParserResult in 4.0. Provide a SqlFinalizer instead that can create the executor.
|
||||
*/
|
||||
public function getSqlExecutor(): AbstractSqlExecutor
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11188',
|
||||
'The SqlExecutor will be removed from %s in 4.0. Provide a %s instead that can create the executor.',
|
||||
self::class,
|
||||
SqlFinalizer::class,
|
||||
);
|
||||
|
||||
if ($this->sqlExecutor === null) {
|
||||
throw new LogicException(sprintf(
|
||||
'Executor not set yet. Call %s::setSqlExecutor() first.',
|
||||
|
||||
@@ -9,6 +9,7 @@ use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\QuoteStrategy;
|
||||
@@ -230,6 +231,14 @@ class SqlWalker
|
||||
*/
|
||||
public function getExecutor(AST\SelectStatement|AST\UpdateStatement|AST\DeleteStatement $statement): Exec\AbstractSqlExecutor
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/11188/',
|
||||
'Output walkers should implement %s. That way, the %s method is no longer needed and will be removed in 4.0',
|
||||
OutputWalker::class,
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return match (true) {
|
||||
$statement instanceof AST\UpdateStatement => $this->createUpdateStatementExecutor($statement),
|
||||
$statement instanceof AST\DeleteStatement => $this->createDeleteStatementExecutor($statement),
|
||||
@@ -1412,7 +1421,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 +1456,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);
|
||||
}
|
||||
@@ -1518,17 +1531,11 @@ class SqlWalker
|
||||
{
|
||||
$sqlSelectExpressions = [];
|
||||
$objIndex = $newObjectResultAlias ?: $this->newObjectCounter++;
|
||||
$aliasGap = $newObjectExpression->hasNamedArgs ? null : 0;
|
||||
|
||||
foreach ($newObjectExpression->args as $argIndex => $e) {
|
||||
if (! $newObjectExpression->hasNamedArgs) {
|
||||
$argIndex += $aliasGap;
|
||||
}
|
||||
|
||||
$resultAlias = $this->scalarResultCounter++;
|
||||
$columnAlias = $this->getSQLColumnAlias('sclr');
|
||||
$fieldType = 'string';
|
||||
$isScalarResult = true;
|
||||
$resultAlias = $this->scalarResultCounter++;
|
||||
$columnAlias = $this->getSQLColumnAlias('sclr');
|
||||
$fieldType = 'string';
|
||||
|
||||
switch (true) {
|
||||
case $e instanceof AST\NewObjectExpression:
|
||||
@@ -1582,26 +1589,18 @@ class SqlWalker
|
||||
$sqlSelectExpressions[] = trim($e->dispatch($this));
|
||||
break;
|
||||
|
||||
case $e instanceof AST\AllFieldsExpression:
|
||||
$isScalarResult = false;
|
||||
$sqlSelectExpressions[] = $e->dispatch($this, $objIndex, $argIndex, $aliasGap);
|
||||
break;
|
||||
|
||||
default:
|
||||
$sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($isScalarResult) {
|
||||
$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
|
||||
$this->scalarResultAliasMap[$resultAlias] = $columnAlias;
|
||||
$this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
|
||||
|
||||
$this->rsm->newObjectMappings[$columnAlias] = [
|
||||
'className' => $newObjectExpression->className,
|
||||
'objIndex' => $objIndex,
|
||||
'argIndex' => $argIndex,
|
||||
];
|
||||
}
|
||||
$this->rsm->newObjectMappings[$columnAlias] = [
|
||||
'objIndex' => $objIndex,
|
||||
'argIndex' => $argIndex,
|
||||
];
|
||||
}
|
||||
|
||||
$this->rsm->newObject[$objIndex] = $newObjectExpression->className;
|
||||
@@ -2306,42 +2305,6 @@ class SqlWalker
|
||||
return $resultAlias;
|
||||
}
|
||||
|
||||
public function walkAllEntityFieldsExpression(AST\AllFieldsExpression $expression, int|string $objIndex, int|string $argIndex, int|null &$aliasGap): string
|
||||
{
|
||||
$dqlAlias = $expression->identificationVariable;
|
||||
$class = $this->getMetadataForDqlAlias($expression->identificationVariable);
|
||||
|
||||
$sqlParts = [];
|
||||
// Select all fields from the queried class
|
||||
foreach ($class->fieldMappings as $fieldName => $mapping) {
|
||||
$tableName = isset($mapping->inherited)
|
||||
? $this->em->getClassMetadata($mapping->inherited)->getTableName()
|
||||
: $class->getTableName();
|
||||
|
||||
$sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
|
||||
$columnAlias = $this->getSQLColumnAlias($mapping->columnName);
|
||||
$quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
|
||||
|
||||
$col = $sqlTableAlias . '.' . $quotedColumnName;
|
||||
|
||||
$type = Type::getType($mapping->type);
|
||||
$col = $type->convertToPHPValueSQL($col, $this->platform);
|
||||
|
||||
$sqlParts[] = $col . ' AS ' . $columnAlias;
|
||||
|
||||
$this->scalarResultAliasMap[$objIndex][] = $columnAlias;
|
||||
|
||||
$this->rsm->addScalarResult($columnAlias, $objIndex, $mapping->type);
|
||||
|
||||
$this->rsm->newObjectMappings[$columnAlias] = [
|
||||
'objIndex' => $objIndex,
|
||||
'argIndex' => $aliasGap === null ? $fieldName : (int) $argIndex + $aliasGap++,
|
||||
];
|
||||
}
|
||||
|
||||
return implode(', ', $sqlParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The list in parentheses of valid child discriminators from the given class
|
||||
*
|
||||
|
||||
@@ -90,4 +90,5 @@ enum TokenType: int
|
||||
case T_WHERE = 255;
|
||||
case T_WITH = 256;
|
||||
case T_NAMED = 257;
|
||||
case T_ON = 258;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\DBAL\ArrayParameterType;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Internal\NoUnknownNamedArguments;
|
||||
use Doctrine\ORM\Query\Expr;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
@@ -116,6 +117,13 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
private int $boundCounter = 0;
|
||||
|
||||
/**
|
||||
* The hints to set on the query.
|
||||
*
|
||||
* @var array<string, string|int|bool|iterable<mixed>|object>
|
||||
*/
|
||||
private array $hints = [];
|
||||
|
||||
/**
|
||||
* Initializes a new <tt>QueryBuilder</tt> that uses the given <tt>EntityManager</tt>.
|
||||
*
|
||||
@@ -207,6 +215,39 @@ class QueryBuilder implements Stringable
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return array<string, string|int|bool|iterable<mixed>|object> */
|
||||
public function getHints(): array
|
||||
{
|
||||
return $this->hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
|
||||
*
|
||||
* @return mixed The value of the hint or FALSE, if the hint name is not recognized.
|
||||
*/
|
||||
public function getHint(string $name): mixed
|
||||
{
|
||||
return $this->hints[$name] ?? false;
|
||||
}
|
||||
|
||||
public function hasHint(string $name): bool
|
||||
{
|
||||
return isset($this->hints[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds hints for the query.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHint(string $name, mixed $value): static
|
||||
{
|
||||
$this->hints[$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @phpstan-return Cache::MODE_*|null */
|
||||
public function getCacheMode(): int|null
|
||||
{
|
||||
@@ -287,6 +328,10 @@ class QueryBuilder implements Stringable
|
||||
$query->setCacheRegion($this->cacheRegion);
|
||||
}
|
||||
|
||||
foreach ($this->hints as $name => $value) {
|
||||
$query->setHint($name, $value);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
@@ -305,8 +350,13 @@ class QueryBuilder implements Stringable
|
||||
} else {
|
||||
// Should never happen with correct joining order. Might be
|
||||
// thoughtful to throw exception instead.
|
||||
// @phpstan-ignore method.deprecated
|
||||
$rootAlias = $this->getRootAlias();
|
||||
$aliases = $this->getRootAliases();
|
||||
|
||||
if (! isset($aliases[0])) {
|
||||
throw new RuntimeException('No alias was set before invoking getRootAlias().');
|
||||
}
|
||||
|
||||
$rootAlias = $aliases[0];
|
||||
}
|
||||
|
||||
$this->joinRootAliases[$alias] = $rootAlias;
|
||||
@@ -541,6 +591,10 @@ class QueryBuilder implements Stringable
|
||||
*/
|
||||
public function setMaxResults(int|null $maxResults): static
|
||||
{
|
||||
if ($this->type === QueryType::Delete || $this->type === QueryType::Update) {
|
||||
throw new RuntimeException('Setting a limit is not supported for delete or update queries.');
|
||||
}
|
||||
|
||||
$this->maxResults = $maxResults;
|
||||
|
||||
return $this;
|
||||
@@ -582,14 +636,25 @@ class QueryBuilder implements Stringable
|
||||
$dqlPart = reset($dqlPart);
|
||||
}
|
||||
|
||||
// This is introduced for backwards compatibility reasons.
|
||||
// TODO: Remove for 3.0
|
||||
if ($dqlPartName === 'join') {
|
||||
$newDqlPart = [];
|
||||
|
||||
foreach ($dqlPart as $k => $v) {
|
||||
// @phpstan-ignore method.deprecated
|
||||
$k = is_numeric($k) ? $this->getRootAlias() : $k;
|
||||
if (is_numeric($k)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12051',
|
||||
'Using numeric keys in %s for join parts is deprecated and will not be supported in 4.0. Use an associative array with the root alias as key instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
$aliases = $this->getRootAliases();
|
||||
|
||||
if (! isset($aliases[0])) {
|
||||
throw new RuntimeException('No alias was set before invoking add().');
|
||||
}
|
||||
|
||||
$k = $aliases[0];
|
||||
}
|
||||
|
||||
$newDqlPart[$k] = $v;
|
||||
}
|
||||
|
||||
29
src/Tools/Console/ApplicationCompatibility.php
Normal file
29
src/Tools/Console/ApplicationCompatibility.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console;
|
||||
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function method_exists;
|
||||
|
||||
/**
|
||||
* Forward compatibility with Symfony Console 7.4
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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')) {
|
||||
return $application->addCommand($command);
|
||||
}
|
||||
|
||||
// @phpstan-ignore method.notFound
|
||||
return $application->add($command);
|
||||
}
|
||||
}
|
||||
34
src/Tools/Console/Command/Debug/AbstractCommand.php
Normal file
34
src/Tools/Console/Command/Debug/AbstractCommand.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command\Debug;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
use function assert;
|
||||
|
||||
/** @internal */
|
||||
abstract class AbstractCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly ManagerRegistry $managerRegistry)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
final protected function getEntityManager(string $name): EntityManagerInterface
|
||||
{
|
||||
$manager = $this->getManagerRegistry()->getManager($name);
|
||||
|
||||
assert($manager instanceof EntityManagerInterface);
|
||||
|
||||
return $manager;
|
||||
}
|
||||
|
||||
final protected function getManagerRegistry(): ManagerRegistry
|
||||
{
|
||||
return $this->managerRegistry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command\Debug;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Helper\TableSeparator;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use function array_keys;
|
||||
use function array_merge;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
use function ksort;
|
||||
use function ltrim;
|
||||
use function sort;
|
||||
use function sprintf;
|
||||
|
||||
final class DebugEntityListenersDoctrineCommand extends AbstractCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('orm:debug:entity-listeners')
|
||||
->setDescription('Lists entity listeners for a given entity')
|
||||
->addArgument('entity', InputArgument::OPTIONAL, 'The fully-qualified entity class name')
|
||||
->addArgument('event', InputArgument::OPTIONAL, 'The event name to filter by (e.g. postPersist)')
|
||||
->setHelp(<<<'EOT'
|
||||
The <info>%command.name%</info> command lists all entity listeners for a given entity:
|
||||
|
||||
<info>php %command.full_name% 'App\Entity\User'</info>
|
||||
|
||||
To show only listeners for a specific event, pass the event name:
|
||||
|
||||
<info>php %command.full_name% 'App\Entity\User' postPersist</info>
|
||||
EOT);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
/** @var class-string|null $entityName */
|
||||
$entityName = $input->getArgument('entity');
|
||||
|
||||
if ($entityName === null) {
|
||||
$choices = $this->listAllEntities();
|
||||
|
||||
if ($choices === []) {
|
||||
$io->error('No entities are configured.');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
/** @var class-string $entityName */
|
||||
$entityName = $io->choice('Which entity do you want to list listeners for?', $choices);
|
||||
}
|
||||
|
||||
$entityName = ltrim($entityName, '\\');
|
||||
$entityManager = $this->getManagerRegistry()->getManagerForClass($entityName);
|
||||
|
||||
if ($entityManager === null) {
|
||||
$io->error(sprintf('No entity manager found for class "%s".', $entityName));
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$classMetadata = $entityManager->getClassMetadata($entityName);
|
||||
assert($classMetadata instanceof ClassMetadata);
|
||||
|
||||
$eventName = $input->getArgument('event');
|
||||
|
||||
if ($eventName === null) {
|
||||
$allListeners = $classMetadata->entityListeners;
|
||||
if (! $allListeners) {
|
||||
$io->info(sprintf('No listeners are configured for the "%s" entity.', $entityName));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
ksort($allListeners);
|
||||
} else {
|
||||
if (! isset($classMetadata->entityListeners[$eventName])) {
|
||||
$io->info(sprintf('No listeners are configured for the "%s" event.', $eventName));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$allListeners = [$eventName => $classMetadata->entityListeners[$eventName]];
|
||||
}
|
||||
|
||||
$io->title(sprintf('Entity listeners for <info>%s</info>', $entityName));
|
||||
|
||||
$rows = [];
|
||||
foreach ($allListeners as $event => $listeners) {
|
||||
if ($rows) {
|
||||
$rows[] = new TableSeparator();
|
||||
}
|
||||
|
||||
foreach ($listeners as $order => $listener) {
|
||||
$rows[] = [$order === 0 ? $event : '', sprintf('#%d', ++$order), sprintf('%s::%s()', $listener['class'], $listener['method'])];
|
||||
}
|
||||
}
|
||||
|
||||
$io->table(['Event', 'Order', 'Listener'], $rows);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('entity')) {
|
||||
$suggestions->suggestValues($this->listAllEntities());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestArgumentValuesFor('event')) {
|
||||
$entityName = ltrim($input->getArgument('entity'), '\\');
|
||||
|
||||
if (! class_exists($entityName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entityManager = $this->getManagerRegistry()->getManagerForClass($entityName);
|
||||
|
||||
if ($entityManager === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$classMetadata = $entityManager->getClassMetadata($entityName);
|
||||
assert($classMetadata instanceof ClassMetadata);
|
||||
|
||||
$suggestions->suggestValues(array_keys($classMetadata->entityListeners));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return list<class-string> */
|
||||
private function listAllEntities(): array
|
||||
{
|
||||
$entities = [];
|
||||
foreach (array_keys($this->getManagerRegistry()->getManagerNames()) as $managerName) {
|
||||
$entities[] = $this->getEntityManager($managerName)->getConfiguration()->getMetadataDriverImpl()->getAllClassNames();
|
||||
}
|
||||
|
||||
$entities = array_values(array_unique(array_merge(...$entities)));
|
||||
|
||||
sort($entities);
|
||||
|
||||
return $entities;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command\Debug;
|
||||
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Helper\TableSeparator;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use function array_keys;
|
||||
use function array_values;
|
||||
use function ksort;
|
||||
use function method_exists;
|
||||
use function sprintf;
|
||||
|
||||
final class DebugEventManagerDoctrineCommand extends AbstractCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('orm:debug:event-manager')
|
||||
->setDescription('Lists event listeners for an entity manager')
|
||||
->addArgument('event', InputArgument::OPTIONAL, 'The event name to filter by (e.g. postPersist)')
|
||||
->addOption('em', null, InputOption::VALUE_REQUIRED, 'The entity manager to use for this command')
|
||||
->setHelp(<<<'EOT'
|
||||
The <info>%command.name%</info> command lists all event listeners for the default entity manager:
|
||||
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
You can also specify an entity manager:
|
||||
|
||||
<info>php %command.full_name% --em=default</info>
|
||||
|
||||
To show only listeners for a specific event, pass the event name as an argument:
|
||||
|
||||
<info>php %command.full_name% postPersist</info>
|
||||
EOT);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$entityManagerName = $input->getOption('em') ?: $this->getManagerRegistry()->getDefaultManagerName();
|
||||
$eventManager = $this->getEntityManager($entityManagerName)->getEventManager();
|
||||
|
||||
$eventName = $input->getArgument('event');
|
||||
|
||||
if ($eventName === null) {
|
||||
$allListeners = $eventManager->getAllListeners();
|
||||
if (! $allListeners) {
|
||||
$io->info(sprintf('No listeners are configured for the "%s" entity manager.', $entityManagerName));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
ksort($allListeners);
|
||||
} else {
|
||||
$listeners = $eventManager->hasListeners($eventName) ? $eventManager->getListeners($eventName) : [];
|
||||
if (! $listeners) {
|
||||
$io->info(sprintf('No listeners are configured for the "%s" event.', $eventName));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$allListeners = [$eventName => $listeners];
|
||||
}
|
||||
|
||||
$io->title(sprintf('Event listeners for <info>%s</info> entity manager', $entityManagerName));
|
||||
|
||||
$rows = [];
|
||||
foreach ($allListeners as $event => $listeners) {
|
||||
if ($rows) {
|
||||
$rows[] = new TableSeparator();
|
||||
}
|
||||
|
||||
foreach (array_values($listeners) as $order => $listener) {
|
||||
$method = method_exists($listener, '__invoke') ? '__invoke' : $event;
|
||||
$rows[] = [$order === 0 ? $event : '', sprintf('#%d', ++$order), sprintf('%s::%s()', $listener::class, $method)];
|
||||
}
|
||||
}
|
||||
|
||||
$io->table(['Event', 'Order', 'Listener'], $rows);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('event')) {
|
||||
$entityManagerName = $input->getOption('em') ?: $this->getManagerRegistry()->getDefaultManagerName();
|
||||
$eventManager = $this->getEntityManager($entityManagerName)->getEventManager();
|
||||
|
||||
$suggestions->suggestValues(array_keys($eventManager->getAllListeners()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('em')) {
|
||||
$suggestions->suggestValues(array_keys($this->getManagerRegistry()->getManagerNames()));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Tools\Console\Command;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Tools\Console\MetadataFilter;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -19,6 +20,8 @@ use function mkdir;
|
||||
use function realpath;
|
||||
use function sprintf;
|
||||
|
||||
use const PHP_VERSION_ID;
|
||||
|
||||
/**
|
||||
* Command to (re)generate the proxy classes used by doctrine.
|
||||
*
|
||||
@@ -39,6 +42,14 @@ class GenerateProxiesCommand extends AbstractEntityManagerCommand
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if (PHP_VERSION_ID >= 80400) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/12005',
|
||||
'Generating proxies is deprecated and will be impossible in Doctrine ORM 4.0.',
|
||||
);
|
||||
}
|
||||
|
||||
$ui = (new SymfonyStyle($input, $output))->getErrorStyle();
|
||||
|
||||
$em = $this->getEntityManager($input);
|
||||
|
||||
@@ -10,6 +10,9 @@ use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Mapping\FieldMapping;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use InvalidArgumentException;
|
||||
use JsonException;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
@@ -19,6 +22,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_values;
|
||||
use function count;
|
||||
use function current;
|
||||
use function get_debug_type;
|
||||
@@ -32,6 +36,7 @@ use function preg_match;
|
||||
use function preg_quote;
|
||||
use function print_r;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
|
||||
use const JSON_PRETTY_PRINT;
|
||||
use const JSON_THROW_ON_ERROR;
|
||||
@@ -48,9 +53,17 @@ final class MappingDescribeCommand extends AbstractEntityManagerCommand
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('orm:mapping:describe')
|
||||
->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
|
||||
->setDescription('Display information about mapped objects')
|
||||
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
|
||||
->addArgument('entityName', InputArgument::REQUIRED, 'Full or partial name of entity')
|
||||
->setDescription('Display information about mapped objects')
|
||||
->addOption('em', null, InputOption::VALUE_REQUIRED, 'Name of the entity manager to operate on')
|
||||
->addOption(
|
||||
'format',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Output format (text, json)',
|
||||
MappingDescribeCommandFormat::TEXT->value,
|
||||
array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()),
|
||||
)
|
||||
->setHelp(<<<'EOT'
|
||||
The %command.full_name% command describes the metadata for the given full or partial entity class name.
|
||||
|
||||
@@ -59,6 +72,13 @@ The %command.full_name% command describes the metadata for the given full or par
|
||||
Or:
|
||||
|
||||
<info>%command.full_name%</info> MyEntity
|
||||
|
||||
To output the metadata in JSON format, use the <info>--format</info> option:
|
||||
<info>%command.full_name% My\Namespace\Entity\MyEntity --format=json</info>
|
||||
|
||||
To use a specific entity manager (e.g., for multi-DB projects), use the <info>--em</info> option:
|
||||
<info>%command.full_name% My\Namespace\Entity\MyEntity --em=my_custom_entity_manager</info>
|
||||
|
||||
EOT);
|
||||
}
|
||||
|
||||
@@ -66,13 +86,33 @@ EOT);
|
||||
{
|
||||
$ui = new SymfonyStyle($input, $output);
|
||||
|
||||
$format = MappingDescribeCommandFormat::from($input->getOption('format'));
|
||||
|
||||
$entityManager = $this->getEntityManager($input);
|
||||
|
||||
$this->displayEntity($input->getArgument('entityName'), $entityManager, $ui);
|
||||
$this->displayEntity($input->getArgument('entityName'), $entityManager, $ui, $format);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('entityName')) {
|
||||
$entityManager = $this->getEntityManager($input);
|
||||
|
||||
$entities = array_map(
|
||||
static fn (string $fqcn) => str_replace('\\', '\\\\', $fqcn),
|
||||
$this->getMappedEntities($entityManager),
|
||||
);
|
||||
|
||||
$suggestions->suggestValues(array_values($entities));
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(array_map(static fn (MappingDescribeCommandFormat $format) => $format->value, MappingDescribeCommandFormat::cases()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display all the mapping information for a single Entity.
|
||||
*
|
||||
@@ -82,9 +122,47 @@ EOT);
|
||||
string $entityName,
|
||||
EntityManagerInterface $entityManager,
|
||||
SymfonyStyle $ui,
|
||||
MappingDescribeCommandFormat $format,
|
||||
): void {
|
||||
$metadata = $this->getClassMetadata($entityName, $entityManager);
|
||||
|
||||
if ($format === MappingDescribeCommandFormat::JSON) {
|
||||
$ui->text(json_encode(
|
||||
[
|
||||
'name' => $metadata->name,
|
||||
'rootEntityName' => $metadata->rootEntityName,
|
||||
'customGeneratorDefinition' => $this->formatValueAsJson($metadata->customGeneratorDefinition),
|
||||
'customRepositoryClassName' => $metadata->customRepositoryClassName,
|
||||
'isMappedSuperclass' => $metadata->isMappedSuperclass,
|
||||
'isEmbeddedClass' => $metadata->isEmbeddedClass,
|
||||
'parentClasses' => $metadata->parentClasses,
|
||||
'subClasses' => $metadata->subClasses,
|
||||
'embeddedClasses' => $metadata->embeddedClasses,
|
||||
'identifier' => $metadata->identifier,
|
||||
'inheritanceType' => $metadata->inheritanceType,
|
||||
'discriminatorColumn' => $this->formatValueAsJson($metadata->discriminatorColumn),
|
||||
'discriminatorValue' => $metadata->discriminatorValue,
|
||||
'discriminatorMap' => $metadata->discriminatorMap,
|
||||
'generatorType' => $metadata->generatorType,
|
||||
'table' => $this->formatValueAsJson($metadata->table),
|
||||
'isIdentifierComposite' => $metadata->isIdentifierComposite,
|
||||
'containsForeignIdentifier' => $metadata->containsForeignIdentifier,
|
||||
'containsEnumIdentifier' => $metadata->containsEnumIdentifier,
|
||||
'sequenceGeneratorDefinition' => $this->formatValueAsJson($metadata->sequenceGeneratorDefinition),
|
||||
'changeTrackingPolicy' => $metadata->changeTrackingPolicy,
|
||||
'isVersioned' => $metadata->isVersioned,
|
||||
'versionField' => $metadata->versionField,
|
||||
'isReadOnly' => $metadata->isReadOnly,
|
||||
'entityListeners' => $metadata->entityListeners,
|
||||
'associationMappings' => $this->formatMappingsAsJson($metadata->associationMappings),
|
||||
'fieldMappings' => $this->formatMappingsAsJson($metadata->fieldMappings),
|
||||
],
|
||||
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR,
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ui->table(
|
||||
['Field', 'Value'],
|
||||
array_merge(
|
||||
@@ -97,7 +175,7 @@ EOT);
|
||||
$this->formatField('Embedded class?', $metadata->isEmbeddedClass),
|
||||
$this->formatField('Parent classes', $metadata->parentClasses),
|
||||
$this->formatField('Sub classes', $metadata->subClasses),
|
||||
$this->formatField('Embedded classes', $metadata->subClasses),
|
||||
$this->formatField('Embedded classes', $metadata->embeddedClasses),
|
||||
$this->formatField('Identifier', $metadata->identifier),
|
||||
$this->formatField('Inheritance type', $metadata->inheritanceType),
|
||||
$this->formatField('Discriminator column', $metadata->discriminatorColumn),
|
||||
@@ -222,6 +300,22 @@ EOT);
|
||||
throw new InvalidArgumentException(sprintf('Do not know how to format value "%s"', print_r($value, true)));
|
||||
}
|
||||
|
||||
/** @throws JsonException */
|
||||
private function formatValueAsJson(mixed $value): mixed
|
||||
{
|
||||
if (is_object($value)) {
|
||||
$value = (array) $value;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $k => $v) {
|
||||
$value[$k] = $this->formatValueAsJson($v);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given label and value to the two column table output
|
||||
*
|
||||
@@ -263,6 +357,22 @@ EOT);
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, FieldMapping|AssociationMapping> $propertyMappings
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function formatMappingsAsJson(array $propertyMappings): array
|
||||
{
|
||||
$output = [];
|
||||
|
||||
foreach ($propertyMappings as $propertyName => $mapping) {
|
||||
$output[$propertyName] = $this->formatValueAsJson((array) $mapping);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the entity listeners
|
||||
*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user