mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
391 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5a6cc6630 | ||
|
|
fc3201bded | ||
|
|
3178b4ec4f | ||
|
|
465c02fe68 | ||
|
|
7e45ad935c | ||
|
|
0b14c01b93 | ||
|
|
552d98d554 | ||
|
|
8f605c652a | ||
|
|
75e42abfdf | ||
|
|
3133bf06c2 | ||
|
|
7cf4074d3a | ||
|
|
bb1deba510 | ||
|
|
a5553a0786 | ||
|
|
0b9c949590 | ||
|
|
3ee7d96179 | ||
|
|
9f926f04ba | ||
|
|
4e445feb6c | ||
|
|
d79e61f8d9 | ||
|
|
06eafd82ac | ||
|
|
5f4b76b88f | ||
|
|
794777b21f | ||
|
|
119c378a3a | ||
|
|
90bc6dc300 | ||
|
|
e93e8e0bdf | ||
|
|
bdf067b58a | ||
|
|
f08b67f0cc | ||
|
|
5f8504b5cf | ||
|
|
16e25656d9 | ||
|
|
cacdc56b6e | ||
|
|
13d1c7b286 | ||
|
|
06c77cebb5 | ||
|
|
1ed0057276 | ||
|
|
5b6f3cd598 | ||
|
|
38271d4aeb | ||
|
|
84213b9f05 | ||
|
|
e750360bd5 | ||
|
|
e8ac1169ad | ||
|
|
dd8c7003b8 | ||
|
|
9bec416bb0 | ||
|
|
021722acc7 | ||
|
|
5a528bef5d | ||
|
|
5fbcb18334 | ||
|
|
8280f41fa5 | ||
|
|
d95d8d0a19 | ||
|
|
d36004f825 | ||
|
|
18366db578 | ||
|
|
1b5bef3d4d | ||
|
|
880b622fe6 | ||
|
|
cbf141f7ed | ||
|
|
536bb4f678 | ||
|
|
3f2f42277e | ||
|
|
757d950128 | ||
|
|
f07840b10c | ||
|
|
6e8afeeb60 | ||
|
|
9130b27aca | ||
|
|
2e7c2bb385 | ||
|
|
d69a0fa2cf | ||
|
|
a8b02fd70f | ||
|
|
60adc6b7d9 | ||
|
|
c65ff6651c | ||
|
|
5f29fcdea2 | ||
|
|
2b8ac15813 | ||
|
|
6dd07e4c76 | ||
|
|
0d043059b9 | ||
|
|
bb3ce7e802 | ||
|
|
bc7e252f00 | ||
|
|
498da2ff98 | ||
|
|
73e1e42ab5 | ||
|
|
5d11648767 | ||
|
|
0ecac1b255 | ||
|
|
b2a4fac40b | ||
|
|
beeba93a53 | ||
|
|
4c253f2403 | ||
|
|
46ec86557e | ||
|
|
f287b74470 | ||
|
|
12d086551e | ||
|
|
5283e1441c | ||
|
|
18be6d2218 | ||
|
|
2fda625dba | ||
|
|
35c44a5667 | ||
|
|
0215b6b9fb | ||
|
|
cc9272f53f | ||
|
|
e3e7f3c209 | ||
|
|
c9870a3d82 | ||
|
|
827cb0c10b | ||
|
|
4d19c0ea71 | ||
|
|
17cfb944f2 | ||
|
|
78d08584f1 | ||
|
|
f7e202f3ed | ||
|
|
33f4db8405 | ||
|
|
fa63a395cc | ||
|
|
2ec2c585b0 | ||
|
|
c8025dc4f8 | ||
|
|
cd95b2a9e5 | ||
|
|
8bfe20073b | ||
|
|
38a9a1c795 | ||
|
|
2ebe18a822 | ||
|
|
9b37541b3b | ||
|
|
518d7f2ef1 | ||
|
|
7684cea8ef | ||
|
|
5085dbe94b | ||
|
|
6c64bc6067 | ||
|
|
f5246bdedd | ||
|
|
705dc6fbda | ||
|
|
faedb90ffa | ||
|
|
2da28703e3 | ||
|
|
20cec8ed79 | ||
|
|
c125a856d0 | ||
|
|
d7db596cb4 | ||
|
|
3a9aa5b8c6 | ||
|
|
99d9c46bde | ||
|
|
61d405162f | ||
|
|
b0f15e070d | ||
|
|
fbb7e24594 | ||
|
|
888a4a8eff | ||
|
|
48b4f63f61 | ||
|
|
4a62b661a5 | ||
|
|
ab4844b82a | ||
|
|
00989d6671 | ||
|
|
7ed0db0621 | ||
|
|
d6dcfbd6f7 | ||
|
|
baf6a394a1 | ||
|
|
1538d70bb9 | ||
|
|
291765e879 | ||
|
|
f79ec43e70 | ||
|
|
306b5f9812 | ||
|
|
68405f3e5b | ||
|
|
83c1ad2f57 | ||
|
|
79447cbb18 | ||
|
|
eea53397c5 | ||
|
|
3295ccfa25 | ||
|
|
b1419ddc6c | ||
|
|
0ef08c5dfb | ||
|
|
dc8ddfd3e6 | ||
|
|
278bf194ca | ||
|
|
b9f2488c6c | ||
|
|
b931a59ebc | ||
|
|
d15eef9051 | ||
|
|
c05e1709e9 | ||
|
|
6e31758c7b | ||
|
|
eff540a996 | ||
|
|
33d74e2e48 | ||
|
|
09ff36cda0 | ||
|
|
e30426cbc0 | ||
|
|
e9135b86e0 | ||
|
|
5ccb59fa02 | ||
|
|
2e927970ca | ||
|
|
0366a5796f | ||
|
|
93f7e78a14 | ||
|
|
d99e64c05e | ||
|
|
9efeefb913 | ||
|
|
3f8430459c | ||
|
|
5f12b8f7de | ||
|
|
f949b9d212 | ||
|
|
2bc0be6fa9 | ||
|
|
3dc5581294 | ||
|
|
7bf2c4c8d1 | ||
|
|
c81776ad12 | ||
|
|
d9c6f86627 | ||
|
|
ddede4064c | ||
|
|
67d82cdf72 | ||
|
|
744f0b5983 | ||
|
|
1d02289481 | ||
|
|
4bd0f974ab | ||
|
|
d0c582ca48 | ||
|
|
cc6cc26f18 | ||
|
|
768e2f3816 | ||
|
|
deb5f49413 | ||
|
|
52ce39f595 | ||
|
|
f84ecb2842 | ||
|
|
b2fedaef9e | ||
|
|
21976471a3 | ||
|
|
6fb88e1496 | ||
|
|
3ac5f119aa | ||
|
|
01fb82b497 | ||
|
|
4f1072e1ac | ||
|
|
a559563682 | ||
|
|
0f9cc194ae | ||
|
|
2513a1e2b1 | ||
|
|
4230214ced | ||
|
|
fb1f258736 | ||
|
|
aae8b43622 | ||
|
|
e66fbc434d | ||
|
|
3f4e9e397a | ||
|
|
0f6f752887 | ||
|
|
c1dd1cfc2c | ||
|
|
3684d236f6 | ||
|
|
bba6c696f5 | ||
|
|
e02e6f481b | ||
|
|
48e4e333c7 | ||
|
|
507bc514ce | ||
|
|
1ae5de5409 | ||
|
|
82508956fe | ||
|
|
a131878814 | ||
|
|
1f63389065 | ||
|
|
359dd4ecfb | ||
|
|
a0697c9aff | ||
|
|
5601c2ce4b | ||
|
|
1141fe106f | ||
|
|
8f7701279d | ||
|
|
779f9c36fa | ||
|
|
0908f92629 | ||
|
|
24badd60fb | ||
|
|
f2d794f8bc | ||
|
|
7311f77dfe | ||
|
|
16afa45abf | ||
|
|
8b4d25e94f | ||
|
|
70087782e8 | ||
|
|
dbc5a818e0 | ||
|
|
31a9c9c49b | ||
|
|
125afb8e39 | ||
|
|
45e196eb57 | ||
|
|
2c30fe6e5b | ||
|
|
6757bdf8c6 | ||
|
|
eed20ff4dd | ||
|
|
636712a928 | ||
|
|
0aa91c7140 | ||
|
|
c2f3831b85 | ||
|
|
2af52f6a18 | ||
|
|
0a79ddf344 | ||
|
|
165c8bd6dd | ||
|
|
07ee555279 | ||
|
|
e6bda4afda | ||
|
|
51b4e02873 | ||
|
|
1915dcd1e8 | ||
|
|
480d99b107 | ||
|
|
c6661caaed | ||
|
|
9e27370f15 | ||
|
|
1a3fbcb145 | ||
|
|
c950e72628 | ||
|
|
05560f260c | ||
|
|
8291a7f09b | ||
|
|
d7d6b9d2c7 | ||
|
|
26e274e373 | ||
|
|
a02642e3e6 | ||
|
|
5209184a60 | ||
|
|
b4da0ece41 | ||
|
|
3980d58b80 | ||
|
|
10cbb24649 | ||
|
|
23f54885bc | ||
|
|
7f29b576d8 | ||
|
|
a8425a5248 | ||
|
|
86ce0e5e35 | ||
|
|
39fd5f4d46 | ||
|
|
476a02075f | ||
|
|
98e10906f8 | ||
|
|
7241b4d2e0 | ||
|
|
b8db858784 | ||
|
|
a9309d748b | ||
|
|
fe09af6df1 | ||
|
|
ed50e3d967 | ||
|
|
1d59e46245 | ||
|
|
d8f3198ef8 | ||
|
|
a0a0b0e476 | ||
|
|
0078a67786 | ||
|
|
38d1124be9 | ||
|
|
e9d3c218ef | ||
|
|
6d2ca8fe40 | ||
|
|
a06011daf3 | ||
|
|
825e9641fd | ||
|
|
0846b8b102 | ||
|
|
1dd2b44982 | ||
|
|
5fbe5ebef4 | ||
|
|
d9508e97df | ||
|
|
534ed9c3c2 | ||
|
|
f4585b954f | ||
|
|
ab3a255440 | ||
|
|
a552df66a9 | ||
|
|
85238d4d98 | ||
|
|
b7e9dd023c | ||
|
|
1ac05f5e4e | ||
|
|
2e4a872272 | ||
|
|
d550364431 | ||
|
|
5b2bf9d74c | ||
|
|
4af1aa3177 | ||
|
|
2fe40679f4 | ||
|
|
7029965d3a | ||
|
|
7e49c70320 | ||
|
|
f7fe5ad1bb | ||
|
|
035c52ce3c | ||
|
|
7e7e38b60e | ||
|
|
36ab133e62 | ||
|
|
e13422ab5e | ||
|
|
f4d5283f70 | ||
|
|
fda79b8e21 | ||
|
|
5a345b01dc | ||
|
|
03f4468be2 | ||
|
|
a3d82f8e2f | ||
|
|
976fe5bc0d | ||
|
|
582b222b00 | ||
|
|
d9e8e839fe | ||
|
|
e8472c8f1a | ||
|
|
deaab5133e | ||
|
|
cffe31fc9d | ||
|
|
0e9c7533fb | ||
|
|
1ffb9152f7 | ||
|
|
51faa6ddb7 | ||
|
|
18d6bc3757 | ||
|
|
7c4ae58517 | ||
|
|
05f8fcf836 | ||
|
|
692c3e1b45 | ||
|
|
acff29fddd | ||
|
|
58659f6c4f | ||
|
|
e410180c6e | ||
|
|
4476b05d59 | ||
|
|
343b0ae576 | ||
|
|
9952350c64 | ||
|
|
3bc78caba9 | ||
|
|
0f1c9ec72a | ||
|
|
80f65d6f77 | ||
|
|
de69f60c6a | ||
|
|
2a653b05a0 | ||
|
|
0f04a82857 | ||
|
|
17903346cf | ||
|
|
98b468da57 | ||
|
|
bccb4c7bd9 | ||
|
|
dc53628faf | ||
|
|
21f339e6eb | ||
|
|
c6831c6b07 | ||
|
|
33da4d84eb | ||
|
|
08de12e962 | ||
|
|
7c83373f1e | ||
|
|
021164fbe5 | ||
|
|
b2d0c21fe0 | ||
|
|
7391e2586a | ||
|
|
3532ce9a25 | ||
|
|
2c769acf8c | ||
|
|
c1b373b931 | ||
|
|
61cb557b18 | ||
|
|
b6cff1aa1c | ||
|
|
4471ad9f6b | ||
|
|
cd57768b08 | ||
|
|
d2206152bb | ||
|
|
a34dc0a0e3 | ||
|
|
881a7b3b69 | ||
|
|
b64824addb | ||
|
|
c7104c9471 | ||
|
|
82bbb1dc4a | ||
|
|
bc6c6c9f0c | ||
|
|
89d0a6a67c | ||
|
|
1febeaca7f | ||
|
|
229dcb082b | ||
|
|
3849aed6fb | ||
|
|
f82db6a894 | ||
|
|
a8a859cf5e | ||
|
|
7be96f64ab | ||
|
|
947935e4c9 | ||
|
|
40af1fcfc6 | ||
|
|
021444b322 | ||
|
|
ec7c637cf2 | ||
|
|
0a0779c4a9 | ||
|
|
a52d9880cc | ||
|
|
08eaba44ca | ||
|
|
05c35c398f | ||
|
|
dac1875a79 | ||
|
|
5a55772559 | ||
|
|
d78fa52ad7 | ||
|
|
4ddaa5fc20 | ||
|
|
d7abcb01bc | ||
|
|
8cff7dcdaf | ||
|
|
601728045c | ||
|
|
b18cd893be | ||
|
|
21390a12b9 | ||
|
|
182bcbae23 | ||
|
|
978f687df9 | ||
|
|
fd1690431f | ||
|
|
3cfcd4ad13 | ||
|
|
69b0b764e3 | ||
|
|
e11cef5fca | ||
|
|
395c02caf4 | ||
|
|
0c4e739e94 | ||
|
|
7a72526e47 | ||
|
|
5f882b1cdd | ||
|
|
b3d849dd38 | ||
|
|
aa1dd881d8 | ||
|
|
92d27f2fea | ||
|
|
f8de44c35f | ||
|
|
f81980e1fa | ||
|
|
e9e54d8f65 | ||
|
|
04bfdf85de | ||
|
|
43f67c6164 | ||
|
|
f5be4183ce | ||
|
|
eed031fab0 | ||
|
|
328f36846e | ||
|
|
f7822c775d | ||
|
|
8c08792f0e | ||
|
|
026bba23f1 | ||
|
|
4305cb9230 | ||
|
|
2886d0dc92 | ||
|
|
07f1c4e8f8 | ||
|
|
0809a2b671 |
@@ -12,21 +12,33 @@
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.12",
|
||||
"branchName": "2.12.x",
|
||||
"slug": "2.12",
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
"slug": "2.14",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.11",
|
||||
"branchName": "2.11.x",
|
||||
"slug": "2.11",
|
||||
"name": "2.13",
|
||||
"branchName": "2.13.x",
|
||||
"slug": "2.13",
|
||||
"current": true,
|
||||
"aliases": [
|
||||
"current",
|
||||
"stable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
|
||||
8
.gitattributes
vendored
8
.gitattributes
vendored
@@ -1,7 +1,8 @@
|
||||
/.github export-ignore
|
||||
/ci export-ignore
|
||||
/docs export-ignore
|
||||
/tests export-ignore
|
||||
/tools export-ignore
|
||||
/docs export-ignore
|
||||
/.github export-ignore
|
||||
.doctrine-project.json export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
@@ -15,5 +16,8 @@ phpcs.xml.dist export-ignore
|
||||
phpbench.json export-ignore
|
||||
phpstan.neon export-ignore
|
||||
phpstan-baseline.neon export-ignore
|
||||
phpstan-dbal2.neon export-ignore
|
||||
phpstan-params.neon export-ignore
|
||||
phpstan-persistence2.neon export-ignore
|
||||
psalm.xml export-ignore
|
||||
psalm-baseline.xml export-ignore
|
||||
|
||||
15
.github/workflows/coding-standard.yml
vendored
15
.github/workflows/coding-standard.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: "Coding Standards"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
|
||||
jobs:
|
||||
coding-standards:
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@1.4.1"
|
||||
with:
|
||||
php-version: "8.1"
|
||||
27
.github/workflows/coding-standards.yml
vendored
Normal file
27
.github/workflows/coding-standards.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: "Coding Standards"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/coding-standards.yml
|
||||
- bin/**
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpcs.xml.dist
|
||||
- tests/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/coding-standards.yml
|
||||
- bin/**
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpcs.xml.dist
|
||||
- tests/**
|
||||
|
||||
jobs:
|
||||
coding-standards:
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@2.1.0"
|
||||
105
.github/workflows/continuous-integration.yml
vendored
105
.github/workflows/continuous-integration.yml
vendored
@@ -4,9 +4,23 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/continuous-integration.yml
|
||||
- ci/**
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpunit.xml.dist
|
||||
- tests/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/continuous-integration.yml
|
||||
- ci/**
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpunit.xml.dist
|
||||
- tests/**
|
||||
|
||||
env:
|
||||
fail-fast: true
|
||||
@@ -14,7 +28,7 @@ env:
|
||||
jobs:
|
||||
phpunit-smoke-check:
|
||||
name: "PHPUnit with SQLite"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -24,17 +38,25 @@ jobs:
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
extension:
|
||||
- "pdo_sqlite"
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
- php-version: "8.1"
|
||||
extension: "pdo_sqlite"
|
||||
- php-version: "8.2"
|
||||
dbal-version: "3@dev"
|
||||
extension: "pdo_sqlite"
|
||||
- php-version: "8.2"
|
||||
dbal-version: "default"
|
||||
extension: "sqlite3"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -42,7 +64,7 @@ jobs:
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
extensions: "pdo, pdo_sqlite"
|
||||
extensions: "apcu, pdo, ${{ matrix.extension }}"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1"
|
||||
|
||||
@@ -51,43 +73,48 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --coverage-clover=coverage-no-cache.xml"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v2"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
with:
|
||||
name: "phpunit-sqlite-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
phpunit-postgres:
|
||||
name: "PHPUnit with PostgreSQL"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
postgres-version:
|
||||
- "9.6"
|
||||
- "14"
|
||||
- "15"
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
postgres-version: "14"
|
||||
- php-version: "8.2"
|
||||
dbal-version: "default"
|
||||
postgres-version: "9.6"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
@@ -103,7 +130,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -119,13 +146,15 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_pgsql.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v2"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -133,17 +162,18 @@ jobs:
|
||||
|
||||
phpunit-mariadb:
|
||||
name: "PHPUnit with MariaDB"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
mariadb-version:
|
||||
- "10.6"
|
||||
- "10.9"
|
||||
extension:
|
||||
- "mysqli"
|
||||
- "pdo_mysql"
|
||||
@@ -168,7 +198,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -185,13 +215,15 @@ jobs:
|
||||
extensions: "${{ matrix.extension }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage.xml"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v2"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
@@ -199,15 +231,16 @@ jobs:
|
||||
|
||||
phpunit-mysql:
|
||||
name: "PHPUnit with MySQL"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
mysql-version:
|
||||
- "5.7"
|
||||
- "8.0"
|
||||
@@ -234,7 +267,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -251,7 +284,9 @@ jobs:
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
composer-options: "--ignore-platform-req=php+"
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --coverage-clover=coverage-no-cache.xml"
|
||||
@@ -264,7 +299,7 @@ jobs:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage files"
|
||||
uses: "actions/upload-artifact@v2"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
@@ -272,7 +307,7 @@ jobs:
|
||||
|
||||
phpunit-lower-php-versions:
|
||||
name: "PHPUnit with SQLite"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -284,7 +319,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -295,17 +330,17 @@ jobs:
|
||||
ini-values: "zend.assertions=1"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
dependency-versions: "${{ matrix.deps }}"
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/sqlite.xml"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_sqlite.xml"
|
||||
|
||||
|
||||
upload_coverage:
|
||||
name: "Upload coverage to Codecov"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs:
|
||||
- "phpunit-smoke-check"
|
||||
- "phpunit-postgres"
|
||||
@@ -314,16 +349,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v2"
|
||||
uses: "actions/download-artifact@v3"
|
||||
with:
|
||||
path: "reports"
|
||||
|
||||
- name: "Upload to Codecov"
|
||||
uses: "codecov/codecov-action@v1"
|
||||
uses: "codecov/codecov-action@v3"
|
||||
with:
|
||||
directory: reports
|
||||
|
||||
18
.github/workflows/phpbench.yml
vendored
18
.github/workflows/phpbench.yml
vendored
@@ -5,9 +5,21 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/phpbench.yml
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpbench.json
|
||||
- tests/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/phpbench.yml
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpbench.json
|
||||
- tests/**
|
||||
|
||||
env:
|
||||
fail-fast: true
|
||||
@@ -15,7 +27,7 @@ env:
|
||||
jobs:
|
||||
phpbench:
|
||||
name: "PHPBench"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -24,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
@@ -36,7 +48,7 @@ jobs:
|
||||
ini-values: "zend.assertions=1"
|
||||
|
||||
- name: "Cache dependencies installed with composer"
|
||||
uses: "actions/cache@v2"
|
||||
uses: "actions/cache@v3"
|
||||
with:
|
||||
path: "~/.composer/cache"
|
||||
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@1.4.1"
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@2.1.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
|
||||
46
.github/workflows/static-analysis.yml
vendored
46
.github/workflows/static-analysis.yml
vendored
@@ -5,14 +5,28 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/static-analysis.yml
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpstan*
|
||||
- psalm*
|
||||
- tests/Doctrine/StaticAnalysis/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/static-analysis.yml
|
||||
- composer.*
|
||||
- lib/**
|
||||
- phpstan*
|
||||
- psalm*
|
||||
- tests/Doctrine/StaticAnalysis/**
|
||||
|
||||
jobs:
|
||||
static-analysis-phpstan:
|
||||
name: "Static Analysis with PHPStan"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -21,11 +35,19 @@ jobs:
|
||||
- "8.1"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "2.13"
|
||||
persistence-version:
|
||||
- "default"
|
||||
include:
|
||||
- php-version: "8.1"
|
||||
dbal-version: "2.13"
|
||||
persistence-version: "default"
|
||||
- php-version: "8.1"
|
||||
dbal-version: "default"
|
||||
persistence-version: "2.5"
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
@@ -37,22 +59,30 @@ jobs:
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Require specific persistence version"
|
||||
run: "composer require doctrine/persistence ^${{ matrix.persistence-version }} --no-update"
|
||||
if: "${{ matrix.persistence-version != 'default' }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Run a static analysis with phpstan/phpstan"
|
||||
run: "vendor/bin/phpstan analyse"
|
||||
if: "${{ matrix.dbal-version == 'default' }}"
|
||||
if: "${{ matrix.dbal-version == 'default' && matrix.persistence-version == 'default'}}"
|
||||
|
||||
- name: "Run a static analysis with phpstan/phpstan"
|
||||
run: "vendor/bin/phpstan analyse -c phpstan-dbal2.neon"
|
||||
if: "${{ matrix.dbal-version == '2.13' }}"
|
||||
|
||||
- name: "Run a static analysis with phpstan/phpstan"
|
||||
run: "vendor/bin/phpstan analyse -c phpstan-persistence2.neon"
|
||||
if: "${{ matrix.dbal-version == 'default' && matrix.persistence-version != 'default'}}"
|
||||
|
||||
static-analysis-psalm:
|
||||
name: "Static Analysis with Psalm"
|
||||
runs-on: "ubuntu-20.04"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -62,7 +92,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v2"
|
||||
uses: "actions/checkout@v3"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
@@ -71,7 +101,7 @@ jobs:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v1"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
|
||||
@@ -37,8 +37,7 @@ will have to run a composer installation in the project:
|
||||
```sh
|
||||
git clone git@github.com:doctrine/orm.git
|
||||
cd orm
|
||||
curl -sS https://getcomposer.org/installer | php --
|
||||
./composer.phar install
|
||||
composer install
|
||||
```
|
||||
|
||||
To run the testsuite against another database, copy the ``phpunit.xml.dist``
|
||||
|
||||
28
README.md
28
README.md
@@ -1,9 +1,11 @@
|
||||
| [3.0.x][3.0] | [2.12.x][2.12] | [2.11.x][2.11] |
|
||||
| [3.0.x][3.0] | [2.14.x][2.14] | [2.13.x][2.13] |
|
||||
|:----------------:|:----------------:|:----------:|
|
||||
| [![Build status][3.0 image]][3.0] | [![Build status][2.12 image]][2.12] | [![Build status][2.11 image]][2.11] |
|
||||
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.12 coverage image]][2.12 coverage] | [![Coverage Status][2.11 coverage image]][2.11 coverage] |
|
||||
| [![Build status][3.0 image]][3.0] | [![Build status][2.14 image]][2.14] | [![Build status][2.13 image]][2.13] |
|
||||
| [![Coverage Status][3.0 coverage image]][3.0 coverage]| [![Coverage Status][2.14 coverage image]][2.14 coverage] | [![Coverage Status][2.13 coverage image]][2.13 coverage] |
|
||||
|
||||
Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence
|
||||
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
|
||||
|
||||
Doctrine ORM is an object-relational mapper for PHP 7.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
|
||||
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
@@ -13,18 +15,18 @@ without requiring unnecessary code duplication.
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org)
|
||||
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
|
||||
|
||||
|
||||
[3.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0.x
|
||||
[3.0]: https://github.com/doctrine/orm/tree/3.0.x
|
||||
[3.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.0.x/graph/badge.svg
|
||||
[3.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.0.x
|
||||
[2.12 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.12.x
|
||||
[2.12]: https://github.com/doctrine/orm/tree/2.12.x
|
||||
[2.12 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.12.x/graph/badge.svg
|
||||
[2.12 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.12.x
|
||||
[2.11 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.11.x
|
||||
[2.11]: https://github.com/doctrine/orm/tree/2.11.x
|
||||
[2.11 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.11.x/graph/badge.svg
|
||||
[2.11 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.11.x
|
||||
[2.14 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.14.x
|
||||
[2.14]: https://github.com/doctrine/orm/tree/2.14.x
|
||||
[2.14 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.14.x/graph/badge.svg
|
||||
[2.14 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.14.x
|
||||
[2.13 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.13.x
|
||||
[2.13]: https://github.com/doctrine/orm/tree/2.13.x
|
||||
[2.13 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.13.x/graph/badge.svg
|
||||
[2.13 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.13.x
|
||||
|
||||
@@ -10,8 +10,8 @@ we cannot protect you from SQL injection.
|
||||
Please read the documentation chapter on Security in Doctrine DBAL and ORM to
|
||||
understand the assumptions we make.
|
||||
|
||||
- [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/security.html)
|
||||
- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/security.html)
|
||||
- [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html)
|
||||
- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html)
|
||||
|
||||
If you find a Security bug in Doctrine, please report it on Jira and change the
|
||||
Security Level to "Security Issues". It will be visible to Doctrine Core
|
||||
|
||||
318
UPGRADE.md
318
UPGRADE.md
@@ -1,3 +1,317 @@
|
||||
# Upgrade to 2.13
|
||||
|
||||
## Deprecated `QueryBuilder` methods and constants.
|
||||
|
||||
1. The `QueryBuilder::getState()` method has been deprecated as the builder state is an internal concern.
|
||||
2. Relying on the type of the query being built by using `QueryBuilder::getType()` has been deprecated.
|
||||
If necessary, track the type of the query being built outside of the builder.
|
||||
|
||||
The following `QueryBuilder` constants related to the above methods have been deprecated:
|
||||
|
||||
1. `SELECT`,
|
||||
2. `DELETE`,
|
||||
3. `UPDATE`,
|
||||
4. `STATE_DIRTY`,
|
||||
5. `STATE_CLEAN`.
|
||||
|
||||
## Deprecated omitting only the alias argument for `QueryBuilder::update` and `QueryBuilder::delete`
|
||||
|
||||
When building an UPDATE or DELETE query and when passing a class/type to the function, the alias argument must not be omitted.
|
||||
|
||||
### Before
|
||||
|
||||
```php
|
||||
$qb = $em->createQueryBuilder()
|
||||
->delete('User u')
|
||||
->where('u.id = :user_id')
|
||||
->setParameter('user_id', 1);
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
```php
|
||||
$qb = $em->createQueryBuilder()
|
||||
->delete('User', 'u')
|
||||
->where('u.id = :user_id')
|
||||
->setParameter('user_id', 1);
|
||||
```
|
||||
|
||||
## Deprecated using the `IDENTITY` identifier strategy on platform that do not support identity columns
|
||||
|
||||
If identity columns are emulated with sequences on the platform you are using,
|
||||
you should switch to the `SEQUENCE` strategy.
|
||||
|
||||
## Deprecated passing `null` to `Doctrine\ORM\Query::setFirstResult()`
|
||||
|
||||
`$query->setFirstResult(null);` is equivalent to `$query->setFirstResult(0)`.
|
||||
|
||||
## Deprecated calling setters without arguments
|
||||
|
||||
The following methods will require an argument in 3.0. Pass `null` instead of
|
||||
omitting the argument.
|
||||
|
||||
* `Doctrine\ORM\Event\OnClassMetadataNotFoundEventArgs::setFoundMetadata()`
|
||||
* `Doctrine\ORM\AbstractQuery::setHydrationCacheProfile()`
|
||||
* `Doctrine\ORM\AbstractQuery::setResultCache()`
|
||||
* `Doctrine\ORM\AbstractQuery::setResultCacheProfile()`
|
||||
|
||||
## Deprecated passing invalid fetch modes to `AbstractQuery::setFetchMode()`
|
||||
|
||||
Calling `AbstractQuery::setFetchMode()` with anything else than
|
||||
`Doctrine\ORM\Mapping::FETCH_EAGER` results in
|
||||
`Doctrine\ORM\Mapping::FETCH_LAZY` being used. Relying on that behavior is
|
||||
deprecated and will result in an exception in 3.0.
|
||||
|
||||
## Deprecated `getEntityManager()` in `Doctrine\ORM\Event\OnClearEventArgs` and `Doctrine\ORM\Event\*FlushEventArgs`
|
||||
|
||||
This method has been deprecated in:
|
||||
|
||||
* `Doctrine\ORM\Event\OnClearEventArgs`
|
||||
* `Doctrine\ORM\Event\OnFlushEventArgs`
|
||||
* `Doctrine\ORM\Event\PostFlushEventArgs`
|
||||
* `Doctrine\ORM\Event\PreFlushEventArgs`
|
||||
|
||||
It will be removed in 3.0. Use `getObjectManager()` instead.
|
||||
|
||||
## Prepare split of output walkers and tree walkers
|
||||
|
||||
In 3.0, `SqlWalker` and its child classes won't implement the `TreeWalker`
|
||||
interface anymore. Relying on that inheritance is deprecated.
|
||||
|
||||
The following methods of the `TreeWalker` interface have been deprecated:
|
||||
|
||||
* `setQueryComponent()`
|
||||
* `walkSelectClause()`
|
||||
* `walkFromClause()`
|
||||
* `walkFunction()`
|
||||
* `walkOrderByClause()`
|
||||
* `walkOrderByItem()`
|
||||
* `walkHavingClause()`
|
||||
* `walkJoin()`
|
||||
* `walkSelectExpression()`
|
||||
* `walkQuantifiedExpression()`
|
||||
* `walkSubselect()`
|
||||
* `walkSubselectFromClause()`
|
||||
* `walkSimpleSelectClause()`
|
||||
* `walkSimpleSelectExpression()`
|
||||
* `walkAggregateExpression()`
|
||||
* `walkGroupByClause()`
|
||||
* `walkGroupByItem()`
|
||||
* `walkDeleteClause()`
|
||||
* `walkUpdateClause()`
|
||||
* `walkUpdateItem()`
|
||||
* `walkWhereClause()`
|
||||
* `walkConditionalExpression()`
|
||||
* `walkConditionalTerm()`
|
||||
* `walkConditionalFactor()`
|
||||
* `walkConditionalPrimary()`
|
||||
* `walkExistsExpression()`
|
||||
* `walkCollectionMemberExpression()`
|
||||
* `walkEmptyCollectionComparisonExpression()`
|
||||
* `walkNullComparisonExpression()`
|
||||
* `walkInExpression()`
|
||||
* `walkInstanceOfExpression()`
|
||||
* `walkLiteral()`
|
||||
* `walkBetweenExpression()`
|
||||
* `walkLikeExpression()`
|
||||
* `walkStateFieldPathExpression()`
|
||||
* `walkComparisonExpression()`
|
||||
* `walkInputParameter()`
|
||||
* `walkArithmeticExpression()`
|
||||
* `walkArithmeticTerm()`
|
||||
* `walkStringPrimary()`
|
||||
* `walkArithmeticFactor()`
|
||||
* `walkSimpleArithmeticExpression()`
|
||||
* `walkPathExpression()`
|
||||
* `walkResultVariable()`
|
||||
* `getExecutor()`
|
||||
|
||||
The following changes have been made to the abstract `TreeWalkerAdapter` class:
|
||||
|
||||
* All implementations of now-deprecated `TreeWalker` methods have been
|
||||
deprecated as well.
|
||||
* The method `setQueryComponent()` will become protected in 3.0. Calling it
|
||||
publicly is deprecated.
|
||||
* The method `_getQueryComponents()` is deprecated, call `getQueryComponents()`
|
||||
instead.
|
||||
|
||||
On the `TreeWalkerChain` class, all implementations of now-deprecated
|
||||
`TreeWalker` methods have been deprecated as well. However, `SqlWalker` is
|
||||
unaffected by those deprecations and will continue to implement all of those
|
||||
methods.
|
||||
|
||||
## Deprecated passing `null` to `Doctrine\ORM\Query::setDQL()`
|
||||
|
||||
Doing `$query->setDQL(null);` achieves nothing.
|
||||
|
||||
## Deprecated omitting second argument to `NamingStrategy::joinColumnName`
|
||||
|
||||
When implementing `NamingStrategy`, it is deprecated to implement
|
||||
`joinColumnName()` with only one argument.
|
||||
|
||||
### Before
|
||||
|
||||
```php
|
||||
<?php
|
||||
class MyStrategy implements NamingStrategy
|
||||
{
|
||||
/**
|
||||
* @param string $propertyName A property name.
|
||||
*/
|
||||
public function joinColumnName($propertyName): string
|
||||
{
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### After
|
||||
|
||||
For backward-compatibility reasons, the parameter has to be optional, but can
|
||||
be documented as guaranteed to be a `class-string`.
|
||||
|
||||
```php
|
||||
<?php
|
||||
class MyStrategy implements NamingStrategy
|
||||
{
|
||||
/**
|
||||
* @param string $propertyName A property name.
|
||||
* @param class-string $className
|
||||
*/
|
||||
public function joinColumnName($propertyName, $className = null): string
|
||||
{
|
||||
// …
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Deprecated methods related to named queries
|
||||
|
||||
The following methods have been deprecated:
|
||||
|
||||
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryMapping()`
|
||||
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryResultClassMapping()`
|
||||
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativQueryResultSetMapping()`
|
||||
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativQueryEntityResultMapping()`
|
||||
|
||||
## Deprecated classes related to Doctrine 1 and reverse engineering
|
||||
|
||||
The following classes have been deprecated:
|
||||
|
||||
- `Doctrine\ORM\Tools\ConvertDoctrine1Schema`
|
||||
- `Doctrine\ORM\Tools\DisconnectedClassMetadataFactory`
|
||||
|
||||
## Deprecate `ClassMetadataInfo` usage
|
||||
|
||||
It is deprecated to pass `Doctrine\ORM\Mapping\ClassMetadataInfo` instances
|
||||
that are not also instances of `Doctrine\ORM\ClassMetadata` to the following
|
||||
methods:
|
||||
|
||||
- `Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder::__construct()`
|
||||
- `Doctrine\ORM\Mapping\Driver\DatabaseDriver::loadMetadataForClass()`
|
||||
- `Doctrine\ORM\Tools\SchemaValidator::validateClass()`
|
||||
|
||||
# Upgrade to 2.12
|
||||
|
||||
## Deprecated the `doctrine` binary.
|
||||
|
||||
The documentation explains how the console tools can be bootstrapped for
|
||||
standalone usage.
|
||||
|
||||
The method `ConsoleRunner::printCliConfigTemplate()` is deprecated because it
|
||||
was only useful in the context of the `doctrine` binary.
|
||||
|
||||
## Deprecate omitting `$class` argument to `ORMInvalidArgumentException::invalidIdentifierBindingEntity()`
|
||||
|
||||
To make it easier to identify understand the cause for that exception, it is
|
||||
deprecated to omit the class name when calling
|
||||
`ORMInvalidArgumentException::invalidIdentifierBindingEntity()`.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper`
|
||||
|
||||
Using a console helper to provide the ORM's console commands with one or
|
||||
multiple entity managers had been deprecated with 2.9 already. This leaves
|
||||
The `EntityManagerHelper` class with no purpose which is why it is now
|
||||
deprecated too. Applications that still rely on the `em` console helper, can
|
||||
easily recreate that class in their own codebase.
|
||||
|
||||
## Deprecate custom repository classes that don't extend `EntityRepository`
|
||||
|
||||
Although undocumented, it is currently possible to configure a custom repository
|
||||
class that implements `ObjectRepository` but does not extend the
|
||||
`EntityRepository` base class.
|
||||
|
||||
This is now deprecated. Please extend `EntityRepository` instead.
|
||||
|
||||
## Deprecated more APIs related to entity namespace aliases
|
||||
|
||||
```diff
|
||||
-$config = $entityManager->getConfiguration();
|
||||
-$config->addEntityNamespace('CMS', 'My\App\Cms');
|
||||
+use My\App\Cms\CmsUser;
|
||||
|
||||
-$entityManager->getRepository('CMS:CmsUser');
|
||||
+$entityManager->getRepository(CmsUser::class);
|
||||
```
|
||||
|
||||
## Deprecate `AttributeDriver::getReader()` and `AnnotationDriver::getReader()`
|
||||
|
||||
That method was inherited from the abstract `AnnotationDriver` class of
|
||||
`doctrine/persistence`, and does not seem to serve any purpose.
|
||||
|
||||
## Un-deprecate `Doctrine\ORM\Proxy\Proxy`
|
||||
|
||||
Because no forward-compatible new proxy solution had been implemented yet, the
|
||||
current proxy mechanism is not considered deprecated anymore for the time
|
||||
being. This applies to the following interfaces/classes:
|
||||
|
||||
* `Doctrine\ORM\Proxy\Proxy`
|
||||
* `Doctrine\ORM\Proxy\ProxyFactory`
|
||||
|
||||
These methods have been un-deprecated:
|
||||
|
||||
* `Doctrine\ORM\Configuration::getAutoGenerateProxyClasses()`
|
||||
* `Doctrine\ORM\Configuration::getProxyDir()`
|
||||
* `Doctrine\ORM\Configuration::getProxyNamespace()`
|
||||
|
||||
Note that the `Doctrine\ORM\Proxy\Autoloader` remains deprecated and will be removed in 3.0.
|
||||
|
||||
## Deprecate helper methods from `AbstractCollectionPersister`
|
||||
|
||||
The following protected methods of
|
||||
`Doctrine\ORM\Cache\Persister\Collection\AbstractCollectionPersister`
|
||||
are not in use anymore and will be removed.
|
||||
|
||||
* `evictCollectionCache()`
|
||||
* `evictElementCache()`
|
||||
|
||||
## Deprecate `Doctrine\ORM\Query\TreeWalkerChainIterator`
|
||||
|
||||
This class won't have a replacement.
|
||||
|
||||
## Deprecate `OnClearEventArgs::getEntityClass()` and `OnClearEventArgs::clearsAllEntities()`
|
||||
|
||||
These methods will be removed in 3.0 along with the ability to partially clear
|
||||
the entity manager.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Configuration::newDefaultAnnotationDriver`
|
||||
|
||||
This functionality has been moved to the new `ORMSetup` class. Call
|
||||
`Doctrine\ORM\ORMSetup::createDefaultAnnotationDriver()` to create
|
||||
a new annotation driver.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Tools\Setup`
|
||||
|
||||
In our effort to migrate from Doctrine Cache to PSR-6, the `Setup` class which
|
||||
accepted a Doctrine Cache instance in each method has been deprecated.
|
||||
|
||||
The replacement is `Doctrine\ORM\ORMSetup` which accepts a PSR-6
|
||||
cache instead.
|
||||
|
||||
## Deprecate `Doctrine\ORM\Cache\MultiGetRegion`
|
||||
|
||||
The interface will be merged with `Doctrine\ORM\Cache\Region` in 3.0.
|
||||
|
||||
# Upgrade to 2.11
|
||||
|
||||
## Rename `AbstractIdGenerator::generate()` to `generateId()`
|
||||
@@ -289,8 +603,8 @@ If you would still like to perform batching operations over small `UnitOfWork`
|
||||
instances, it is suggested to follow these paths instead:
|
||||
|
||||
* eagerly use `EntityManager#clear()` in conjunction with a specific second level
|
||||
cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
|
||||
* use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/change-tracking-policies.html)
|
||||
cache configuration (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/second-level-cache.html)
|
||||
* use an explicit change tracking policy (see http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/change-tracking-policies.html)
|
||||
|
||||
## Deprecated `YAML` mapping drivers.
|
||||
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<?php
|
||||
|
||||
fwrite(
|
||||
STDERR,
|
||||
'[Warning] The use of this script is discouraged. See'
|
||||
. ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console'
|
||||
. ' for instructions on bootstrapping the console runner.'
|
||||
. PHP_EOL
|
||||
);
|
||||
|
||||
echo PHP_EOL . PHP_EOL;
|
||||
|
||||
require_once 'Doctrine/Common/ClassLoader.php';
|
||||
|
||||
$classLoader = new \Doctrine\Common\ClassLoader('Doctrine');
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
fwrite(
|
||||
STDERR,
|
||||
'[Warning] The use of this script is discouraged. See'
|
||||
. ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console'
|
||||
. ' for instructions on bootstrapping the console runner.'
|
||||
. PHP_EOL
|
||||
);
|
||||
|
||||
echo PHP_EOL . PHP_EOL;
|
||||
|
||||
$autoloadFiles = [
|
||||
__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/../../../autoload.php'
|
||||
|
||||
38
ci/github/phpunit/sqlite3.xml
Normal file
38
ci/github/phpunit/sqlite3.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../../vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
beStrictAboutTodoAnnotatedTests="true"
|
||||
failOnRisky="true"
|
||||
convertDeprecationsToExceptions="true"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<!-- use an in-memory sqlite database -->
|
||||
<var name="db_driver" value="sqlite3"/>
|
||||
<var name="db_memory" value="true"/>
|
||||
|
||||
<!-- necessary change for some CLI/console output test assertions -->
|
||||
<env name="COLUMNS" value="120"/>
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Doctrine DBAL Test Suite">
|
||||
<directory>../../../tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">../../../lib/Doctrine</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -27,32 +27,34 @@
|
||||
"doctrine/collections": "^1.5",
|
||||
"doctrine/common": "^3.0.3",
|
||||
"doctrine/dbal": "^2.13.1 || ^3.2",
|
||||
"doctrine/deprecations": "^0.5.3",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"doctrine/event-manager": "^1.1",
|
||||
"doctrine/inflector": "^1.4 || ^2.0",
|
||||
"doctrine/instantiator": "^1.3",
|
||||
"doctrine/lexer": "^1.0",
|
||||
"doctrine/persistence": "^2.2",
|
||||
"doctrine/lexer": "^1.2.3",
|
||||
"doctrine/persistence": "^2.4 || ^3",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^3.0 || ^4.0 || ^5.0 || ^6.0",
|
||||
"symfony/polyfill-php72": "^1.23",
|
||||
"symfony/polyfill-php80": "^1.15"
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.13",
|
||||
"doctrine/coding-standard": "^9.0",
|
||||
"doctrine/coding-standard": "^9.0.2 || ^10.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/phpstan": "1.4.6",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.4",
|
||||
"squizlabs/php_codesniffer": "3.6.2",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.9.2",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"squizlabs/php_codesniffer": "3.7.1",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6.0",
|
||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
|
||||
"vimeo/psalm": "4.22.0"
|
||||
"vimeo/psalm": "4.30.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 2.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",
|
||||
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
|
||||
},
|
||||
|
||||
201
docs/en/conf.py
201
docs/en/conf.py
@@ -1,201 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Doctrine 2 ORM documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Dec 3 18:10:24 2010.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os, datetime
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.append(os.path.abspath('_exts'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['configurationblock']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Doctrine 2 ORM'
|
||||
copyright = u'2010-%y, Doctrine Project Team'.format(datetime.date.today)
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '2'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '2'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
language = 'en'
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of documents that shouldn't be included in the build.
|
||||
#unused_docs = []
|
||||
|
||||
# List of directories, relative to source directory, that shouldn't be searched
|
||||
# for source files.
|
||||
exclude_trees = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
show_authors = True
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
||||
html_theme = 'doctrine'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_theme']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_use_modindex = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = ''
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Doctrine2ORMdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
# The paper size ('letter' or 'a4').
|
||||
#latex_paper_size = 'letter'
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#latex_font_size = '10pt'
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation',
|
||||
u'Doctrine Project Team', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#latex_preamble = ''
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_use_modindex = True
|
||||
|
||||
primary_domain = "dcorm"
|
||||
|
||||
def linkcode_resolve(domain, info):
|
||||
if domain == 'dcorm':
|
||||
return 'http://'
|
||||
return None
|
||||
@@ -32,59 +32,39 @@ The entity class:
|
||||
|
||||
namespace Geo\Entity;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
use Geo\ValueObject\Point;
|
||||
|
||||
#[Entity]
|
||||
class Location
|
||||
{
|
||||
/**
|
||||
* @Column(type="point")
|
||||
*
|
||||
* @var \Geo\ValueObject\Point
|
||||
*/
|
||||
private $point;
|
||||
#[Column(type: 'point')]
|
||||
private Point $point;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $address;
|
||||
#[Column]
|
||||
private string $address;
|
||||
|
||||
/**
|
||||
* @param \Geo\ValueObject\Point $point
|
||||
*/
|
||||
public function setPoint(\Geo\ValueObject\Point $point)
|
||||
public function setPoint(Point $point): void
|
||||
{
|
||||
$this->point = $point;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Geo\ValueObject\Point
|
||||
*/
|
||||
public function getPoint()
|
||||
public function getPoint(): Point
|
||||
{
|
||||
return $this->point;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $address
|
||||
*/
|
||||
public function setAddress($address)
|
||||
public function setAddress(string $address): void
|
||||
{
|
||||
$this->address = $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress()
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
}
|
||||
|
||||
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
|
||||
We use the custom type ``point`` in the ``#[Column]`` attribute of the
|
||||
``$point`` field. We will create this custom mapping type in the next chapter.
|
||||
|
||||
The point class:
|
||||
@@ -97,29 +77,18 @@ The point class:
|
||||
|
||||
class Point
|
||||
{
|
||||
|
||||
/**
|
||||
* @param float $latitude
|
||||
* @param float $longitude
|
||||
*/
|
||||
public function __construct($latitude, $longitude)
|
||||
{
|
||||
$this->latitude = $latitude;
|
||||
$this->longitude = $longitude;
|
||||
public function __construct(
|
||||
private float $latitude,
|
||||
private float $longitude,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getLatitude()
|
||||
public function getLatitude(): float
|
||||
{
|
||||
return $this->latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getLongitude()
|
||||
public function getLongitude(): float
|
||||
{
|
||||
return $this->longitude;
|
||||
}
|
||||
|
||||
@@ -23,48 +23,32 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
|
||||
|
||||
namespace Test;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
|
||||
"cd" = "Test\Decorator\ConcreteDecorator"})
|
||||
*/
|
||||
#[Entity]
|
||||
#[InheritanceType('SINGLE_TABLE')]
|
||||
#[DiscriminatorColumn(name: 'discr', type: 'string')]
|
||||
#[DiscriminatorMap(['cc' => Component\ConcreteComponent::class,
|
||||
'cd' => Decorator\ConcreteDecorator::class])]
|
||||
abstract class Component
|
||||
{
|
||||
|
||||
/**
|
||||
* @Id @Column(type="integer")
|
||||
* @GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
#[Id, Column]
|
||||
#[GeneratedValue(strategy: 'AUTO')]
|
||||
protected int|null $id = null;
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* Get id
|
||||
* @return integer $id
|
||||
*/
|
||||
public function getId()
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set name
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
public function setName(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name
|
||||
* @return string $name
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
@@ -86,7 +70,7 @@ purpose of keeping this example simple).
|
||||
|
||||
use Test\Component;
|
||||
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class ConcreteComponent extends Component
|
||||
{}
|
||||
|
||||
@@ -103,14 +87,11 @@ use a ``MappedSuperclass`` for this.
|
||||
|
||||
namespace Test;
|
||||
|
||||
/** @MappedSuperclass */
|
||||
#[MappedSuperclass]
|
||||
abstract class Decorator extends Component
|
||||
{
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
|
||||
* @JoinColumn(name="decorates", referencedColumnName="id")
|
||||
*/
|
||||
#[OneToOne(targetEntity: Component::class, cascade: ['all'])]
|
||||
#[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
|
||||
protected $decorates;
|
||||
|
||||
/**
|
||||
@@ -126,25 +107,19 @@ use a ``MappedSuperclass`` for this.
|
||||
* (non-PHPdoc)
|
||||
* @see Test.Component::getName()
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Decorated ' . $this->getDecorates()->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* the component being decorated
|
||||
* @return Component
|
||||
*/
|
||||
protected function getDecorates()
|
||||
/** the component being decorated */
|
||||
protected function getDecorates(): Component
|
||||
{
|
||||
return $this->decorates;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the component being decorated
|
||||
* @param Component $c
|
||||
*/
|
||||
protected function setDecorates(Component $c)
|
||||
/** sets the component being decorated */
|
||||
protected function setDecorates(Component $c): void
|
||||
{
|
||||
$this->decorates = $c;
|
||||
}
|
||||
@@ -187,27 +162,19 @@ of the getSpecial() method to its return value.
|
||||
|
||||
use Test\Decorator;
|
||||
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class ConcreteDecorator extends Decorator
|
||||
{
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
protected $special;
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
protected string|null $special = null;
|
||||
|
||||
/**
|
||||
* Set special
|
||||
* @param string $special
|
||||
*/
|
||||
public function setSpecial($special)
|
||||
public function setSpecial(string|null $special): void
|
||||
{
|
||||
$this->special = $special;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get special
|
||||
* @return string $special
|
||||
*/
|
||||
public function getSpecial()
|
||||
public function getSpecial(): string|null
|
||||
{
|
||||
return $this->special;
|
||||
}
|
||||
@@ -216,7 +183,7 @@ of the getSpecial() method to its return value.
|
||||
* (non-PHPdoc)
|
||||
* @see Test.Component::getName()
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return '[' . $this->getSpecial()
|
||||
. '] ' . parent::getName();
|
||||
@@ -270,4 +237,3 @@ objects
|
||||
|
||||
echo $d->getName();
|
||||
// prints: [Really] Decorated Test Component 2
|
||||
|
||||
|
||||
@@ -131,8 +131,8 @@ generation of a DateDiff FunctionNode somewhere in the AST of the
|
||||
dql statement.
|
||||
|
||||
The ``ArithmeticPrimary`` method call is the most common
|
||||
denominator of valid EBNF tokens taken from the
|
||||
`DQL EBNF grammar <https://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#ebnf>`_
|
||||
denominator of valid EBNF tokens taken from the :ref:`DQL EBNF grammar
|
||||
<dql_ebnf_grammar>`
|
||||
that matches our requirements for valid input into the DateDiff Dql
|
||||
function. Picking the right tokens for your methods is a tricky
|
||||
business, but the EBNF grammar is pretty helpful finding it, as is
|
||||
|
||||
@@ -29,15 +29,15 @@ implement the ``NotifyPropertyChanged`` interface from the
|
||||
<?php
|
||||
use Doctrine\Persistence\NotifyPropertyChanged;
|
||||
use Doctrine\Persistence\PropertyChangedListener;
|
||||
|
||||
|
||||
abstract class DomainObject implements NotifyPropertyChanged
|
||||
{
|
||||
private $listeners = array();
|
||||
|
||||
|
||||
public function addPropertyChangedListener(PropertyChangedListener $listener) {
|
||||
$this->listeners[] = $listener;
|
||||
}
|
||||
|
||||
|
||||
/** Notifies listeners of a change. */
|
||||
protected function onPropertyChanged($propName, $oldValue, $newValue) {
|
||||
if ($this->listeners) {
|
||||
@@ -55,12 +55,12 @@ listeners:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Mapping not shown, either in annotations, xml or yaml as usual
|
||||
// Mapping not shown, either in attributes, annotations, xml or yaml as usual
|
||||
class MyEntity extends DomainObject
|
||||
{
|
||||
private $data;
|
||||
// ... other fields as usual
|
||||
|
||||
|
||||
public function setData($data) {
|
||||
if ($data != $this->data) { // check: is it actually modified?
|
||||
$this->onPropertyChanged('data', $this->data, $data);
|
||||
@@ -73,5 +73,3 @@ The check whether the new value is different from the old one is
|
||||
not mandatory but recommended. That way you can avoid unnecessary
|
||||
updates and also have full control over when you consider a
|
||||
property changed.
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ Implementing Wakeup or Clone
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
As explained in the
|
||||
`restrictions for entity classes in the manual <https://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
|
||||
As explained in the :ref:`restrictions for entity classes in the manual
|
||||
<terminology_entities>`,
|
||||
it is usually not allowed for an entity to implement ``__wakeup``
|
||||
or ``__clone``, because Doctrine makes special use of them.
|
||||
However, it is quite easy to make use of these methods in a safe
|
||||
|
||||
@@ -47,7 +47,7 @@ appropriate autoloaders.
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
|
||||
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) {
|
||||
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
|
||||
$mappedTableName = $mapping['joinTable']['name'];
|
||||
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
|
||||
}
|
||||
|
||||
@@ -87,12 +87,12 @@ Such an interface could look like this:
|
||||
* @return \Zend_View_Helper_Interface
|
||||
*/
|
||||
public function setView(\Zend_View_Interface $view);
|
||||
|
||||
|
||||
/**
|
||||
* @return \Zend_View_Interface
|
||||
*/
|
||||
public function getView();
|
||||
|
||||
|
||||
/**
|
||||
* Renders this strategy. This method will be called when the user
|
||||
* displays the site.
|
||||
@@ -100,7 +100,7 @@ Such an interface could look like this:
|
||||
* @return string
|
||||
*/
|
||||
public function renderFrontend();
|
||||
|
||||
|
||||
/**
|
||||
* Renders the backend of this block. This method will be called when
|
||||
* a user tries to reconfigure this block instance.
|
||||
@@ -118,21 +118,21 @@ Such an interface could look like this:
|
||||
* @return array
|
||||
*/
|
||||
public function getRequiredPanelTypes();
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether a Block is able to use a given type or not
|
||||
* @param string $typeName The typename
|
||||
* @return boolean
|
||||
*/
|
||||
public function canUsePanelType($typeName);
|
||||
|
||||
|
||||
public function setBlockEntity(AbstractBlock $block);
|
||||
|
||||
public function getBlockEntity();
|
||||
}
|
||||
|
||||
|
||||
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
@@ -154,8 +154,8 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
|
||||
* This var contains the classname of the strategy
|
||||
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine ORM)
|
||||
*
|
||||
* This is a doctrine field, so make sure that you use an @column annotation or setup your
|
||||
* yaml or xml files correctly
|
||||
* This is a doctrine field, so make sure that you use a
|
||||
#[Column] attribute or setup your yaml or xml files correctly
|
||||
* @var string
|
||||
*/
|
||||
protected $strategyClassName;
|
||||
@@ -177,7 +177,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
|
||||
public function getStrategyClassName() {
|
||||
return $this->strategyClassName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the instantiated strategy
|
||||
*
|
||||
@@ -186,7 +186,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
|
||||
public function getStrategyInstance() {
|
||||
return $this->strategyInstance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the strategy this block / panel should work as. Make sure that you've used
|
||||
* this method before persisting the block!
|
||||
@@ -213,28 +213,29 @@ This might look like this:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use \Doctrine\ORM,
|
||||
\Doctrine\Common;
|
||||
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
/**
|
||||
* The BlockStrategyEventListener will initialize a strategy after the
|
||||
* block itself was loaded.
|
||||
*/
|
||||
class BlockStrategyEventListener implements Common\EventSubscriber {
|
||||
|
||||
class BlockStrategyEventListener implements EventSubscriber {
|
||||
|
||||
protected $view;
|
||||
|
||||
|
||||
public function __construct(\Zend_View_Interface $view) {
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
|
||||
public function getSubscribedEvents() {
|
||||
return array(ORM\Events::postLoad);
|
||||
return array(Events::postLoad);
|
||||
}
|
||||
|
||||
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
|
||||
$blockItem = $args->getEntity();
|
||||
|
||||
|
||||
public function postLoad(LifecycleEventArgs $args) {
|
||||
$blockItem = $args->getObject();
|
||||
|
||||
// Both blocks and panels are instances of Block\AbstractBlock
|
||||
if ($blockItem instanceof Block\AbstractBlock) {
|
||||
$strategy = $blockItem->getStrategyClassName();
|
||||
@@ -250,5 +251,3 @@ This might look like this:
|
||||
|
||||
In this example, even some variables are set - like a view object
|
||||
or a specific configuration object.
|
||||
|
||||
|
||||
|
||||
@@ -36,12 +36,12 @@ are allowed to:
|
||||
public function assertCustomerAllowedBuying()
|
||||
{
|
||||
$orderLimit = $this->customer->getOrderLimit();
|
||||
|
||||
|
||||
$amount = 0;
|
||||
foreach ($this->orderLines as $line) {
|
||||
$amount += $line->getAmount();
|
||||
}
|
||||
|
||||
|
||||
if ($amount > $orderLimit) {
|
||||
throw new CustomerOrderLimitExceededException();
|
||||
}
|
||||
@@ -53,7 +53,21 @@ code, enforcing it at any time is important so that customers with
|
||||
a unknown reputation don't owe your business too much money.
|
||||
|
||||
We can enforce this constraint in any of the metadata drivers.
|
||||
First Annotations:
|
||||
First Attributes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
#[Entity]
|
||||
#[HasLifecycleCallbacks]
|
||||
class Order
|
||||
{
|
||||
#[PrePersist, PreUpdate]
|
||||
public function assertCustomerAllowedBuying() {}
|
||||
}
|
||||
|
||||
As Annotations:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -83,9 +97,6 @@ In XML Mappings:
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
YAML needs some little change yet, to allow multiple lifecycle
|
||||
events for one method, this will happen before Beta 1 though.
|
||||
|
||||
Now validation is performed whenever you call
|
||||
``EntityManager#persist($order)`` or when you call
|
||||
``EntityManager#flush()`` and an order is about to be updated. Any
|
||||
@@ -101,19 +112,17 @@ validation callbacks.
|
||||
<?php
|
||||
class Order
|
||||
{
|
||||
/**
|
||||
* @PrePersist @PreUpdate
|
||||
*/
|
||||
#[PrePersist, PreUpdate]
|
||||
public function validate()
|
||||
{
|
||||
if (!($this->plannedShipDate instanceof DateTime)) {
|
||||
throw new ValidateException();
|
||||
}
|
||||
|
||||
|
||||
if ($this->plannedShipDate->format('U') < time()) {
|
||||
throw new ValidateException();
|
||||
}
|
||||
|
||||
|
||||
if ($this->customer == null) {
|
||||
throw new OrderRequiresCustomerException();
|
||||
}
|
||||
|
||||
@@ -15,13 +15,16 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
|
||||
use DateTime;
|
||||
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
/** @Column(type="datetime") */
|
||||
private $updated;
|
||||
#[Column(type='datetime')]
|
||||
private DateTime $updated;
|
||||
|
||||
public function setUpdated()
|
||||
public function setUpdated(): void
|
||||
{
|
||||
// will NOT be saved in the database
|
||||
$this->updated->modify("now");
|
||||
@@ -33,12 +36,14 @@ The way to go would be:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use DateTime;
|
||||
|
||||
class Article
|
||||
{
|
||||
public function setUpdated()
|
||||
public function setUpdated(): void
|
||||
{
|
||||
// WILL be saved in the database
|
||||
$this->updated = new \DateTime("now");
|
||||
$this->updated = new DateTime("now");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,16 +89,14 @@ the UTC time at the time of the booking and the timezone the event happened in.
|
||||
|
||||
namespace DoctrineExtensions\DBAL\Types;
|
||||
|
||||
use DateTimeZone;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\DateTimeType;
|
||||
|
||||
class UTCDateTimeType extends DateTimeType
|
||||
{
|
||||
/**
|
||||
* @var \DateTimeZone
|
||||
*/
|
||||
private static $utc;
|
||||
private static DateTimeZone $utc;
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
@@ -126,10 +129,10 @@ the UTC time at the time of the booking and the timezone the event happened in.
|
||||
|
||||
return $converted;
|
||||
}
|
||||
|
||||
private static function getUtc(): \DateTimeZone
|
||||
|
||||
private static function getUtc(): DateTimeZone
|
||||
{
|
||||
return self::$utc ?: self::$utc = new \DateTimeZone('UTC');
|
||||
return self::$utc ??= new DateTimeZone('UTC');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ steps of configuration.
|
||||
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
|
||||
|
||||
@@ -27,7 +29,7 @@ steps of configuration.
|
||||
|
||||
$config = new Configuration;
|
||||
$config->setMetadataCache($metadataCache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCache($queryCache);
|
||||
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
|
||||
@@ -118,21 +120,22 @@ There are currently 5 available implementations:
|
||||
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
|
||||
|
||||
Throughout the most part of this manual the AnnotationDriver is
|
||||
used in the examples. For information on the usage of the XmlDriver
|
||||
or YamlDriver please refer to the dedicated chapters
|
||||
``XML Mapping`` and ``YAML Mapping``.
|
||||
Throughout the most part of this manual the AttributeDriver is
|
||||
used in the examples. For information on the usage of the
|
||||
AnnotationDriver, XmlDriver or YamlDriver please refer to the dedicated
|
||||
chapters ``Annotation Reference``, ``XML Mapping`` and ``YAML Mapping``.
|
||||
|
||||
The annotation driver can be configured with a factory method on
|
||||
the ``Doctrine\ORM\Configuration``:
|
||||
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
The path information to the entities is required for the annotation
|
||||
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
|
||||
@@ -149,7 +152,7 @@ Metadata Cache (***RECOMMENDED***)
|
||||
$config->getMetadataCache();
|
||||
|
||||
Gets or sets the cache adapter to use for caching metadata
|
||||
information, that is, all the information you supply via
|
||||
information, that is, all the information you supply via attributes,
|
||||
annotations, xml or yaml, so that they do not need to be parsed and
|
||||
loaded from scratch on every single request which is a waste of
|
||||
resources. The cache implementation must implement the PSR-6
|
||||
@@ -401,15 +404,15 @@ Multiple Metadata Sources
|
||||
|
||||
When using different components using Doctrine ORM you may end up
|
||||
with them using two different metadata drivers, for example XML and
|
||||
YAML. You can use the DriverChain Metadata implementations to
|
||||
YAML. You can use the MappingDriverChain Metadata implementations to
|
||||
aggregate these drivers based on namespaces:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Driver\DriverChain;
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
|
||||
|
||||
$chain = new DriverChain();
|
||||
$chain = new MappingDriverChain();
|
||||
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
|
||||
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
|
||||
|
||||
@@ -454,18 +457,5 @@ Setting up the Console
|
||||
----------------------
|
||||
|
||||
Doctrine uses the Symfony Console component for generating the command
|
||||
line interface. You can take a look at the ``vendor/bin/doctrine.php``
|
||||
script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command
|
||||
for inspiration how to setup the cli.
|
||||
|
||||
In general the required code looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
|
||||
$cli->setCatchExceptions(true);
|
||||
$cli->setHelperSet($helperSet);
|
||||
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
|
||||
$cli->run();
|
||||
|
||||
line interface. You can take a look at the
|
||||
:doc:`tools chapter <../reference/tools>` for inspiration how to setup the cli.
|
||||
|
||||
@@ -66,6 +66,8 @@ The root namespace of the ORM package is ``Doctrine\ORM``.
|
||||
Terminology
|
||||
-----------
|
||||
|
||||
.. _terminology_entities:
|
||||
|
||||
Entities
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
@@ -37,7 +37,26 @@ A many-to-one association is the most common association between objects. Exampl
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
#[ManyToOne(targetEntity: Address::class)]
|
||||
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
|
||||
private Address|null $address = null;
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
@@ -49,7 +68,7 @@ A many-to-one association is the most common association between objects. Exampl
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
*/
|
||||
private $address;
|
||||
private Address|null $address = null;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
@@ -82,9 +101,11 @@ A many-to-one association is the most common association between objects. Exampl
|
||||
|
||||
.. note::
|
||||
|
||||
The above ``@JoinColumn`` is optional as it would default
|
||||
The above ``#[JoinColumn]`` is optional as it would default
|
||||
to ``address_id`` and ``id`` anyways. You can omit it and let it
|
||||
use the defaults.
|
||||
Likewise, inside the ``#[ManyToOne]`` attribute you can omit the
|
||||
``targetEntity`` argument and it will default to ``Address``.
|
||||
|
||||
Generated MySQL Schema:
|
||||
|
||||
@@ -111,7 +132,29 @@ references one ``Shipment`` entity.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
|
||||
private Shipment|null $shipment = null;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Shipment
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
@@ -124,7 +167,7 @@ references one ``Shipment`` entity.
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private $shipment;
|
||||
private Shipment|null $shipment = null;
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -156,7 +199,7 @@ references one ``Shipment`` entity.
|
||||
name: shipment_id
|
||||
referencedColumnName: id
|
||||
|
||||
Note that the @JoinColumn is not really necessary in this example,
|
||||
Note that the ``#[JoinColumn]`` is not really necessary in this example,
|
||||
as the defaults would be the same.
|
||||
|
||||
Generated MySQL Schema:
|
||||
@@ -188,7 +231,35 @@ object.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class Customer
|
||||
{
|
||||
// ...
|
||||
|
||||
/** One Customer has One Cart. */
|
||||
#[OneToOne(targetEntity: Cart::class, mappedBy: 'customer')]
|
||||
private Cart|null $cart = null;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Cart
|
||||
{
|
||||
// ...
|
||||
|
||||
/** One Cart has One Customer. */
|
||||
#[OneToOne(targetEntity: Customer::class, inversedBy: 'cart')]
|
||||
#[JoinColumn(name: 'customer_id', referencedColumnName: 'id')]
|
||||
private Customer|null $customer = null;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
@@ -200,7 +271,7 @@ object.
|
||||
* One Customer has One Cart.
|
||||
* @OneToOne(targetEntity="Cart", mappedBy="customer")
|
||||
*/
|
||||
private $cart;
|
||||
private Cart|null $cart = null;
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -215,7 +286,7 @@ object.
|
||||
* @OneToOne(targetEntity="Customer", inversedBy="cart")
|
||||
* @JoinColumn(name="customer_id", referencedColumnName="id")
|
||||
*/
|
||||
private $customer;
|
||||
private Customer|null $customer = null;
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -281,17 +352,15 @@ below.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class Student
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Student has One Mentor.
|
||||
* @OneToOne(targetEntity="Student")
|
||||
* @JoinColumn(name="mentor_id", referencedColumnName="id")
|
||||
*/
|
||||
private $mentor;
|
||||
/** One Student has One Mentor. */
|
||||
#[OneToOne(targetEntity: Student::class)]
|
||||
#[JoinColumn(name: 'mentor_id', referencedColumnName: 'id')]
|
||||
private Student|null $mentor = null;
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -326,7 +395,40 @@ bidirectional many-to-one.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
#[Entity]
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* One product has many features. This is the inverse side.
|
||||
* @var Collection<int, Feature>
|
||||
*/
|
||||
#[OneToMany(targetEntity: Feature::class, mappedBy: 'product')]
|
||||
private Collection $features;
|
||||
// ...
|
||||
|
||||
public function __construct() {
|
||||
$this->features = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Feature
|
||||
{
|
||||
// ...
|
||||
/** Many features have one product. This is the owning side. */
|
||||
#[ManyToOne(targetEntity: Product::class, inversedBy: 'features')]
|
||||
#[JoinColumn(name: 'product_id', referencedColumnName: 'id')]
|
||||
private Product|null $product = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
@@ -337,9 +439,10 @@ bidirectional many-to-one.
|
||||
// ...
|
||||
/**
|
||||
* One product has many features. This is the inverse side.
|
||||
* @var Collection<int, Feature>
|
||||
* @OneToMany(targetEntity="Feature", mappedBy="product")
|
||||
*/
|
||||
private $features;
|
||||
private Collection $features;
|
||||
// ...
|
||||
|
||||
public function __construct() {
|
||||
@@ -356,7 +459,7 @@ bidirectional many-to-one.
|
||||
* @ManyToOne(targetEntity="Product", inversedBy="features")
|
||||
* @JoinColumn(name="product_id", referencedColumnName="id")
|
||||
*/
|
||||
private $product;
|
||||
private Product|null $product = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -421,7 +524,39 @@ The following example sets up such a unidirectional one-to-many association:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Phonenumbers.
|
||||
* @var Collection<int, Phonenumber>
|
||||
*/
|
||||
#[JoinTable(name: 'users_phonenumbers')]
|
||||
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
|
||||
#[InverseJoinColumn(name: 'phonenumber_id', referencedColumnName: 'id', unique: true)]
|
||||
#[ManyToMany(targetEntity: 'Phonenumber')]
|
||||
private Collection $phonenumbers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->phonenumbers = new ArrayCollection();
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Phonenumber
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
@@ -436,8 +571,9 @@ The following example sets up such a unidirectional one-to-many association:
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
|
||||
* )
|
||||
* @var Collection<int, Phonenumber>
|
||||
*/
|
||||
private $phonenumbers;
|
||||
private Collection $phonenumbers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -523,7 +659,32 @@ database perspective is known as an adjacency list approach.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class Category
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* One Category has Many Categories.
|
||||
* @var Collection<int, Category>
|
||||
*/
|
||||
#[OneToMany(targetEntity: Category::class, mappedBy: 'parent')]
|
||||
private Collection $children;
|
||||
|
||||
/** Many Categories have One Category. */
|
||||
#[ManyToOne(targetEntity: Category::class, inversedBy: 'children')]
|
||||
#[JoinColumn(name: 'parent_id', referencedColumnName: 'id')]
|
||||
private Category|null $parent = null;
|
||||
// ...
|
||||
|
||||
public function __construct() {
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
@@ -533,15 +694,16 @@ database perspective is known as an adjacency list approach.
|
||||
/**
|
||||
* One Category has Many Categories.
|
||||
* @OneToMany(targetEntity="Category", mappedBy="parent")
|
||||
* @var Collection<int, Category>
|
||||
*/
|
||||
private $children;
|
||||
private Collection $children;
|
||||
|
||||
/**
|
||||
* Many Categories have One Category.
|
||||
* @ManyToOne(targetEntity="Category", inversedBy="children")
|
||||
* @JoinColumn(name="parent_id", referencedColumnName="id")
|
||||
*/
|
||||
private $parent;
|
||||
private Category|null $parent = null;
|
||||
// ...
|
||||
|
||||
public function __construct() {
|
||||
@@ -594,7 +756,38 @@ entities:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
#[JoinTable(name: 'users_groups')]
|
||||
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
|
||||
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
|
||||
#[ManyToMany(targetEntity: Group::class)]
|
||||
private Collection $groups;
|
||||
|
||||
// ...
|
||||
|
||||
public function __construct() {
|
||||
$this->groups = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Group
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
@@ -609,8 +802,9 @@ entities:
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
|
||||
* )
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
private $groups;
|
||||
private Collection $groups;
|
||||
|
||||
// ...
|
||||
|
||||
@@ -695,7 +889,48 @@ one is bidirectional.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
#[ManyToMany(targetEntity: Group::class, inversedBy: 'users')]
|
||||
#[JoinTable(name: 'users_groups')]
|
||||
private Collection $groups;
|
||||
|
||||
public function __construct() {
|
||||
$this->groups = new ArrayCollection();
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Group
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many Groups have Many Users.
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
#[ManyToMany(targetEntity: User::class, mappedBy: 'groups')]
|
||||
private Collection $users;
|
||||
|
||||
public function __construct() {
|
||||
$this->users = new ArrayCollection();
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
@@ -707,8 +942,9 @@ one is bidirectional.
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group", inversedBy="users")
|
||||
* @JoinTable(name="users_groups")
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
private $groups;
|
||||
private Collection $groups;
|
||||
|
||||
public function __construct() {
|
||||
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
@@ -724,8 +960,9 @@ one is bidirectional.
|
||||
/**
|
||||
* Many Groups have Many Users.
|
||||
* @ManyToMany(targetEntity="User", mappedBy="groups")
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
private $users;
|
||||
private Collection $users;
|
||||
|
||||
public function __construct() {
|
||||
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
@@ -806,9 +1043,9 @@ understandable:
|
||||
<?php
|
||||
class Article
|
||||
{
|
||||
private $tags;
|
||||
private Collection $tags;
|
||||
|
||||
public function addTag(Tag $tag)
|
||||
public function addTag(Tag $tag): void
|
||||
{
|
||||
$tag->addArticle($this); // synchronously updating inverse side
|
||||
$this->tags[] = $tag;
|
||||
@@ -817,9 +1054,9 @@ understandable:
|
||||
|
||||
class Tag
|
||||
{
|
||||
private $articles;
|
||||
private Collection $articles;
|
||||
|
||||
public function addArticle(Article $article)
|
||||
public function addArticle(Article $article): void
|
||||
{
|
||||
$this->articles[] = $article;
|
||||
}
|
||||
@@ -847,30 +1084,31 @@ field named ``$friendsWithMe`` and ``$myFriends``.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Users.
|
||||
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
private $friendsWithMe;
|
||||
#[ManyToMany(targetEntity: User::class, mappedBy: 'myFriends')]
|
||||
private Collection $friendsWithMe;
|
||||
|
||||
/**
|
||||
* Many Users have many Users.
|
||||
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
|
||||
* @JoinTable(name="friends",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
|
||||
* )
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
private $myFriends;
|
||||
#[JoinTable(name: 'friends')]
|
||||
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
|
||||
#[InverseJoinColumn(name: 'friend_user_id', referencedColumnName: 'id')]
|
||||
#[ManyToMany(targetEntity: 'User', inversedBy: 'friendsWithMe')]
|
||||
private Collection $myFriends;
|
||||
|
||||
public function __construct() {
|
||||
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
$this->friendsWithMe = new ArrayCollection();
|
||||
$this->myFriends = new ArrayCollection();
|
||||
}
|
||||
|
||||
// ...
|
||||
@@ -910,11 +1148,17 @@ As an example, consider this mapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
private Shipment|null $shipment = null;
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @OneToOne(targetEntity="Shipment") */
|
||||
private $shipment;
|
||||
private Shipment|null $shipment = null;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
@@ -937,7 +1181,15 @@ mapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id')]
|
||||
private Shipment|null $shipment = null;
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
@@ -945,7 +1197,7 @@ mapping:
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private $shipment;
|
||||
private Shipment|null $shipment = null;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
@@ -973,14 +1225,29 @@ similar defaults. As an example, consider this mapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @ManyToMany(targetEntity="Group") */
|
||||
private $groups;
|
||||
/** @var Collection<int, Group> */
|
||||
#[ManyToMany(targetEntity: Group::class)]
|
||||
private Collection $groups;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
private Collection $groups;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -1004,7 +1271,25 @@ This is essentially the same as the following, more verbose, mapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
#[JoinTable(name: 'User_Group')]
|
||||
#[JoinColumn(name: 'User_id', referencedColumnName: 'id')]
|
||||
#[InverseJoinColumn(name: 'Group_id', referencedColumnName: 'id')]
|
||||
#[ManyToMany(targetEntity: Group::class)]
|
||||
private Collection $groups;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
class User
|
||||
@@ -1017,8 +1302,9 @@ This is essentially the same as the following, more verbose, mapping:
|
||||
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
|
||||
* )
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
private $groups;
|
||||
private Collection $groups;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -1069,7 +1355,13 @@ attribute on ``JoinColumn`` will be inherited from PHP type. So that:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[OneToOne]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @OneToOne */
|
||||
@@ -1094,7 +1386,7 @@ Is essentially the same as following:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
@@ -1104,6 +1396,14 @@ Is essentially the same as following:
|
||||
*/
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
/** One Product has One Shipment. */
|
||||
#[OneToOne(targetEntity: Shipment::class)]
|
||||
#[JoinColumn(name: 'shipment_id', referencedColumnName: 'id', nullable: false)]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
@@ -1163,22 +1463,19 @@ and ``@ManyToMany`` associations in the constructor of your entities:
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @var Collection
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
*/
|
||||
private $groups;
|
||||
/** Many Users have Many Groups. */
|
||||
#[ManyToMany(targetEntity: Group::class)]
|
||||
private Collection $groups;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->groups = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getGroups()
|
||||
public function getGroups(): Collection
|
||||
{
|
||||
return $this->groups;
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ annotation metadata supported since the first version 2.0.
|
||||
Index
|
||||
-----
|
||||
|
||||
- :ref:`#[AssociationOverride] <attrref_associationoverride]`
|
||||
- :ref:`#[AttributeOverride] <attrref_attributeoverride]`
|
||||
- :ref:`#[AssociationOverride] <attrref_associationoverride>`
|
||||
- :ref:`#[AttributeOverride] <attrref_attributeoverride>`
|
||||
- :ref:`#[Column] <attrref_column>`
|
||||
- :ref:`#[Cache] <attrref_cache>`
|
||||
- :ref:`#[ChangeTrackingPolicy <attrref_changetrackingpolicy>`
|
||||
@@ -27,7 +27,6 @@ Index
|
||||
- :ref:`#[Id] <attrref_id>`
|
||||
- :ref:`#[InheritanceType] <attrref_inheritancetype>`
|
||||
- :ref:`#[JoinColumn] <attrref_joincolumn>`
|
||||
- :ref:`#[JoinColumns] <attrref_joincolumns>`
|
||||
- :ref:`#[JoinTable] <attrref_jointable>`
|
||||
- :ref:`#[ManyToOne] <attrref_manytoone>`
|
||||
- :ref:`#[ManyToMany] <attrref_manytomany>`
|
||||
@@ -179,7 +178,7 @@ Optional parameters:
|
||||
If not specified, default value is ``false``.
|
||||
|
||||
- **insertable**: Boolean value to determine if the column should be
|
||||
included when inserting a new row into the underlying entities table.
|
||||
included when inserting a new row into the underlying entities table.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **updatable**: Boolean value to determine if the column should be
|
||||
@@ -207,6 +206,8 @@ Optional parameters:
|
||||
- ``comment``: The comment of the column in the schema (might not
|
||||
be supported by all vendors).
|
||||
|
||||
- ``charset``: The charset of the column (only supported by Mysql, PostgreSQL, Sqlite and SQLServer).
|
||||
|
||||
- ``collation``: The collation of the column (only supported by Mysql, PostgreSQL, Sqlite and SQLServer).
|
||||
|
||||
- ``check``: Adds a check constraint type to the column (might not
|
||||
@@ -681,6 +682,8 @@ Optional parameters:
|
||||
"columnDefinition" attribute on :ref:`#[Column] <attrref_column>` also sets
|
||||
the related ``#[JoinColumn]``'s columnDefinition. This is necessary to
|
||||
make foreign keys work.
|
||||
- **options**:
|
||||
See "options" attribute on :ref:`#[Column] <attrref_column>`.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -50,8 +50,9 @@ mapping metadata:
|
||||
- :doc:`PHP code <php-mapping>`
|
||||
- :doc:`YAML <yaml-mapping>` (deprecated and will be removed in ``doctrine/orm`` 3.0.)
|
||||
|
||||
This manual will usually show mapping metadata via docblock annotations, though
|
||||
many examples also show the equivalent configuration in YAML and XML.
|
||||
This manual will usually show mapping metadata via attributes, though
|
||||
many examples also show the equivalent configuration in annotations,
|
||||
YAML and XML.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -157,10 +158,10 @@ Property Mapping
|
||||
|
||||
The next step is mapping its properties to columns in the table.
|
||||
|
||||
To configure a property use the ``Column`` docblock annotation. The ``type``
|
||||
attribute specifies the :ref:`Doctrine Mapping Type <reference-mapping-types>`
|
||||
to use for the field. If the type is not specified, ``string`` is used as the
|
||||
default.
|
||||
To configure a property use the ``Column`` attribute. The ``type``
|
||||
argument specifies the :ref:`Doctrine Mapping Type
|
||||
<reference-mapping-types>` to use for the field. If the type is not
|
||||
specified, ``string`` is used as the default.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -360,12 +361,23 @@ Identifiers / Primary Keys
|
||||
--------------------------
|
||||
|
||||
Every entity class must have an identifier/primary key. You can select
|
||||
the field that serves as the identifier with the ``@Id``
|
||||
annotation.
|
||||
the field that serves as the identifier with the ``#[Id]`` attribute.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class Message
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'integer')]
|
||||
#[GeneratedValue]
|
||||
private int|null $id = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
class Message
|
||||
@@ -375,7 +387,7 @@ annotation.
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
private int|null $id = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -402,7 +414,7 @@ annotation.
|
||||
fields:
|
||||
# fields here
|
||||
|
||||
In most cases using the automatic generator strategy (``@GeneratedValue``) is
|
||||
In most cases using the automatic generator strategy (``#[GeneratedValue]``) is
|
||||
what you want. It defaults to the identifier generation mechanism your current
|
||||
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
|
||||
and Oracle and so on.
|
||||
@@ -438,8 +450,8 @@ Here is the list of possible generation strategies:
|
||||
- ``NONE``: Tells Doctrine that the identifiers are assigned (and
|
||||
thus generated) by your code. The assignment must take place before
|
||||
a new entity is passed to ``EntityManager#persist``. NONE is the
|
||||
same as leaving off the @GeneratedValue entirely.
|
||||
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
|
||||
same as leaving off the ``#[GeneratedValue]`` entirely.
|
||||
- ``CUSTOM``: With this option, you can use the ``#[CustomIdGenerator]`` attribute.
|
||||
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
|
||||
|
||||
Sequence Generator
|
||||
@@ -451,7 +463,19 @@ besides specifying the sequence's name:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class Message
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue(strategy: 'SEQUENCE')]
|
||||
#[SequenceGenerator(sequenceName: 'message_seq', initialValue: 1, allocationSize: 100)]
|
||||
protected int|null $id = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
class Message
|
||||
@@ -461,7 +485,7 @@ besides specifying the sequence's name:
|
||||
* @GeneratedValue(strategy="SEQUENCE")
|
||||
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
|
||||
*/
|
||||
protected $id = null;
|
||||
protected int|null $id = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -526,11 +550,12 @@ need to access the sequence once to generate the identifiers for
|
||||
Composite Keys
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
With Doctrine ORM you can use composite primary keys, using ``@Id`` on more then
|
||||
one column. Some restrictions exist opposed to using a single identifier in
|
||||
this case: The use of the ``@GeneratedValue`` annotation is not supported,
|
||||
which means you can only use composite keys if you generate the primary key
|
||||
values yourself before calling ``EntityManager#persist()`` on the entity.
|
||||
With Doctrine ORM you can use composite primary keys, using ``#[Id]`` on
|
||||
more than one column. Some restrictions exist opposed to using a single
|
||||
identifier in this case: The use of the ``#[GeneratedValue]`` attribute
|
||||
is not supported, which means you can only use composite keys if you
|
||||
generate the primary key values yourself before calling
|
||||
``EntityManager#persist()`` on the entity.
|
||||
|
||||
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
|
||||
<../tutorials/composite-primary-keys>`.
|
||||
@@ -546,7 +571,8 @@ needs to be done explicitly using ticks in the definition.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Column(name="`number`", type="integer") */
|
||||
|
||||
#[Column(name: '`number`', type: 'integer')]
|
||||
private $number;
|
||||
|
||||
Doctrine will then quote this column name in all SQL statements
|
||||
|
||||
@@ -74,11 +74,13 @@ collections in entities in the constructor. Example:
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
|
||||
class User {
|
||||
private $addresses;
|
||||
private $articles;
|
||||
|
||||
/** @var Collection<int, Address> */
|
||||
private Collection $addresses;
|
||||
/** @var Collection<int, Article> */
|
||||
private Collection $articles;
|
||||
|
||||
public function __construct() {
|
||||
$this->addresses = new ArrayCollection;
|
||||
$this->articles = new ArrayCollection;
|
||||
|
||||
@@ -109,8 +109,9 @@ Metadata Cache
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Your class metadata can be parsed from a few different sources like
|
||||
YAML, XML, Annotations, etc. Instead of parsing this information on
|
||||
each request we should cache it using one of the cache drivers.
|
||||
YAML, XML, Attributes, Annotations etc. Instead of parsing this
|
||||
information on each request we should cache it using one of the cache
|
||||
drivers.
|
||||
|
||||
Just like the query and result cache we need to configure it
|
||||
first.
|
||||
@@ -199,5 +200,3 @@ not letting your users' requests populate the cache.
|
||||
|
||||
You can read more about cache slams
|
||||
`in this blog post <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.
|
||||
|
||||
|
||||
|
||||
@@ -49,10 +49,9 @@ This policy can be configured as follows:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
|
||||
*/
|
||||
|
||||
#[Entity]
|
||||
#[ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
@@ -78,18 +77,16 @@ follows:
|
||||
<?php
|
||||
use Doctrine\Persistence\NotifyPropertyChanged,
|
||||
Doctrine\Persistence\PropertyChangedListener;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("NOTIFY")
|
||||
*/
|
||||
|
||||
#[Entity]
|
||||
#[ChangeTrackingPolicy('NOTIFY')]
|
||||
class MyEntity implements NotifyPropertyChanged
|
||||
{
|
||||
// ...
|
||||
|
||||
private $_listeners = array();
|
||||
|
||||
public function addPropertyChangedListener(PropertyChangedListener $listener)
|
||||
|
||||
private array $_listeners = array();
|
||||
|
||||
public function addPropertyChangedListener(PropertyChangedListener $listener): void
|
||||
{
|
||||
$this->_listeners[] = $listener;
|
||||
}
|
||||
@@ -104,12 +101,12 @@ behaviour:
|
||||
|
||||
<?php
|
||||
// ...
|
||||
|
||||
|
||||
class MyEntity implements NotifyPropertyChanged
|
||||
{
|
||||
// ...
|
||||
|
||||
protected function _onPropertyChanged($propName, $oldValue, $newValue)
|
||||
|
||||
protected function _onPropertyChanged($propName, $oldValue, $newValue): void
|
||||
{
|
||||
if ($this->_listeners) {
|
||||
foreach ($this->_listeners as $listener) {
|
||||
@@ -117,8 +114,8 @@ behaviour:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setData($data)
|
||||
|
||||
public function setData($data): void
|
||||
{
|
||||
if ($data != $this->data) {
|
||||
$this->_onPropertyChanged('data', $this->data, $data);
|
||||
@@ -134,18 +131,18 @@ The check whether the new value is different from the old one is
|
||||
not mandatory but recommended. That way you also have full control
|
||||
over when you consider a property changed.
|
||||
|
||||
If your entity contains an embeddable, you will need to notify
|
||||
separately for each property in the embeddable when it changes
|
||||
If your entity contains an embeddable, you will need to notify
|
||||
separately for each property in the embeddable when it changes
|
||||
for example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// ...
|
||||
|
||||
|
||||
class MyEntity implements NotifyPropertyChanged
|
||||
{
|
||||
public function setEmbeddable(MyValueObject $embeddable)
|
||||
public function setEmbeddable(MyValueObject $embeddable): void
|
||||
{
|
||||
if (!$embeddable->equals($this->embeddable)) {
|
||||
// notice the entityField.embeddableField notation for referencing the property
|
||||
@@ -178,5 +175,3 @@ The positive point and main advantage of this policy is its
|
||||
effectiveness. It has the best performance characteristics of the 3
|
||||
policies with larger units of work and a flush() operation is very
|
||||
cheap when nothing has changed.
|
||||
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ access point to ORM functionality provided by Doctrine.
|
||||
// bootstrap.php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
$paths = array("/path/to/entity-files");
|
||||
$isDevMode = false;
|
||||
@@ -55,16 +55,21 @@ access point to ORM functionality provided by Doctrine.
|
||||
'dbname' => 'foo',
|
||||
);
|
||||
|
||||
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
.. note::
|
||||
|
||||
The ``ORMSetup`` class has been introduced with ORM 2.12. It's predecessor ``Setup`` is deprecated and will
|
||||
be removed in version 3.0.
|
||||
|
||||
Or if you prefer XML:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/xml-mappings");
|
||||
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
Or if you prefer YAML:
|
||||
@@ -73,54 +78,39 @@ Or if you prefer YAML:
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/yml-mappings");
|
||||
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$config = ORMSetup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
.. note::
|
||||
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
|
||||
|
||||
|
||||
::
|
||||
|
||||
|
||||
"symfony/yaml": "*"
|
||||
|
||||
Inside the ``Setup`` methods several assumptions are made:
|
||||
Inside the ``ORMSetup`` methods several assumptions are made:
|
||||
|
||||
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
|
||||
- If ``$isDevMode`` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument.
|
||||
- If ``$isDevMode`` is true caching is done in memory with the ``ArrayAdapter``. Proxy objects are recreated on every request.
|
||||
- If ``$isDevMode`` is false, check for Caches in the order APCu, Redis (127.0.0.1:6379), Memcache (127.0.0.1:11211) unless `$cache` is passed as fourth argument.
|
||||
- If ``$isDevMode`` is false, set then proxy classes have to be explicitly created through the command line.
|
||||
- If third argument `$proxyDir` is not set, use the systems temporary directory.
|
||||
|
||||
.. note::
|
||||
|
||||
In order to have ``ORMSetup`` configure the cache automatically, the library ``symfony/cache``
|
||||
has to be installed as a dependency.
|
||||
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
|
||||
|
||||
.. note::
|
||||
|
||||
You can learn more about the database connection configuration in the
|
||||
`Doctrine DBAL connection configuration reference <https://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||
`Doctrine DBAL connection configuration reference <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/configuration.html>`_.
|
||||
|
||||
Setting up the Commandline Tool
|
||||
-------------------------------
|
||||
|
||||
Doctrine ships with a number of command line tools that are very helpful
|
||||
during development. You can call this command from the Composer binary
|
||||
directory:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ php vendor/bin/doctrine
|
||||
|
||||
You need to register your applications EntityManager to the console tool
|
||||
to make use of the tasks by creating a ``cli-config.php`` file with the
|
||||
following content:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
// replace with file to your own project bootstrap
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
|
||||
return ConsoleRunner::createHelperSet($entityManager);
|
||||
during development. In order to make use of them, create an executable PHP
|
||||
script in your project as described in the
|
||||
:doc:`tools chapter <../reference/tools>`.
|
||||
|
||||
@@ -672,18 +672,18 @@ The same restrictions apply for the reference of related entities.
|
||||
|
||||
DQL DELETE statements are ported directly into an SQL DELETE statement.
|
||||
Therefore, some limitations apply:
|
||||
|
||||
|
||||
- Lifecycle events for the affected entities are not executed.
|
||||
- A cascading ``remove`` operation (as indicated e. g. by ``cascade={"remove"}``
|
||||
or ``cascade={"all"}`` in the mapping configuration) is not being performed
|
||||
- A cascading ``remove`` operation (as indicated e. g. by ``cascade: ['remove']``
|
||||
or ``cascade: ['all']`` in the mapping configuration) is not being performed
|
||||
for associated entities. You can rely on database level cascade operations by
|
||||
configuring each join column with the ``onDelete`` option.
|
||||
- Checks for the version column are bypassed if they are not explicitly added
|
||||
to the WHERE clause of the query.
|
||||
|
||||
When you rely on one of these features, one option is to use the
|
||||
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
|
||||
It means collections and related entities are fetched into memory
|
||||
|
||||
When you rely on one of these features, one option is to use the
|
||||
``EntityManager#remove($entity)`` method. This, however, is costly performance-wise:
|
||||
It means collections and related entities are fetched into memory
|
||||
(even if they are marked as lazy). Pulling object graphs into memory on cascade
|
||||
can cause considerable performance overhead, especially when the cascaded collections
|
||||
are large. Make sure to weigh the benefits and downsides.
|
||||
@@ -864,36 +864,26 @@ scenario it is a generic Person and Employee example:
|
||||
<?php
|
||||
namespace Entities;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||
*/
|
||||
#[Entity]
|
||||
#[InheritanceType('SINGLE_TABLE')]
|
||||
#[DiscriminatorColumn(name: 'discr', type: 'string')]
|
||||
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
|
||||
class Person
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
protected $id;
|
||||
#[Id, Column(type: 'integer')]
|
||||
#[GeneratedValue]
|
||||
protected int|null $id = null;
|
||||
|
||||
/**
|
||||
* @Column(type="string", length=50)
|
||||
*/
|
||||
protected $name;
|
||||
#[Column(type: 'string', length: 50)]
|
||||
protected string $name;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
#[Entity]
|
||||
class Employee extends Person
|
||||
{
|
||||
/**
|
||||
* @Column(type="string", length=50)
|
||||
*/
|
||||
#[Column(type: 'string', length: 50)]
|
||||
private $department;
|
||||
|
||||
// ...
|
||||
@@ -959,12 +949,11 @@ table, you just need to change the inheritance type from
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("JOINED")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||
*/
|
||||
|
||||
#[Entity]
|
||||
#[InheritanceType('JOINED')]
|
||||
#[DiscriminatorColumn(name: 'discr', type: 'string')]
|
||||
#[DiscriminatorMap(['person' => 'Person', 'employee' => 'Employee'])]
|
||||
class Person
|
||||
{
|
||||
// ...
|
||||
@@ -1531,6 +1520,8 @@ Given that there are 10 users and corresponding addresses in the database the ex
|
||||
a one-by-one basis once they are accessed.
|
||||
|
||||
|
||||
.. _dql_ebnf_grammar:
|
||||
|
||||
EBNF
|
||||
----
|
||||
|
||||
@@ -1839,5 +1830,3 @@ Functions
|
||||
"LOWER" "(" StringPrimary ")" |
|
||||
"UPPER" "(" StringPrimary ")" |
|
||||
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
|
||||
|
||||
|
||||
|
||||
@@ -79,7 +79,9 @@ array of events it should be subscribed to.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
|
||||
class TestEventSubscriber implements EventSubscriber
|
||||
{
|
||||
public $preFooInvoked = false;
|
||||
|
||||
@@ -142,33 +144,33 @@ Events Overview
|
||||
| Event | Dispatched by | Lifecycle | Passed |
|
||||
| | | Callback | Argument |
|
||||
+=================================================================+=======================+===========+=====================================+
|
||||
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `_LifecycleEventArgs`_ |
|
||||
| :ref:`preRemove<reference-events-pre-remove>` | ``$em->remove()`` | Yes | `LifecycleEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `_LifecycleEventArgs`_ |
|
||||
| :ref:`postRemove<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `_LifecycleEventArgs`_ |
|
||||
| :ref:`prePersist<reference-events-pre-persist>` | ``$em->persist()`` | Yes | `LifecycleEventArgs`_ |
|
||||
| | on *initial* persist | | |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `_LifecycleEventArgs`_ |
|
||||
| :ref:`postPersist<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `_PreUpdateEventArgs`_ |
|
||||
| :ref:`preUpdate<reference-events-pre-update>` | ``$em->flush()`` | Yes | `PreUpdateEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `_LifecycleEventArgs`_ |
|
||||
| :ref:`postUpdate<reference-events-post-update-remove-persist>` | ``$em->flush()`` | Yes | `LifecycleEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `_LifecycleEventArgs`_ |
|
||||
| :ref:`postLoad<reference-events-post-load>` | Loading from database | Yes | `LifecycleEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `_LoadClassMetadataEventArgs` |
|
||||
| :ref:`loadClassMetadata<reference-events-load-class-metadata>` | Loading of mapping | No | `LoadClassMetadataEventArgs`_ |
|
||||
| | metadata | | |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `_OnClassMetadataNotFoundEventArgs` |
|
||||
| ``onClassMetadataNotFound`` | ``MappingException`` | No | `OnClassMetadataNotFoundEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`preFlush<reference-events-pre-flush>` | ``$em->flush()`` | Yes | `_PreFlushEventArgs`_ |
|
||||
| :ref:`preFlush<reference-events-pre-flush>` | ``$em->flush()`` | Yes | `PreFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onFlush<reference-events-on-flush>` | ``$em->flush()`` | No | `_OnFlushEventArgs` |
|
||||
| :ref:`onFlush<reference-events-on-flush>` | ``$em->flush()`` | No | `OnFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No | `_PostFlushEventArgs` |
|
||||
| :ref:`postFlush<reference-events-post-flush>` | ``$em->flush()`` | No | `PostFlushEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
| :ref:`onClear<reference-events-on-clear>` | ``$em->clear()`` | No | `_OnClearEventArgs` |
|
||||
| :ref:`onClear<reference-events-on-clear>` | ``$em->clear()`` | No | `OnClearEventArgs`_ |
|
||||
+-----------------------------------------------------------------+-----------------------+-----------+-------------------------------------+
|
||||
|
||||
Naming convention
|
||||
@@ -211,9 +213,8 @@ specific to a particular entity class's lifecycle.
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
|
||||
#[Entity]
|
||||
#[HasLifecycleCallbacks]
|
||||
@@ -245,7 +246,6 @@ specific to a particular entity class's lifecycle.
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
/**
|
||||
@@ -504,7 +504,6 @@ result in an infinite loop.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
|
||||
class PreFlushExampleListener
|
||||
@@ -542,7 +541,7 @@ mentioned sets. See this example:
|
||||
{
|
||||
public function onFlush(OnFlushEventArgs $eventArgs)
|
||||
{
|
||||
$em = $eventArgs->getEntityManager();
|
||||
$em = $eventArgs->getObjectManager();
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||
@@ -590,7 +589,6 @@ This event is not a lifecycle callback.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Event\PostFlushEventArgs;
|
||||
|
||||
class PostFlushExampleListener
|
||||
@@ -615,7 +613,7 @@ Changes to associations of the updated entity are never allowed in
|
||||
this event, since Doctrine cannot guarantee to correctly handle
|
||||
referential integrity at this point of the flush operation. This
|
||||
event has a powerful feature however, it is executed with a
|
||||
`_PreUpdateEventArgs`_ instance, which contains a reference to the
|
||||
`PreUpdateEventArgs`_ instance, which contains a reference to the
|
||||
computed change-set of this entity.
|
||||
|
||||
This means you have access to all the fields that have changed for
|
||||
@@ -637,6 +635,8 @@ A simple example for this event looks like:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class NeverAliceOnlyBobListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
@@ -644,6 +644,9 @@ A simple example for this event looks like:
|
||||
if ($eventArgs->getEntity() instanceof User) {
|
||||
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
|
||||
$eventArgs->setNewValue('name', 'Bob');
|
||||
// The following will only work if `status` is already present in the computed changeset.
|
||||
// Otherwise it will throw an InvalidArgumentException:
|
||||
$eventArgs->setNewValue('status', 'active');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -656,6 +659,8 @@ lifecycle callback when there are expensive validations to call:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class ValidCreditCardListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
@@ -751,7 +756,7 @@ An entity listener is a lifecycle listener class used for an entity.
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
|
||||
<?php
|
||||
namespace MyProject\Entity;
|
||||
use App\EventListener\UserListener;
|
||||
@@ -807,6 +812,8 @@ An ``Entity Listener`` could be any class, by default it should be a class with
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
class UserListener
|
||||
{
|
||||
public function preUpdate(User $user, PreUpdateEventArgs $event)
|
||||
@@ -820,35 +827,74 @@ you need to map the listener method using the event type mapping:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
class UserListener
|
||||
{
|
||||
#[PrePersist]
|
||||
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
#[PostPersist]
|
||||
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
#[PreUpdate]
|
||||
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
|
||||
|
||||
#[PostUpdate]
|
||||
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
#[PostRemove]
|
||||
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
#[PreRemove]
|
||||
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
#[PreFlush]
|
||||
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
|
||||
|
||||
#[PostLoad]
|
||||
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
class UserListener
|
||||
{
|
||||
/** @PrePersist */
|
||||
public function prePersistHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
public function prePersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostPersist */
|
||||
public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
public function postPersistHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
/** @PreUpdate */
|
||||
public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
|
||||
public function preUpdateHandler(User $user, PreUpdateEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostUpdate */
|
||||
public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
public function postUpdateHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostRemove */
|
||||
public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
public function postRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
/** @PreRemove */
|
||||
public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
public function preRemoveHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
|
||||
/** @PreFlush */
|
||||
public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
|
||||
public function preFlushHandler(User $user, PreFlushEventArgs $event): void { // ... }
|
||||
|
||||
/** @PostLoad */
|
||||
public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
|
||||
public function postLoadHandler(User $user, LifecycleEventArgs $event): void { // ... }
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
@@ -907,6 +953,8 @@ Specifying an entity listener instance :
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\PreUpdateEventArgs;
|
||||
|
||||
// User.php
|
||||
|
||||
/** @Entity @EntityListeners({"UserListener"}) */
|
||||
@@ -933,12 +981,14 @@ Specifying an entity listener instance :
|
||||
$listener = $container->get('user_listener');
|
||||
$em->getConfiguration()->getEntityListenerResolver()->register($listener);
|
||||
|
||||
Implementing your own resolver :
|
||||
Implementing your own resolver:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntityListenerResolver extends \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
|
||||
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
|
||||
|
||||
class MyEntityListenerResolver extends DefaultEntityListenerResolver
|
||||
{
|
||||
public function __construct($container)
|
||||
{
|
||||
@@ -965,20 +1015,22 @@ Load ClassMetadata Event
|
||||
|
||||
``loadClassMetadata`` - The ``loadClassMetadata`` event occurs after the
|
||||
mapping metadata for a class has been loaded from a mapping source
|
||||
(annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
|
||||
(attributes/annotations/xml/yaml) in to a ``Doctrine\ORM\Mapping\ClassMetadata`` instance.
|
||||
You can hook in to this process and manipulate the instance.
|
||||
This event is not a lifecycle callback.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
|
||||
$test = new TestEventListener();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $test);
|
||||
|
||||
class TestEventListener
|
||||
{
|
||||
public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
|
||||
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
$fieldMapping = array(
|
||||
@@ -1011,13 +1063,16 @@ instance and class metadata.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\ToolEvents;
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs;
|
||||
|
||||
$test = new TestEventListener();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchemaTable, $test);
|
||||
$evm->addEventListener(ToolEvents::postGenerateSchemaTable, $test);
|
||||
|
||||
class TestEventListener
|
||||
{
|
||||
public function postGenerateSchemaTable(\Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs $eventArgs)
|
||||
public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
$schema = $eventArgs->getSchema();
|
||||
@@ -1035,13 +1090,16 @@ and the EntityManager.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\ToolEvents;
|
||||
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
|
||||
|
||||
$test = new TestEventListener();
|
||||
$evm = $em->getEventManager();
|
||||
$evm->addEventListener(\Doctrine\ORM\Tools\ToolEvents::postGenerateSchema, $test);
|
||||
$evm->addEventListener(ToolEvents::postGenerateSchema, $test);
|
||||
|
||||
class TestEventListener
|
||||
{
|
||||
public function postGenerateSchema(\Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs $eventArgs)
|
||||
public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs)
|
||||
{
|
||||
$schema = $eventArgs->getSchema();
|
||||
$em = $eventArgs->getEntityManager();
|
||||
|
||||
@@ -13,10 +13,11 @@ Database Schema
|
||||
How do I set the charset and collation for MySQL tables?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can't set these values inside the annotations, yml or xml mapping files. To make a database
|
||||
work with the default charset and collation you should configure MySQL to use it as default charset,
|
||||
or create the database with charset and collation details. This way they get inherited to all newly
|
||||
created database tables and columns.
|
||||
You can't set these values with attributes, annotations or inside yml or
|
||||
xml mapping files. To make a database work with the default charset and
|
||||
collation you should configure MySQL to use it as default charset, or
|
||||
create the database with charset and collation details. This way they
|
||||
get inherited to all newly created database tables and columns.
|
||||
|
||||
Entity Classes
|
||||
--------------
|
||||
@@ -32,11 +33,12 @@ upon insert:
|
||||
|
||||
class User
|
||||
{
|
||||
const STATUS_DISABLED = 0;
|
||||
const STATUS_ENABLED = 1;
|
||||
private const STATUS_DISABLED = 0;
|
||||
private const STATUS_ENABLED = 1;
|
||||
|
||||
private $algorithm = "sha1";
|
||||
private $status = self:STATUS_DISABLED;
|
||||
private string $algorithm = "sha1";
|
||||
/** @var self::STATUS_* */
|
||||
private int $status = self:STATUS_DISABLED;
|
||||
}
|
||||
|
||||
.
|
||||
|
||||
@@ -42,7 +42,7 @@ proper quoting of parameters.
|
||||
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
|
||||
{
|
||||
// Check if the entity implements the LocalAware interface
|
||||
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
|
||||
|
||||
@@ -38,38 +38,36 @@ Example:
|
||||
use Doctrine\ORM\Mapping\MappedSuperclass;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
/** @MappedSuperclass */
|
||||
#[MappedSuperclass]
|
||||
class Person
|
||||
{
|
||||
/** @Column(type="integer") */
|
||||
protected $mapped1;
|
||||
/** @Column(type="string") */
|
||||
protected $mapped2;
|
||||
/**
|
||||
* @OneToOne(targetEntity="Toothbrush")
|
||||
* @JoinColumn(name="toothbrush_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $toothbrush;
|
||||
#[Column(type: 'integer')]
|
||||
protected int $mapped1;
|
||||
#[Column(type: 'string')]
|
||||
protected string $mapped2;
|
||||
#[OneToOne(targetEntity: Toothbrush::class)]
|
||||
#[JoinColumn(name: 'toothbrush_id', referencedColumnName: 'id')]
|
||||
protected Toothbrush|null $toothbrush = null;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
|
||||
#[Entity]
|
||||
class Employee extends Person
|
||||
{
|
||||
/** @Id @Column(type="integer") */
|
||||
private $id;
|
||||
/** @Column(type="string") */
|
||||
private $name;
|
||||
|
||||
#[Id, Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
#[Column(type: 'string')]
|
||||
private string $name;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class Toothbrush
|
||||
{
|
||||
/** @Id @Column(type="integer") */
|
||||
private $id;
|
||||
#[Id, Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
@@ -99,11 +97,31 @@ Example:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
|
||||
|
||||
#[Entity]
|
||||
#[InheritanceType('SINGLE_TABLE')]
|
||||
#[DiscriminatorColumn(name: 'discr', type: 'string')]
|
||||
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
|
||||
class Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class Employee extends Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("SINGLE_TABLE")
|
||||
@@ -114,7 +132,7 @@ Example:
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
@@ -124,7 +142,7 @@ Example:
|
||||
}
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
||||
MyProject\Model\Person:
|
||||
type: entity
|
||||
inheritanceType: SINGLE_TABLE
|
||||
@@ -134,29 +152,30 @@ Example:
|
||||
discriminatorMap:
|
||||
person: Person
|
||||
employee: Employee
|
||||
|
||||
|
||||
MyProject\Model\Employee:
|
||||
type: entity
|
||||
|
||||
|
||||
Things to note:
|
||||
|
||||
|
||||
- The @InheritanceType and @DiscriminatorColumn must be specified
|
||||
on the topmost class that is part of the mapped entity hierarchy.
|
||||
- The @DiscriminatorMap specifies which values of the
|
||||
- The ``#[InheritanceType]`` and ``#[DiscriminatorColumn]`` must be
|
||||
specified on the topmost class that is part of the mapped entity
|
||||
hierarchy.
|
||||
- The ``#[DiscriminatorMap]`` specifies which values of the
|
||||
discriminator column identify a row as being of a certain type. In
|
||||
the case above a value of "person" identifies a row as being of
|
||||
type ``Person`` and "employee" identifies a row as being of type
|
||||
``Employee``.
|
||||
- All entity classes that is part of the mapped entity hierarchy
|
||||
(including the topmost class) should be specified in the
|
||||
@DiscriminatorMap. In the case above Person class included.
|
||||
``#[DiscriminatorMap]``. In the case above Person class included.
|
||||
- The names of the classes in the discriminator map do not need to
|
||||
be fully qualified if the classes are contained in the same
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
|
||||
Design-time considerations
|
||||
@@ -178,9 +197,9 @@ relationships involving types that employ this mapping strategy are
|
||||
very performing.
|
||||
|
||||
There is a general performance consideration with Single Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is an STI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is an STI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
@@ -212,19 +231,17 @@ Example:
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("JOINED")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||
*/
|
||||
|
||||
#[Entity]
|
||||
#[InheritanceType('JOINED')]
|
||||
#[DiscriminatorColumn(name: 'discr', type: 'string')]
|
||||
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
|
||||
class Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
|
||||
#[Entity]
|
||||
class Employee extends Person
|
||||
{
|
||||
// ...
|
||||
@@ -233,10 +250,10 @@ Example:
|
||||
Things to note:
|
||||
|
||||
|
||||
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
|
||||
must be specified on the topmost class that is part of the mapped
|
||||
entity hierarchy.
|
||||
- The @DiscriminatorMap specifies which values of the
|
||||
- The ``#[InheritanceType]``, ``#[DiscriminatorColumn]`` and
|
||||
``#[DiscriminatorMap]`` must be specified on the topmost class that is
|
||||
part of the mapped entity hierarchy.
|
||||
- The ``#[DiscriminatorMap]`` specifies which values of the
|
||||
discriminator column identify a row as being of which type. In the
|
||||
case above a value of "person" identifies a row as being of type
|
||||
``Person`` and "employee" identifies a row as being of type
|
||||
@@ -246,7 +263,7 @@ Things to note:
|
||||
namespace as the entity class on which the discriminator map is
|
||||
applied.
|
||||
- If no discriminator map is provided, then the map is generated
|
||||
automatically. The automatically generated discriminator map
|
||||
automatically. The automatically generated discriminator map
|
||||
contains the lowercase short name of each class as key.
|
||||
|
||||
.. note::
|
||||
@@ -284,13 +301,13 @@ themselves on access of any subtype fields, so accessing fields of
|
||||
subtypes after such a query is not safe.
|
||||
|
||||
There is a general performance consideration with Class Table
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is a CTI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||
association is a CTI entity, it is preferable for performance reasons that it
|
||||
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
There is also another important performance consideration that it is *NOT POSSIBLE*
|
||||
There is also another important performance consideration that it is *NOT POSSIBLE*
|
||||
to query for the base entity without any LEFT JOINs to the sub-types.
|
||||
|
||||
SQL Schema considerations
|
||||
@@ -330,7 +347,52 @@ Example:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
namespace MyProject\Model;
|
||||
|
||||
#[MappedSuperclass]
|
||||
class User
|
||||
{
|
||||
// other fields mapping
|
||||
|
||||
/** @var Collection<int, Group> */
|
||||
#[JoinTable(name: 'users_groups')]
|
||||
#[JoinColumn(name: 'user_id', referencedColumnName: 'id')]
|
||||
#[InverseJoinColumn(name: 'group_id', referencedColumnName: 'id')]
|
||||
#[ManyToMany(targetEntity: 'Group', inversedBy: 'users')]
|
||||
protected Collection $groups;
|
||||
|
||||
#[ManyToOne(targetEntity: 'Address')]
|
||||
#[JoinColumn(name: 'address_id', referencedColumnName: 'id')]
|
||||
protected Address|null $address = null;
|
||||
}
|
||||
|
||||
// admin mapping
|
||||
namespace MyProject\Model;
|
||||
|
||||
#[Entity]
|
||||
#[AssociationOverrides([
|
||||
new AssociationOverride(
|
||||
name: 'groups',
|
||||
joinTable: new JoinTable(
|
||||
name: 'users_admingroups',
|
||||
),
|
||||
joinColumns: [new JoinColumn(name: 'adminuser_id')],
|
||||
inverseJoinColumns: [new JoinColumn(name: 'admingroup_id')]
|
||||
),
|
||||
new AssociationOverride(
|
||||
name: 'address',
|
||||
joinColumns: [new JoinColumn(name: 'adminaddress_id', referencedColumnName: 'id')]
|
||||
)
|
||||
])]
|
||||
class Admin extends User
|
||||
{
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
@@ -348,14 +410,15 @@ Example:
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
|
||||
* )
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
protected $groups;
|
||||
protected Collection $groups;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $address;
|
||||
protected Address|null $address = null;
|
||||
}
|
||||
|
||||
// admin mapping
|
||||
@@ -490,7 +553,51 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
namespace MyProject\Model;
|
||||
|
||||
#[MappedSuperclass]
|
||||
class User
|
||||
{
|
||||
#[Id, GeneratedValue, Column(type: 'integer', name: 'user_id', length: 150)]
|
||||
protected int|null $id = null;
|
||||
|
||||
#[Column(name: 'user_name', nullable: true, unique: false, length: 250)]
|
||||
protected string $name;
|
||||
|
||||
// other fields mapping
|
||||
}
|
||||
|
||||
// guest mapping
|
||||
namespace MyProject\Model;
|
||||
#[Entity]
|
||||
#[AttributeOverrides([
|
||||
new AttributeOverride(
|
||||
name: 'id',
|
||||
column: new Column(
|
||||
name: 'guest_id',
|
||||
type: 'integer',
|
||||
length: 140
|
||||
)
|
||||
),
|
||||
new AttributeOverride(
|
||||
name: 'name',
|
||||
column: new Column(
|
||||
name: 'guest_name',
|
||||
nullable: false,
|
||||
unique: true,
|
||||
length: 240
|
||||
)
|
||||
)
|
||||
])]
|
||||
class Guest extends User
|
||||
{
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
@@ -501,10 +608,10 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
class User
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
|
||||
protected $id;
|
||||
protected int|null $id = null;
|
||||
|
||||
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
|
||||
protected $name;
|
||||
protected string $name;
|
||||
|
||||
// other fields mapping
|
||||
}
|
||||
|
||||
@@ -55,51 +55,66 @@ Implementing Metadata Drivers
|
||||
|
||||
In addition to the included metadata drivers you can very easily
|
||||
implement your own. All you need to do is define a class which
|
||||
implements the ``Driver`` interface:
|
||||
implements the ``MappingDriver`` interface:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
|
||||
interface Driver
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\Persistence\Mapping\Driver;
|
||||
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Contract for metadata drivers.
|
||||
*/
|
||||
interface MappingDriver
|
||||
{
|
||||
/**
|
||||
* Loads the metadata for the specified class into the provided container.
|
||||
*
|
||||
* @param string $className
|
||||
* @param ClassMetadataInfo $metadata
|
||||
*
|
||||
* @psalm-param class-string<T> $className
|
||||
* @psalm-param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @template T of object
|
||||
*/
|
||||
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
|
||||
|
||||
public function loadMetadataForClass(string $className, ClassMetadata $metadata);
|
||||
|
||||
/**
|
||||
* Gets the names of all mapped classes known to this driver.
|
||||
*
|
||||
* @return array The names of all mapped classes known to this driver.
|
||||
*/
|
||||
function getAllClassNames();
|
||||
|
||||
/**
|
||||
* Whether the class with the specified name should have its metadata loaded.
|
||||
* This is only the case if it is either mapped as an Entity or a
|
||||
* MappedSuperclass.
|
||||
*
|
||||
* @param string $className
|
||||
* @return boolean
|
||||
* @return array<int, string> The names of all mapped classes known to this driver.
|
||||
* @psalm-return list<class-string>
|
||||
*/
|
||||
function isTransient($className);
|
||||
public function getAllClassNames();
|
||||
|
||||
/**
|
||||
* Returns whether the class with the specified name should have its metadata loaded.
|
||||
* This is only the case if it is either mapped as an Entity or a MappedSuperclass.
|
||||
*
|
||||
* @psalm-param class-string $className
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isTransient(string $className);
|
||||
}
|
||||
|
||||
If you want to write a metadata driver to parse information from
|
||||
some file format we've made your life a little easier by providing
|
||||
the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
the ``FileDriver`` implementation for you to extend from:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyMetadataDriver extends AbstractFileDriver
|
||||
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\FileDriver;
|
||||
|
||||
class MyMetadataDriver extends FileDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@@ -109,11 +124,11 @@ the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
{
|
||||
$data = $this->_loadMappingFile($file);
|
||||
|
||||
// populate ClassMetadataInfo instance from $data
|
||||
// populate ClassMetadata instance from $data
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,13 +142,12 @@ the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
|
||||
.. note::
|
||||
|
||||
When using the ``AbstractFileDriver`` it requires that you
|
||||
only have one entity defined per file and the file named after the
|
||||
class described inside where namespace separators are replaced by
|
||||
periods. So if you have an entity named ``Entities\User`` and you
|
||||
wanted to write a mapping file for your driver above you would need
|
||||
to name the file ``Entities.User.dcm.ext`` for it to be
|
||||
recognized.
|
||||
When using the ``FileDriver`` it requires that you only have one
|
||||
entity defined per file and the file named after the class described
|
||||
inside where namespace separators are replaced by periods. So if you
|
||||
have an entity named ``Entities\User`` and you wanted to write a
|
||||
mapping file for your driver above you would need to name the file
|
||||
``Entities.User.dcm.ext`` for it to be recognized.
|
||||
|
||||
|
||||
Now you can use your ``MyMetadataDriver`` implementation by setting
|
||||
@@ -156,14 +170,6 @@ entity when needed.
|
||||
|
||||
You have all the methods you need to manually specify the mapping
|
||||
information instead of using some mapping file to populate it from.
|
||||
The base ``ClassMetadataInfo`` class is responsible for only data
|
||||
storage and is not meant for runtime use. It does not require that
|
||||
the class actually exists yet so it is useful for describing some
|
||||
entity before it exists and using that information to generate for
|
||||
example the entities themselves. The class ``ClassMetadata``
|
||||
extends ``ClassMetadataInfo`` and adds some functionality required
|
||||
for runtime usage and requires that the PHP class is present and
|
||||
can be autoloaded.
|
||||
|
||||
You can read more about the API of the ``ClassMetadata`` classes in
|
||||
the PHP Mapping chapter.
|
||||
|
||||
@@ -7,7 +7,7 @@ reduce the verbosity of the mapping document, eliminating repetitive noise (eg:
|
||||
|
||||
.. warning
|
||||
|
||||
The naming strategy is always overridden by entity mapping such as the `Table` annotation.
|
||||
The naming strategy is always overridden by entity mapping such as the `Table` attribute.
|
||||
|
||||
Configuring a naming strategy
|
||||
-----------------------------
|
||||
|
||||
@@ -90,12 +90,14 @@ Static Function
|
||||
In addition to the PHP files you can also specify your mapping
|
||||
information inside of a static function defined on the entity class
|
||||
itself. This is useful for cases where you want to keep your entity
|
||||
and mapping information together but don't want to use annotations.
|
||||
For this you just need to use the ``StaticPHPDriver``:
|
||||
and mapping information together but don't want to use attributes or
|
||||
annotations. For this you just need to use the ``StaticPHPDriver``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Persistence\Mapping\Driver\StaticPHPDriver;
|
||||
|
||||
$driver = new StaticPHPDriver('/path/to/entities');
|
||||
$em->getConfiguration()->setMetadataDriverImpl($driver);
|
||||
|
||||
@@ -185,13 +187,12 @@ It also has several methods that create builders (which are necessary for advanc
|
||||
- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance
|
||||
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
|
||||
|
||||
ClassMetadataInfo API
|
||||
---------------------
|
||||
ClassMetadata API
|
||||
-----------------
|
||||
|
||||
The ``ClassMetadataInfo`` class is the base data object for storing
|
||||
the mapping metadata for a single entity. It contains all the
|
||||
getters and setters you need populate and retrieve information for
|
||||
an entity.
|
||||
The ``ClassMetadata`` class is the data object for storing the mapping
|
||||
metadata for a single entity. It contains all the getters and setters
|
||||
you need populate and retrieve information for an entity.
|
||||
|
||||
General Setters
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -309,13 +310,11 @@ Lifecycle Callback Getters
|
||||
- ``hasLifecycleCallbacks($lifecycleEvent)``
|
||||
- ``getLifecycleCallbacks($event)``
|
||||
|
||||
ClassMetadata API
|
||||
-----------------
|
||||
Runtime reflection methods
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds
|
||||
the runtime functionality required by Doctrine. It adds a few extra
|
||||
methods related to runtime reflection for working with the entities
|
||||
themselves.
|
||||
These are methods related to runtime reflection for working with the
|
||||
entities themselves.
|
||||
|
||||
|
||||
- ``getReflectionClass()``
|
||||
@@ -326,5 +325,3 @@ themselves.
|
||||
- ``setIdentifierValues($entity, $id)``
|
||||
- ``setFieldValue($entity, $field, $value)``
|
||||
- ``getFieldValue($entity, $field)``
|
||||
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
// prevents attempt to load metadata for date time class, improving performance
|
||||
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATE_IMMUTABLE)
|
||||
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATETIME_IMMUTABLE)
|
||||
|
||||
If you've got several parameters to bind to your query, you can
|
||||
also use setParameters() instead of setParameter() with the
|
||||
@@ -434,6 +434,12 @@ complete list of supported helper methods available:
|
||||
// Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL
|
||||
public function isNotNull($x); // Returns string
|
||||
|
||||
// Example - $qb->expr()->isMemberOf('?1', 'u.groups') => ?1 MEMBER OF u.groups
|
||||
public function isMemberOf($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
// Example - $qb->expr()->isInstanceOf('u', Employee::class) => u INSTANCE OF Employee
|
||||
public function isInstanceOf($x, $y); // Returns Expr\Comparison instance
|
||||
|
||||
|
||||
/** Arithmetic objects **/
|
||||
|
||||
|
||||
@@ -277,26 +277,44 @@ level cache region.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
#[Cache(usage: 'READ_ONLY', region: 'my_entity_region')]
|
||||
class Country
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
#[Column]
|
||||
protected int|null $id = null;
|
||||
|
||||
#[Column(unique: true)]
|
||||
protected string $name;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache(usage="READ_ONLY", region="my_entity_region")
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
*/
|
||||
class Country
|
||||
class State
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
protected int|null $id = null;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
protected string $name;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
@@ -340,7 +358,35 @@ It caches the primary keys of association and cache each element will be cached
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
|
||||
class State
|
||||
{
|
||||
#[Id]
|
||||
#[GeneratedValue]
|
||||
#[Column]
|
||||
protected int|null $id = null;
|
||||
|
||||
#[Column(unique: true)]
|
||||
protected string $name;
|
||||
|
||||
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
|
||||
#[ManyToOne(targetEntity: Country::class)]
|
||||
#[JoinColumn(name: 'country_id', referencedColumnName: 'id')]
|
||||
protected Country|null $country = null;
|
||||
|
||||
/** @var Collection<int, City> */
|
||||
#[Cache(usage: 'NONSTRICT_READ_WRITE')]
|
||||
#[OneToMany(targetEntity: City::class, mappedBy: 'state')]
|
||||
protected Collection $cities;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
@@ -354,25 +400,26 @@ It caches the primary keys of association and cache each element will be cached
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
protected int|null $id = null;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected $name;
|
||||
protected string $name;
|
||||
|
||||
/**
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @ManyToOne(targetEntity="Country")
|
||||
* @JoinColumn(name="country_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $country;
|
||||
protected Country|null $country;
|
||||
|
||||
/**
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @OneToMany(targetEntity="City", mappedBy="state")
|
||||
* @var Collection<int, City>
|
||||
*/
|
||||
protected $cities;
|
||||
protected Collection $cities;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
@@ -673,23 +720,18 @@ For performance reasons the cache API does not extract from composite primary ke
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
|
||||
#[Entity]
|
||||
class Reference
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity="Article", inversedBy="references")
|
||||
* @JoinColumn(name="source_id", referencedColumnName="article_id")
|
||||
*/
|
||||
private $source;
|
||||
#[Id]
|
||||
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
|
||||
#[JoinColumn(name: 'source_id', referencedColumnName: 'article_id')]
|
||||
private Article|null $source = null;
|
||||
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity="Article")
|
||||
* @JoinColumn(name="target_id", referencedColumnName="article_id")
|
||||
*/
|
||||
#[Id]
|
||||
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
|
||||
#[JoinColumn(name: 'target_id', referencedColumnName: 'article_id')]
|
||||
private $target;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,19 +98,20 @@ entity might look like this:
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
#[Entity]
|
||||
class InsecureEntity
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
/** @Column */
|
||||
private $email;
|
||||
/** @Column(type="boolean") */
|
||||
private $isAdmin;
|
||||
#[Id, Column, GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
public function fromArray(array $userInput)
|
||||
#[Column]
|
||||
private string $email;
|
||||
|
||||
#[Column]
|
||||
private bool $isAdmin;
|
||||
|
||||
/** @param array<string, mixed> $userInput */
|
||||
public function fromArray(array $userInput): void
|
||||
{
|
||||
foreach ($userInput as $key => $value) {
|
||||
$this->$key = $value;
|
||||
@@ -118,7 +119,7 @@ entity might look like this:
|
||||
}
|
||||
}
|
||||
|
||||
Now the possiblity of mass-asignment exists on this entity and can
|
||||
Now the possiblity 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:
|
||||
|
||||
|
||||
@@ -7,24 +7,10 @@ Doctrine Console
|
||||
The Doctrine Console is a Command Line Interface tool for simplifying common
|
||||
administration tasks during the development of a project that uses ORM.
|
||||
|
||||
Take a look at the :doc:`Installation and Configuration <configuration>`
|
||||
chapter for more information how to setup the console command.
|
||||
For the following examples, we will set up the CLI as ``bin/doctrine``.
|
||||
|
||||
Display Help Information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Type ``php vendor/bin/doctrine`` on the command line and you should see an
|
||||
overview of the available commands or use the --help flag to get
|
||||
information on the available commands. If you want to know more
|
||||
about the use of generate entities for example, you can call:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$> php vendor/bin/doctrine orm:generate-entities --help
|
||||
|
||||
|
||||
Configuration
|
||||
~~~~~~~~~~~~~
|
||||
Setting Up the Console
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Whenever the ``doctrine`` command line tool is invoked, it can
|
||||
access all Commands that were registered by a developer. There is no
|
||||
@@ -34,25 +20,33 @@ Doctrine DBAL and ORM. If you want to use additional commands you
|
||||
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 with
|
||||
``ConsoleRunner::createHelperSet``. Whenever you invoke the Doctrine
|
||||
binary, it searches the current directory for the file ``cli-config.php``.
|
||||
This file contains the project-specific configuration.
|
||||
``EntityManager``. You have to inject it into the console application.
|
||||
|
||||
Here is an example of a the project-specific ``cli-config.php``:
|
||||
Here is an example of a the project-specific ``bin/doctrine`` binary.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
// replace this with the path to your own project bootstrap file.
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
|
||||
// replace with path to your own project bootstrap file
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
|
||||
return ConsoleRunner::createHelperSet($entityManager);
|
||||
$commands = [
|
||||
// If you want to add your own custom console commands,
|
||||
// you can do so here.
|
||||
];
|
||||
|
||||
ConsoleRunner::run(
|
||||
new SingleManagerProvider($entityManager),
|
||||
$commands
|
||||
);
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -60,6 +54,18 @@ Here is an example of a the project-specific ``cli-config.php``:
|
||||
and use their facilities to access the Doctrine EntityManager and
|
||||
Connection Resources.
|
||||
|
||||
Display Help Information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Type ``php bin/doctrine`` on the command line and you should see an
|
||||
overview of the available commands or use the ``--help`` flag to get
|
||||
information on the available commands. If you want to know more
|
||||
about the use of generate entities for example, you can call:
|
||||
|
||||
::
|
||||
|
||||
$> php bin/doctrine orm:generate-entities --help
|
||||
|
||||
Command Overview
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -143,7 +149,7 @@ When using the SchemaTool class directly, create your schema using
|
||||
the ``createSchema()`` method. First create an instance of the
|
||||
``SchemaTool`` and pass it an instance of the ``EntityManager``
|
||||
that you want to use to create the schema. This method receives an
|
||||
array of ``ClassMetadataInfo`` instances.
|
||||
array of ``ClassMetadata`` instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -174,8 +180,8 @@ tables of the current model to clean up with orphaned tables.
|
||||
|
||||
You can also use database introspection to update your schema
|
||||
easily with the ``updateSchema()`` method. It will compare your
|
||||
existing database schema to the passed array of
|
||||
``ClassMetadataInfo`` instances.
|
||||
existing database schema to the passed array of ``ClassMetadata``
|
||||
instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -189,38 +195,35 @@ To create the schema use the ``create`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
To drop the schema use the ``drop`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
$ php bin/doctrine orm:schema-tool:drop
|
||||
|
||||
If you want to drop and then recreate the schema then use both
|
||||
options:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
$ php doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:drop
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
As you would think, if you want to update your schema use the
|
||||
``update`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:update
|
||||
$ php bin/doctrine orm:schema-tool:update
|
||||
|
||||
All of the above commands also accept a ``--dump-sql`` option that
|
||||
will output the SQL for the ran operation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:schema-tool:create --dump-sql
|
||||
|
||||
Before using the orm:schema-tool commands, remember to configure
|
||||
your cli-config.php properly.
|
||||
$ php bin/doctrine orm:schema-tool:create --dump-sql
|
||||
|
||||
Entity Generation
|
||||
-----------------
|
||||
@@ -229,9 +232,9 @@ Generate entity classes and method stubs from your mapping information.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:generate-entities
|
||||
$ php doctrine orm:generate-entities --update-entities
|
||||
$ php doctrine orm:generate-entities --regenerate-entities
|
||||
$ php bin/doctrine orm:generate-entities
|
||||
$ php bin/doctrine orm:generate-entities --update-entities
|
||||
$ php bin/doctrine orm:generate-entities --regenerate-entities
|
||||
|
||||
This command is not suited for constant usage. It is a little helper and does
|
||||
not support all the mapping edge cases very well. You still have to put work
|
||||
@@ -316,14 +319,14 @@ convert to and the path to generate it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
|
||||
$ php bin/doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml
|
||||
|
||||
Reverse Engineering
|
||||
-------------------
|
||||
|
||||
You can use the ``DatabaseDriver`` to reverse engineer a database
|
||||
to an array of ``ClassMetadataInfo`` instances and generate YAML,
|
||||
XML, etc. from them.
|
||||
You can use the ``DatabaseDriver`` to reverse engineer a database to an
|
||||
array of ``ClassMetadata`` instances and generate YAML, XML, etc. from
|
||||
them.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -366,7 +369,7 @@ You can also reverse engineer a database using the
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
|
||||
$ php bin/doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -393,6 +396,11 @@ You can either use the Doctrine Command Line Tool:
|
||||
|
||||
doctrine orm:validate-schema
|
||||
|
||||
If the validation fails, you can change the verbosity level to
|
||||
check the detected errors:
|
||||
|
||||
doctrine orm:validate-schema -v
|
||||
|
||||
Or you can trigger the validation manually:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -189,14 +189,25 @@ example we'll use an integer.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
#[Version, Column(type: 'integer')]
|
||||
private int $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="integer") */
|
||||
private $version;
|
||||
private int $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -222,14 +233,25 @@ timestamp or datetime):
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
#[Version, Column(type: 'datetime')]
|
||||
private DateTime $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="datetime") */
|
||||
private $version;
|
||||
private DateTime $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -279,15 +301,15 @@ either when calling ``EntityManager#find()``:
|
||||
<?php
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
|
||||
|
||||
$theEntityId = 1;
|
||||
$expectedVersion = 184;
|
||||
|
||||
|
||||
try {
|
||||
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
|
||||
|
||||
|
||||
// do the work
|
||||
|
||||
|
||||
$em->flush();
|
||||
} catch(OptimisticLockException $e) {
|
||||
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
|
||||
@@ -300,16 +322,16 @@ Or you can use ``EntityManager#lock()`` to find out:
|
||||
<?php
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
|
||||
|
||||
$theEntityId = 1;
|
||||
$expectedVersion = 184;
|
||||
|
||||
|
||||
$entity = $em->find('User', $theEntityId);
|
||||
|
||||
|
||||
try {
|
||||
// assert version
|
||||
$em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion);
|
||||
|
||||
|
||||
} catch(OptimisticLockException $e) {
|
||||
echo "Sorry, but someone else has already changed this entity. Please apply the changes again!";
|
||||
}
|
||||
@@ -348,7 +370,7 @@ See the example code, The form (GET Request):
|
||||
|
||||
<?php
|
||||
$post = $em->find('BlogPost', 123456);
|
||||
|
||||
|
||||
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
|
||||
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
|
||||
|
||||
@@ -359,7 +381,7 @@ And the change headline action (POST Request):
|
||||
<?php
|
||||
$postId = (int)$_GET['id'];
|
||||
$postVersion = (int)$_GET['version'];
|
||||
|
||||
|
||||
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
|
||||
|
||||
.. _transactions-and-concurrency_pessimistic-locking:
|
||||
@@ -405,5 +427,3 @@ You can use pessimistic locks in three different scenarios:
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ ask for an entity with a specific ID twice, it will return the same instance:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function testIdentityMap()
|
||||
public function testIdentityMap(): void
|
||||
{
|
||||
$objectA = $this->entityManager->find('EntityName', 1);
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
@@ -34,7 +34,7 @@ will still end up with the same reference:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function testIdentityMapReference()
|
||||
public function testIdentityMapReference(): void
|
||||
{
|
||||
$objectA = $this->entityManager->getReference('EntityName', 1);
|
||||
// check for proxyinterface
|
||||
@@ -104,7 +104,7 @@ How Doctrine Detects Changes
|
||||
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
|
||||
This means you map php objects into a relational database that don't
|
||||
necessarily know about the database at all. A natural question would now be,
|
||||
"how does Doctrine even detect objects have changed?".
|
||||
"how does Doctrine even detect objects have changed?".
|
||||
|
||||
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
|
||||
an object from the database Doctrine will keep a copy of all the properties and
|
||||
@@ -202,4 +202,3 @@ ClassMetadataFactory
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
tbr
|
||||
|
||||
|
||||
@@ -32,62 +32,62 @@ information about its type and if it's the owning or inverse side.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="string") */
|
||||
private $id;
|
||||
|
||||
#[Id, GeneratedValue, Column]
|
||||
private int|null $id = null;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
|
||||
*
|
||||
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
|
||||
* @JoinTable(name="user_favorite_comments")
|
||||
* @var Collection<int, Comment>
|
||||
*/
|
||||
private $favorites;
|
||||
|
||||
#[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')]
|
||||
#[JoinTable(name: 'user_favorite_comments')]
|
||||
private Collection $favorites;
|
||||
|
||||
/**
|
||||
* Unidirectional - Many users have marked many comments as read
|
||||
*
|
||||
* @ManyToMany(targetEntity="Comment")
|
||||
* @JoinTable(name="user_read_comments")
|
||||
* @var Collection<int, Comment>
|
||||
*/
|
||||
private $commentsRead;
|
||||
|
||||
#[ManyToMany(targetEntity: Comment::class)]
|
||||
#[JoinTable(name: 'user_read_comments')]
|
||||
private Collection $commentsRead;
|
||||
|
||||
/**
|
||||
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||
*
|
||||
* @OneToMany(targetEntity="Comment", mappedBy="author")
|
||||
* @var Collection<int, Comment>
|
||||
*/
|
||||
private $commentsAuthored;
|
||||
|
||||
/**
|
||||
* Unidirectional - Many-To-One
|
||||
*
|
||||
* @ManyToOne(targetEntity="Comment")
|
||||
*/
|
||||
private $firstComment;
|
||||
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
|
||||
private Collection $commentsAuthored;
|
||||
|
||||
/** Unidirectional - Many-To-One */
|
||||
#[ManyToOne(targetEntity: Comment::class)]
|
||||
private Comment|null $firstComment = null;
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
|
||||
#[Entity]
|
||||
class Comment
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="string") */
|
||||
private $id;
|
||||
|
||||
#[Id, GeneratedValue, Column]
|
||||
private string $id;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
|
||||
*
|
||||
* @ManyToMany(targetEntity="User", mappedBy="favorites")
|
||||
* @var Collection<int, User>
|
||||
*/
|
||||
private $userFavorites;
|
||||
|
||||
#[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')]
|
||||
private Collection $userFavorites;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
|
||||
*
|
||||
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
|
||||
*/
|
||||
private $author;
|
||||
#[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')]
|
||||
private User|null $author = null;
|
||||
}
|
||||
|
||||
This two entities generate the following MySQL Schema (Foreign Key
|
||||
@@ -100,19 +100,19 @@ definitions omitted):
|
||||
firstComment_id VARCHAR(255) DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
|
||||
CREATE TABLE Comment (
|
||||
id VARCHAR(255) NOT NULL,
|
||||
author_id VARCHAR(255) DEFAULT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
|
||||
CREATE TABLE user_favorite_comments (
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
favorite_comment_id VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY(user_id, favorite_comment_id)
|
||||
) ENGINE = InnoDB;
|
||||
|
||||
|
||||
CREATE TABLE user_read_comments (
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
comment_id VARCHAR(255) NOT NULL,
|
||||
@@ -132,11 +132,12 @@ relations of the ``User``:
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
public function getReadComments() {
|
||||
/** @return Collection<int, Comment> */
|
||||
public function getReadComments(): Collection {
|
||||
return $this->commentsRead;
|
||||
}
|
||||
|
||||
public function setFirstComment(Comment $c) {
|
||||
|
||||
public function setFirstComment(Comment $c): void {
|
||||
$this->firstComment = $c;
|
||||
}
|
||||
}
|
||||
@@ -148,17 +149,17 @@ The interaction code would then look like in the following snippet
|
||||
|
||||
<?php
|
||||
$user = $em->find('User', $userId);
|
||||
|
||||
|
||||
// unidirectional many to many
|
||||
$comment = $em->find('Comment', $readCommentId);
|
||||
$user->getReadComments()->add($comment);
|
||||
|
||||
|
||||
$em->flush();
|
||||
|
||||
|
||||
// unidirectional many to one
|
||||
$myFirstComment = new Comment();
|
||||
$user->setFirstComment($myFirstComment);
|
||||
|
||||
|
||||
$em->persist($myFirstComment);
|
||||
$em->flush();
|
||||
|
||||
@@ -171,40 +172,43 @@ fields on both sides:
|
||||
class User
|
||||
{
|
||||
// ..
|
||||
|
||||
public function getAuthoredComments() {
|
||||
|
||||
/** @return Collection<int, Comment> */
|
||||
public function getAuthoredComments(): Collection {
|
||||
return $this->commentsAuthored;
|
||||
}
|
||||
|
||||
public function getFavoriteComments() {
|
||||
|
||||
/** @return Collection<int, Comment> */
|
||||
public function getFavoriteComments(): Collection {
|
||||
return $this->favorites;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Comment
|
||||
{
|
||||
// ...
|
||||
|
||||
public function getUserFavorites() {
|
||||
|
||||
/** @return Collection<int, User> */
|
||||
public function getUserFavorites(): Collection {
|
||||
return $this->userFavorites;
|
||||
}
|
||||
|
||||
public function setAuthor(User $author = null) {
|
||||
|
||||
public function setAuthor(User|null $author = null): void {
|
||||
$this->author = $author;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Many-to-Many
|
||||
$user->getFavorites()->add($favoriteComment);
|
||||
$favoriteComment->getUserFavorites()->add($user);
|
||||
|
||||
|
||||
$em->flush();
|
||||
|
||||
|
||||
// Many-To-One / One-To-Many Bidirectional
|
||||
$newComment = new Comment();
|
||||
$user->getAuthoredComments()->add($newComment);
|
||||
$newComment->setAuthor($user);
|
||||
|
||||
|
||||
$em->persist($newComment);
|
||||
$em->flush();
|
||||
|
||||
@@ -225,10 +229,10 @@ element. Here are some examples:
|
||||
// Remove by Elements
|
||||
$user->getComments()->removeElement($comment);
|
||||
$comment->setAuthor(null);
|
||||
|
||||
|
||||
$user->getFavorites()->removeElement($comment);
|
||||
$comment->getUserFavorites()->removeElement($user);
|
||||
|
||||
|
||||
// Remove by Key
|
||||
$user->getComments()->remove($ithComment);
|
||||
$comment->setAuthor(null);
|
||||
@@ -240,7 +244,7 @@ Notice how both sides of the bidirectional association are always
|
||||
updated. Unidirectional associations are consequently simpler to
|
||||
handle.
|
||||
|
||||
Also note that if you use type-hinting in your methods, you will
|
||||
Also note that if you use type-hinting in your methods, you will
|
||||
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
|
||||
otherwise ``setAddress(null)`` will fail to remove the association.
|
||||
Another way to deal with this is to provide a special method, like
|
||||
@@ -271,8 +275,8 @@ entities that have been re-added to the collection.
|
||||
|
||||
Say you clear a collection of tags by calling
|
||||
``$post->getTags()->clear();`` and then call
|
||||
``$post->getTags()->add($tag)``. This will not recognize the tag having
|
||||
already been added previously and will consequently issue two separate database
|
||||
``$post->getTags()->add($tag)``. This will not recognize the tag having
|
||||
already been added previously and will consequently issue two separate database
|
||||
calls.
|
||||
|
||||
Association Management Methods
|
||||
@@ -292,43 +296,43 @@ example that encapsulate much of the association management code:
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
public function markCommentRead(Comment $comment) {
|
||||
public function markCommentRead(Comment $comment): void {
|
||||
// Collections implement ArrayAccess
|
||||
$this->commentsRead[] = $comment;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment) {
|
||||
|
||||
public function addComment(Comment $comment): void {
|
||||
if (count($this->commentsAuthored) == 0) {
|
||||
$this->setFirstComment($comment);
|
||||
}
|
||||
$this->comments[] = $comment;
|
||||
$comment->setAuthor($this);
|
||||
}
|
||||
|
||||
private function setFirstComment(Comment $c) {
|
||||
|
||||
private function setFirstComment(Comment $c): void {
|
||||
$this->firstComment = $c;
|
||||
}
|
||||
|
||||
public function addFavorite(Comment $comment) {
|
||||
|
||||
public function addFavorite(Comment $comment): void {
|
||||
$this->favorites->add($comment);
|
||||
$comment->addUserFavorite($this);
|
||||
}
|
||||
|
||||
public function removeFavorite(Comment $comment) {
|
||||
|
||||
public function removeFavorite(Comment $comment): void {
|
||||
$this->favorites->removeElement($comment);
|
||||
$comment->removeUserFavorite($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Comment
|
||||
{
|
||||
// ..
|
||||
|
||||
public function addUserFavorite(User $user) {
|
||||
|
||||
public function addUserFavorite(User $user): void {
|
||||
$this->userFavorites[] = $user;
|
||||
}
|
||||
|
||||
public function removeUserFavorite(User $user) {
|
||||
|
||||
public function removeUserFavorite(User $user): void {
|
||||
$this->userFavorites->removeElement($user);
|
||||
}
|
||||
}
|
||||
@@ -356,7 +360,8 @@ the details inside the classes can be challenging.
|
||||
|
||||
<?php
|
||||
class User {
|
||||
public function getReadComments() {
|
||||
/** @return array<int, Comment> */
|
||||
public function getReadComments(): array {
|
||||
return $this->commentsRead->toArray();
|
||||
}
|
||||
}
|
||||
@@ -373,7 +378,7 @@ as your preferences.
|
||||
Synchronizing Bidirectional Collections
|
||||
---------------------------------------
|
||||
|
||||
In the case of Many-To-Many associations you as the developer have the
|
||||
In the case of Many-To-Many associations you as the developer have the
|
||||
responsibility of keeping the collections on the owning and inverse side
|
||||
in sync when you apply changes to them. Doctrine can only
|
||||
guarantee a consistent state for the hydration, not for your client
|
||||
@@ -387,7 +392,7 @@ can show the possible caveats you can encounter:
|
||||
<?php
|
||||
$user->getFavorites()->add($favoriteComment);
|
||||
// not calling $favoriteComment->getUserFavorites()->add($user);
|
||||
|
||||
|
||||
$user->getFavorites()->contains($favoriteComment); // TRUE
|
||||
$favoriteComment->getUserFavorites()->contains($user); // FALSE
|
||||
|
||||
@@ -422,7 +427,7 @@ comment might look like in your controller (without ``cascade: persist``):
|
||||
$user = new User();
|
||||
$myFirstComment = new Comment();
|
||||
$user->addComment($myFirstComment);
|
||||
|
||||
|
||||
$em->persist($user);
|
||||
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
|
||||
$em->flush();
|
||||
@@ -437,8 +442,10 @@ only accessing it through the User entity:
|
||||
// User entity
|
||||
class User
|
||||
{
|
||||
private $id;
|
||||
private $comments;
|
||||
private int $id;
|
||||
|
||||
/** @var Collection<int, Comment> */
|
||||
private Collection $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -464,11 +471,8 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||
*
|
||||
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
|
||||
*/
|
||||
/** Bidirectional - One-To-Many (INVERSE SIDE) */
|
||||
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author', cascade: ['persist', 'remove'])]
|
||||
private $commentsAuthored;
|
||||
// ...
|
||||
}
|
||||
@@ -480,7 +484,7 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||
<?php
|
||||
$user = new User();
|
||||
$user->comment('Lorem ipsum', new DateTime());
|
||||
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
@@ -559,6 +563,13 @@ OrphanRemoval works with one-to-one, one-to-many and many-to-many associations.
|
||||
If you neglect this assumption your entities will get deleted by Doctrine even if
|
||||
you assigned the orphaned entity to another one.
|
||||
|
||||
.. note::
|
||||
|
||||
``orphanRemoval=true`` option should be used in combination with ``cascade=["persist"]`` option
|
||||
as the child entity, that is manually persisted, will not be deleted automatically by Doctrine
|
||||
when a collection is still an instance of ArrayCollection (before first flush / hydration).
|
||||
This is a Doctrine limitation since ArrayCollection does not have access to a UnitOfWork.
|
||||
|
||||
As a better example consider an Addressbook application where you have Contacts, Addresses
|
||||
and StandingData:
|
||||
|
||||
@@ -570,31 +581,30 @@ and StandingData:
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
#[Entity]
|
||||
class Contact
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
/** @OneToOne(targetEntity="StandingData", orphanRemoval=true) */
|
||||
private $standingData;
|
||||
#[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)]
|
||||
private StandingData|null $standingData = null;
|
||||
|
||||
/** @OneToMany(targetEntity="Address", mappedBy="contact", orphanRemoval=true) */
|
||||
private $addresses;
|
||||
/** @var Collection<int, Address> */
|
||||
#[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)]
|
||||
private Collection $addresses;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addresses = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function newStandingData(StandingData $sd)
|
||||
public function newStandingData(StandingData $sd): void
|
||||
{
|
||||
$this->standingData = $sd;
|
||||
}
|
||||
|
||||
public function removeAddress($pos)
|
||||
public function removeAddress(int $pos): void
|
||||
{
|
||||
unset($this->addresses[$pos]);
|
||||
}
|
||||
@@ -612,10 +622,10 @@ Now two examples of what happens when you remove the references:
|
||||
|
||||
$em->flush();
|
||||
|
||||
In this case you have not only changed the ``Contact`` entity itself but
|
||||
you have also removed the references for standing data and as well as one
|
||||
address reference. When flush is called not only are the references removed
|
||||
but both the old standing data and the one address entity are also deleted
|
||||
In this case you have not only changed the ``Contact`` entity itself but
|
||||
you have also removed the references for standing data and as well as one
|
||||
address reference. When flush is called not only are the references removed
|
||||
but both the old standing data and the one address entity are also deleted
|
||||
from the database.
|
||||
|
||||
.. _filtering-collections:
|
||||
|
||||
@@ -95,28 +95,29 @@ from newly opened EntityManager.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $headline;
|
||||
#[Column(type: 'string')]
|
||||
private string $headline;
|
||||
|
||||
/** @ManyToOne(targetEntity="User") */
|
||||
private $author;
|
||||
#[ManyToOne(targetEntity: User::class)]
|
||||
private User|null $author = null;
|
||||
|
||||
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
|
||||
private $comments;
|
||||
/** @var Collection<int, Comment> */
|
||||
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
|
||||
private Collection $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getAuthor() { return $this->author; }
|
||||
public function getComments() { return $this->comments; }
|
||||
public function getAuthor(): User|null { return $this->author; }
|
||||
public function getComments(): Collection { return $this->comments; }
|
||||
}
|
||||
|
||||
$article = $em->find('Article', 1);
|
||||
@@ -170,12 +171,12 @@ methods along the lines of the ``getName()`` method shown below:
|
||||
<?php
|
||||
class UserProxy extends User implements Proxy
|
||||
{
|
||||
private function _load()
|
||||
private function _load(): void
|
||||
{
|
||||
// lazy loading code
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
$this->_load();
|
||||
return parent::getName();
|
||||
@@ -304,7 +305,7 @@ as follows:
|
||||
- A removed entity X will be removed from the database as a result
|
||||
of the flush operation.
|
||||
|
||||
After an entity has been removed its in-memory state is the same as
|
||||
After an entity has been removed, its in-memory state is the same as
|
||||
before the removal, except for generated identifiers.
|
||||
|
||||
Removing an entity will also automatically delete any existing
|
||||
@@ -339,8 +340,8 @@ in multiple ways with very different performance impacts.
|
||||
.. note::
|
||||
|
||||
Calling ``remove`` on an entity will remove the object from the identity
|
||||
map and therefore detach it. Querying the same entity again, for example
|
||||
via a lazy loaded relation, will return a new object.
|
||||
map and therefore detach it. Querying the same entity again, for example
|
||||
via a lazy loaded relation, will return a new object.
|
||||
|
||||
|
||||
Detaching entities
|
||||
@@ -833,7 +834,7 @@ By default the EntityManager returns a default implementation of
|
||||
``Doctrine\ORM\EntityRepository`` when you call
|
||||
``EntityManager#getRepository($entityClass)``. You can overwrite
|
||||
this behaviour by specifying the class name of your own Entity
|
||||
Repository in the Annotation, XML or YAML metadata. In large
|
||||
Repository in the Attribute, Annotation, XML or YAML metadata. In large
|
||||
applications that require lots of specialized DQL queries using a
|
||||
custom repository is one recommended way of grouping these queries
|
||||
in a central location.
|
||||
@@ -843,12 +844,11 @@ in a central location.
|
||||
<?php
|
||||
namespace MyDomain\Model;
|
||||
|
||||
use MyDomain\Model\UserRepository;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
class User
|
||||
{
|
||||
|
||||
@@ -856,7 +856,8 @@ in a central location.
|
||||
|
||||
class UserRepository extends EntityRepository
|
||||
{
|
||||
public function getAllAdminUsers()
|
||||
/** @return Collection<User> */
|
||||
public function getAllAdminUsers(): Collection
|
||||
{
|
||||
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
|
||||
->getResult();
|
||||
@@ -871,5 +872,3 @@ You can access your repository now by calling:
|
||||
// $em instanceof EntityManager
|
||||
|
||||
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,34 @@ and year of production as primary keys:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace VehicleCatalogue\Model;
|
||||
|
||||
#[Entity]
|
||||
class Car
|
||||
{
|
||||
public function __construct(
|
||||
#[Id, Column(type: 'string')]
|
||||
private string $name,
|
||||
#[Id, Column(type: 'integer')]
|
||||
private int $year,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getModelName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getYearOfProduction(): int
|
||||
{
|
||||
return $this->year;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
namespace VehicleCatalogue\Model;
|
||||
@@ -34,9 +61,9 @@ and year of production as primary keys:
|
||||
class Car
|
||||
{
|
||||
/** @Id @Column(type="string") */
|
||||
private $name;
|
||||
private string $name;
|
||||
/** @Id @Column(type="integer") */
|
||||
private $year;
|
||||
private int $year;
|
||||
|
||||
public function __construct($name, $year)
|
||||
{
|
||||
@@ -44,17 +71,38 @@ and year of production as primary keys:
|
||||
$this->year = $year;
|
||||
}
|
||||
|
||||
public function getModelName()
|
||||
public function getModelName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getYearOfProduction()
|
||||
public function getYearOfProduction(): int
|
||||
{
|
||||
return $this->year;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
/** @Id @OneToOne(targetEntity="User") */
|
||||
private User|null $user = null;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -131,7 +179,7 @@ of one or many parent entities.
|
||||
The semantics of mapping identity through foreign entities are easy:
|
||||
|
||||
- Only allowed on Many-To-One or One-To-One associations.
|
||||
- Plug an ``@Id`` annotation onto every association.
|
||||
- Plug an ``#[Id]`` attribute onto every association.
|
||||
- Set an attribute ``association-key`` with the field name of the association in XML.
|
||||
- Set a key ``associationKey:`` with the field name of the association in YAML.
|
||||
|
||||
@@ -155,16 +203,17 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
class Article
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
private int|null $id = null;
|
||||
/** @Column(type="string") */
|
||||
private $title;
|
||||
private string $title;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
|
||||
* @var Collection<int, ArticleAttribute>
|
||||
*/
|
||||
private $attributes;
|
||||
private Collection $attributes;
|
||||
|
||||
public function addAttribute($name, $value)
|
||||
public function addAttribute($name, $value): void
|
||||
{
|
||||
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
|
||||
}
|
||||
@@ -176,13 +225,13 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
class ArticleAttribute
|
||||
{
|
||||
/** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */
|
||||
private $article;
|
||||
private Article|null $article;
|
||||
|
||||
/** @Id @Column(type="string") */
|
||||
private $attribute;
|
||||
private string $attribute;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $value;
|
||||
private string $value;
|
||||
|
||||
public function __construct($name, $value, $article)
|
||||
{
|
||||
@@ -192,6 +241,51 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace Application\Model;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
#[Column(type: 'string')]
|
||||
private string $title;
|
||||
|
||||
/** @var ArrayCollection<string, ArticleAttribute> */
|
||||
#[OneToMany(targetEntity: ArticleAttribute::class, mappedBy: 'article', cascade: ['ALL'], indexBy: 'attribute')]
|
||||
private Collection $attributes;
|
||||
|
||||
public function addAttribute(string $name, ArticleAttribute $value): void
|
||||
{
|
||||
$this->attributes[$name] = new ArticleAttribute($name, $value, $this);
|
||||
}
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
class ArticleAttribute
|
||||
{
|
||||
#[Id, ManyToOne(targetEntity: Article::class, inversedBy: 'attributes')]
|
||||
private Article $article;
|
||||
|
||||
#[Id, Column(type: 'string')]
|
||||
private string $attribute;
|
||||
|
||||
#[Column(type: 'string')]
|
||||
private string $value;
|
||||
|
||||
public function __construct(string $name, string $value, Article $article)
|
||||
{
|
||||
$this->attribute = $name;
|
||||
$this->value = $value;
|
||||
$this->article = $article;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
@@ -202,7 +296,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
<id name="attribute" type="string" />
|
||||
|
||||
|
||||
<field name="value" type="string" />
|
||||
|
||||
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
|
||||
@@ -237,25 +331,22 @@ One good example for this is a user-address relationship:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
#[Entity]
|
||||
class Address
|
||||
{
|
||||
/** @Id @OneToOne(targetEntity="User") */
|
||||
private $user;
|
||||
#[Id, OneToOne(targetEntity: User::class)]
|
||||
private User|null $user = null;
|
||||
}
|
||||
|
||||
.. code-block:: yaml
|
||||
@@ -288,68 +379,70 @@ of products purchased and maybe even the current price.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class Order
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
/** @ManyToOne(targetEntity="Customer") */
|
||||
private $customer;
|
||||
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
|
||||
private $items;
|
||||
/** @var ArrayCollection<int, OrderItem> */
|
||||
#[OneToMany(targetEntity: OrderItem::class, mappedBy: 'order')]
|
||||
private Collection $items;
|
||||
|
||||
/** @Column(type="boolean") */
|
||||
private $paid = false;
|
||||
/** @Column(type="boolean") */
|
||||
private $shipped = false;
|
||||
/** @Column(type="datetime") */
|
||||
private $created;
|
||||
#[Column(type: 'boolean')]
|
||||
private bool $paid = false;
|
||||
#[Column(type: 'boolean')]
|
||||
private bool $shipped = false;
|
||||
#[Column(type: 'datetime')]
|
||||
private DateTime $created;
|
||||
|
||||
public function __construct(Customer $customer)
|
||||
{
|
||||
$this->customer = $customer;
|
||||
public function __construct(
|
||||
#[ManyToOne(targetEntity: Customer::class)]
|
||||
private Customer $customer,
|
||||
) {
|
||||
$this->items = new ArrayCollection();
|
||||
$this->created = new \DateTime("now");
|
||||
$this->created = new DateTime("now");
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class Product
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $name;
|
||||
#[Column(type: 'string')]
|
||||
private string $name;
|
||||
|
||||
/** @Column(type="decimal") */
|
||||
private $currentPrice;
|
||||
#[Column(type: 'decimal')]
|
||||
private float $currentPrice;
|
||||
|
||||
public function getCurrentPrice()
|
||||
public function getCurrentPrice(): float
|
||||
{
|
||||
return $this->currentPrice;
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class OrderItem
|
||||
{
|
||||
/** @Id @ManyToOne(targetEntity="Order") */
|
||||
private $order;
|
||||
#[Id, ManyToOne(targetEntity: Order::class)]
|
||||
private Order|null $order = null;
|
||||
|
||||
/** @Id @ManyToOne(targetEntity="Product") */
|
||||
private $product;
|
||||
#[Id, ManyToOne(targetEntity: Product::class)]
|
||||
private Product|null $product = null;
|
||||
|
||||
/** @Column(type="integer") */
|
||||
private $amount = 1;
|
||||
#[Column(type: 'integer')]
|
||||
private int $amount = 1;
|
||||
|
||||
/** @Column(type="decimal") */
|
||||
private $offeredPrice;
|
||||
#[Column(type: 'decimal')]
|
||||
private float $offeredPrice;
|
||||
|
||||
public function __construct(Order $order, Product $product, $amount = 1)
|
||||
public function __construct(Order $order, Product $product, int $amount = 1)
|
||||
{
|
||||
$this->order = $order;
|
||||
$this->product = $product;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
Separating Concerns using Embeddables
|
||||
-------------------------------------
|
||||
=====================================
|
||||
|
||||
Embeddables are classes which are not entities themselves, but are embedded
|
||||
in entities and can also be queried in DQL. You'll mostly want to use them
|
||||
to reduce duplication or separating concerns. Value objects such as date range
|
||||
or address are the primary use case for this feature.
|
||||
or address are the primary use case for this feature.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -17,7 +17,34 @@ instead of simply adding the respective columns to the ``User`` class.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
#[Embedded(class: Address::class)]
|
||||
private Address $address;
|
||||
}
|
||||
|
||||
#[Embeddable]
|
||||
class Address
|
||||
{
|
||||
#[Column(type: "string")]
|
||||
private string $street;
|
||||
|
||||
#[Column(type: "string")]
|
||||
private string $postalCode;
|
||||
|
||||
#[Column(type: "string")]
|
||||
private string $city;
|
||||
|
||||
#[Column(type: "string")]
|
||||
private string $country;
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
|
||||
@@ -25,23 +52,23 @@ instead of simply adding the respective columns to the ``User`` class.
|
||||
class User
|
||||
{
|
||||
/** @Embedded(class = "Address") */
|
||||
private $address;
|
||||
private Address $address;
|
||||
}
|
||||
|
||||
/** @Embeddable */
|
||||
class Address
|
||||
{
|
||||
/** @Column(type = "string") */
|
||||
private $street;
|
||||
private string $street;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $postalCode;
|
||||
private string $postalCode;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $city;
|
||||
private string $city;
|
||||
|
||||
/** @Column(type = "string") */
|
||||
private $country;
|
||||
private string $country;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -109,7 +136,18 @@ The following example shows you how to set your prefix to ``myPrefix_``:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
#[Embedded(class: Address::class, columnPrefix: "myPrefix_")]
|
||||
private Address $address;
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
|
||||
@@ -140,7 +178,18 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
#[Embedded(class: Address::class, columnPrefix: false)]
|
||||
private Address $address;
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
|
||||
@@ -148,9 +197,15 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
|
||||
class User
|
||||
{
|
||||
/** @Embedded(class = "Address", columnPrefix = false) */
|
||||
private $address;
|
||||
private Address $address;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="User">
|
||||
<embedded name="address" class="Address" use-column-prefix="false" />
|
||||
</entity>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
@@ -160,12 +215,6 @@ directly, set ``columnPrefix=false`` (``use-column-prefix="false"`` for XML):
|
||||
class: Address
|
||||
columnPrefix: false
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<entity name="User">
|
||||
<embedded name="address" class="Address" use-column-prefix="false" />
|
||||
</entity>
|
||||
|
||||
|
||||
DQL
|
||||
---
|
||||
@@ -176,4 +225,3 @@ as if they were declared in the ``User`` class:
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u FROM User u WHERE u.address.city = :myCity
|
||||
|
||||
|
||||
@@ -52,7 +52,20 @@ switch to extra lazy as shown in these examples:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\CMS;
|
||||
|
||||
#[Entity]
|
||||
class CmsGroup
|
||||
{
|
||||
/** @var Collection<int, CmsUser> */
|
||||
#[ManyToMany(targetEntity: CmsUser::class, mappedBy: 'groups', fetch: 'EXTRA_LAZY')]
|
||||
public Collection $users;
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\CMS;
|
||||
@@ -64,8 +77,9 @@ switch to extra lazy as shown in these examples:
|
||||
{
|
||||
/**
|
||||
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
|
||||
* @var Collection<int, CmsUser>
|
||||
*/
|
||||
public $users;
|
||||
public Collection $users;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -92,4 +106,3 @@ switch to extra lazy as shown in these examples:
|
||||
targetEntity: CmsUser
|
||||
mappedBy: groups
|
||||
fetch: EXTRA_LAZY
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ that directory with the following contents:
|
||||
"require": {
|
||||
"doctrine/orm": "^2.11.0",
|
||||
"doctrine/dbal": "^3.2",
|
||||
"doctrine/annotations": "1.13.2",
|
||||
"symfony/yaml": "^5.4",
|
||||
"symfony/cache": "^5.4"
|
||||
},
|
||||
@@ -137,20 +136,29 @@ step:
|
||||
|
||||
<?php
|
||||
// bootstrap.php
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
// Create a simple "default" Doctrine ORM configuration for Annotations
|
||||
$isDevMode = true;
|
||||
$proxyDir = null;
|
||||
$cache = null;
|
||||
$useSimpleAnnotationReader = false;
|
||||
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/src"), $isDevMode, $proxyDir, $cache, $useSimpleAnnotationReader);
|
||||
// or if you prefer yaml or XML
|
||||
// $config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode);
|
||||
// $config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode);
|
||||
// Create a simple "default" Doctrine ORM configuration for Attributes
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration(
|
||||
paths: array(__DIR__."/src"),
|
||||
isDevMode: true,
|
||||
);
|
||||
// or if you prefer annotation, YAML or XML
|
||||
// $config = ORMSetup::createAnnotationMetadataConfiguration(
|
||||
// paths: array(__DIR__."/src"),
|
||||
// isDevMode: true,
|
||||
// );
|
||||
// $config = ORMSetup::createXMLMetadataConfiguration(
|
||||
// paths: array(__DIR__."/config/xml"),
|
||||
// isDevMode: true,
|
||||
//);
|
||||
// $config = ORMSetup::createYAMLMetadataConfiguration(
|
||||
// paths: array(__DIR__."/config/yaml"),
|
||||
// isDevMode: true,
|
||||
// );
|
||||
|
||||
// database configuration parameters
|
||||
$conn = array(
|
||||
@@ -165,15 +173,11 @@ step:
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. note::
|
||||
It is recommended not to use the SimpleAnnotationReader because its
|
||||
usage will be removed for version 3.0.
|
||||
|
||||
The ``require_once`` statement sets up the class autoloading for Doctrine and
|
||||
its dependencies using Composer's autoloader.
|
||||
|
||||
The second block consists of the instantiation of the ORM
|
||||
``Configuration`` object using the Setup helper. It assumes a bunch
|
||||
``Configuration`` object using the ``ORMSetup`` helper. It assumes a bunch
|
||||
of defaults that you don't have to bother about for now. You can
|
||||
read up on the configuration details in the
|
||||
:doc:`reference chapter on configuration <../reference/configuration>`.
|
||||
@@ -191,22 +195,35 @@ Generating the Database Schema
|
||||
|
||||
Doctrine has a command-line interface that allows you to access the SchemaTool,
|
||||
a component that can generate a relational database schema based entirely on the
|
||||
defined entity classes and their metadata. For this tool to work, a
|
||||
``cli-config.php`` file must exist in the project root directory:
|
||||
defined entity classes and their metadata. For this tool to work, you need to
|
||||
create an executable console script as described in the
|
||||
:doc:`tools chapter <../reference/tools>`.
|
||||
|
||||
If you created the ``bootstrap.php`` file as described in the previous section,
|
||||
that script could look like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once "bootstrap.php";
|
||||
// bin/doctrine
|
||||
|
||||
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
|
||||
Now call the Doctrine command-line tool:
|
||||
// Adjust this path to your actual bootstrap.php
|
||||
require __DIR__ . 'path/to/your/bootstrap.php';
|
||||
|
||||
ConsoleRunner::run(
|
||||
new SingleManagerProvider($entityManager)
|
||||
);
|
||||
|
||||
In the following examples, we will assume that this script has been created as
|
||||
``bin/doctrine``.
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
Since we haven't added any entity metadata in ``src`` yet, you'll see a message
|
||||
stating "No Metadata Classes to process." In the next section, we'll create a
|
||||
@@ -218,14 +235,14 @@ You can easily recreate the database using the following commands:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:drop --force
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
$ php bin/doctrine orm:schema-tool:drop --force
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
|
||||
Or you can use the update functionality:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
$ php bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
The updating of databases uses a diff algorithm for a given
|
||||
database schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
|
||||
@@ -243,14 +260,8 @@ entity definition:
|
||||
// src/Product.php
|
||||
class Product
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
private int|null $id = null;
|
||||
private string $name;
|
||||
}
|
||||
|
||||
When creating entity classes, all of the fields should be ``private``.
|
||||
@@ -487,14 +498,35 @@ the ``Product`` entity to Doctrine using a metadata language. The metadata
|
||||
language describes how entities, their properties and references should be
|
||||
persisted and what constraints should be applied to them.
|
||||
|
||||
Metadata for an Entity can be configured using DocBlock annotations directly
|
||||
in the Entity class itself, or in an external XML or YAML file. This Getting
|
||||
Started guide will demonstrate metadata mappings using all three methods,
|
||||
but you only need to choose one.
|
||||
Metadata for an Entity can be configured using attributes directly in
|
||||
the Entity class itself, or in an external XML or YAML file. This
|
||||
Getting Started guide will demonstrate metadata mappings using all three
|
||||
methods, but you only need to choose one.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
// src/Product.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'products')]
|
||||
class Product
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\GeneratedValue]
|
||||
private int|null $id = null;
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $name;
|
||||
|
||||
// .. (other code)
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
// src/Product.php
|
||||
@@ -512,11 +544,11 @@ but you only need to choose one.
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
private int|null $id = null;
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
private $name;
|
||||
private string $name;
|
||||
|
||||
// .. (other code)
|
||||
}
|
||||
@@ -571,7 +603,7 @@ let's update the database schema:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
$ php bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
|
||||
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
|
||||
statements to be executed and then printed to the screen.
|
||||
@@ -695,49 +727,35 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="bugs")
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'bugs')]
|
||||
class Bug
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\GeneratedValue]
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $description;
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $description;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime")
|
||||
* @var DateTime
|
||||
*/
|
||||
private $created;
|
||||
#[ORM\Column(type: 'datetime')]
|
||||
private DateTime $created;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $status;
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $status;
|
||||
|
||||
public function getId()
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription($description)
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
@@ -747,17 +765,17 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
$this->created = $created;
|
||||
}
|
||||
|
||||
public function getCreated()
|
||||
public function getCreated(): DateTime
|
||||
{
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function setStatus($status)
|
||||
public function setStatus($status): void
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function getStatus()
|
||||
public function getStatus():string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
@@ -770,37 +788,31 @@ classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="users")
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'users')]
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
/** @var int */
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
/** @var string */
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $name;
|
||||
|
||||
public function getId()
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
public function setName(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
@@ -829,13 +841,16 @@ domain model to match the requirements:
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
class Bug
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
private $products;
|
||||
/** @var Collection<int, Product> */
|
||||
private Collection $products;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -853,8 +868,10 @@ domain model to match the requirements:
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
private $reportedBugs;
|
||||
private $assignedBugs;
|
||||
/** @var Collection<int, Bug> */
|
||||
private Collection $reportedBugs;
|
||||
/** @var Collection<int, Bug> */
|
||||
private Collection $assignedBugs;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -929,27 +946,27 @@ the bi-directional reference:
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
private $engineer;
|
||||
private $reporter;
|
||||
private User $engineer;
|
||||
private User $reporter;
|
||||
|
||||
public function setEngineer(User $engineer)
|
||||
public function setEngineer(User $engineer): void
|
||||
{
|
||||
$engineer->assignedToBug($this);
|
||||
$this->engineer = $engineer;
|
||||
}
|
||||
|
||||
public function setReporter(User $reporter)
|
||||
public function setReporter(User $reporter): void
|
||||
{
|
||||
$reporter->addReportedBug($this);
|
||||
$this->reporter = $reporter;
|
||||
}
|
||||
|
||||
public function getEngineer()
|
||||
public function getEngineer(): User
|
||||
{
|
||||
return $this->engineer;
|
||||
}
|
||||
|
||||
public function getReporter()
|
||||
public function getReporter(): User
|
||||
{
|
||||
return $this->reporter;
|
||||
}
|
||||
@@ -963,15 +980,17 @@ the bi-directional reference:
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
private $reportedBugs;
|
||||
private $assignedBugs;
|
||||
/** @var Collection<int, Bug> */
|
||||
private Collection $reportedBugs;
|
||||
/** @var Collection<int, Bug> */
|
||||
private Collection $assignedBugs;
|
||||
|
||||
public function addReportedBug(Bug $bug)
|
||||
public function addReportedBug(Bug $bug): void
|
||||
{
|
||||
$this->reportedBugs[] = $bug;
|
||||
}
|
||||
|
||||
public function assignedToBug(Bug $bug)
|
||||
public function assignedToBug(Bug $bug): void
|
||||
{
|
||||
$this->assignedBugs[] = $bug;
|
||||
}
|
||||
@@ -1015,14 +1034,16 @@ the database that points from Bugs to Products.
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
private $products;
|
||||
/** @var Collection<int, Product> */
|
||||
private Collection $products;
|
||||
|
||||
public function assignToProduct(Product $product)
|
||||
public function assignToProduct(Product $product): void
|
||||
{
|
||||
$this->products[] = $product;
|
||||
}
|
||||
|
||||
public function getProducts()
|
||||
/** @return Collection<int, Product> */
|
||||
public function getProducts(): Collection
|
||||
{
|
||||
return $this->products;
|
||||
}
|
||||
@@ -1033,7 +1054,46 @@ Lets add metadata mappings for the ``Bug`` entity, as we did for
|
||||
the ``Product`` before:
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'bugs')]
|
||||
class Bug
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $description;
|
||||
|
||||
#[ORM\Column(type: 'datetime')]
|
||||
private DateTime $created;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $status;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'assignedBugs')]
|
||||
private User|null $engineer = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'reportedBugs')]
|
||||
private User|null $reporter;
|
||||
|
||||
/** @var Collection<int, Product> */
|
||||
#[ORM\ManyToMany(targetEntity: Product::class)]
|
||||
private Collection $products;
|
||||
|
||||
// ... (other code)
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
// src/Bug.php
|
||||
@@ -1051,37 +1111,37 @@ the ``Product`` before:
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
private int|null $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
private $description;
|
||||
private string $description;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
private $created;
|
||||
private DateTime $created;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
private $status;
|
||||
private string $status;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="assignedBugs")
|
||||
*/
|
||||
private $engineer;
|
||||
private User|null $engineer;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="reportedBugs")
|
||||
*/
|
||||
private $reporter;
|
||||
private User|null $reporter;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="Product")
|
||||
*/
|
||||
private $products;
|
||||
private Collection $products;
|
||||
|
||||
// ... (other code)
|
||||
}
|
||||
@@ -1170,7 +1230,37 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'users')]
|
||||
class User
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $name;
|
||||
|
||||
/** @var Collection<int, Bug> An ArrayCollection of Bug objects. */
|
||||
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'reporter')]
|
||||
private Collection $reportedBugs;
|
||||
|
||||
/** @var Collection<int,Bug> An ArrayCollection of Bug objects. */
|
||||
#[ORM\OneToMany(targetEntity: Bug::class, mappedBy: 'engineer')]
|
||||
private $assignedBugs;
|
||||
|
||||
// .. (other code)
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
// src/User.php
|
||||
@@ -1189,29 +1279,28 @@ Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
* @ORM\Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
private int|null $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
|
||||
*/
|
||||
private $reportedBugs;
|
||||
private Collection $reportedBugs;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
* @var Collection<int, Bug> An ArrayCollection of Bug objects.
|
||||
*/
|
||||
private $assignedBugs;
|
||||
private Collection $assignedBugs;
|
||||
|
||||
// .. (other code)
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<!-- config/xml/User.dcm.xml -->
|
||||
@@ -1268,7 +1357,7 @@ class that holds the owning sides.
|
||||
Update your database schema by running:
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
$ php bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
|
||||
Implementing more Requirements
|
||||
@@ -1741,7 +1830,20 @@ we have to adjust the metadata slightly.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: BugRepository::class)]
|
||||
#[ORM\Table(name: 'bugs')]
|
||||
class Bug
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
|
||||
@@ -1750,7 +1852,7 @@ we have to adjust the metadata slightly.
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="BugRepository")
|
||||
* @ORM\Table(name="bugs")
|
||||
**/
|
||||
*/
|
||||
class Bug
|
||||
{
|
||||
// ...
|
||||
|
||||
@@ -5,28 +5,42 @@ There are use-cases when you'll want to sort collections when they are
|
||||
retrieved from the database. In userland you do this as long as you
|
||||
haven't initially saved an entity with its associations into the
|
||||
database. To retrieve a sorted collection from the database you can
|
||||
use the ``@OrderBy`` annotation with a collection that specifies
|
||||
use the ``#[OrderBy]`` attribute with a collection that specifies
|
||||
a DQL snippet that is appended to all queries with this
|
||||
collection.
|
||||
|
||||
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
|
||||
can specify the ``@OrderBy`` in the following way:
|
||||
Additional to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute you
|
||||
can specify the ``#[OrderBy]`` in the following way:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
#[ManyToMany(targetEntity: Group::class)]
|
||||
#[OrderBy(["name" => "ASC"])]
|
||||
private Collection $groups;
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @OrderBy({"name" = "ASC"})
|
||||
**/
|
||||
private $groups;
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
private Collection $groups;
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -62,7 +76,7 @@ The DQL Snippet in OrderBy is only allowed to consist of
|
||||
unqualified, unquoted field names and of an optional ASC/DESC
|
||||
positional statement. Multiple Fields are separated by a comma (,).
|
||||
The referenced field names have to exist on the ``targetEntity``
|
||||
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
|
||||
class of the ``#[ManyToMany]`` or ``#[OneToMany]`` attribute.
|
||||
|
||||
The semantics of this feature can be described as follows:
|
||||
|
||||
@@ -106,5 +120,3 @@ You can reverse the order with an explicit DQL ORDER BY:
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC
|
||||
|
||||
|
||||
|
||||
@@ -14,47 +14,43 @@ Suppose we have a class ExampleEntityWithOverride. This class uses trait Example
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
*
|
||||
* @AttributeOverrides({
|
||||
* @AttributeOverride(name="foo",
|
||||
* column=@Column(
|
||||
* name = "foo_overridden",
|
||||
* type = "integer",
|
||||
* length = 140,
|
||||
* nullable = false,
|
||||
* unique = false
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
*
|
||||
* @AssociationOverrides({
|
||||
* @AssociationOverride(name="bar",
|
||||
* joinColumns=@JoinColumn(
|
||||
* name="example_entity_overridden_bar_id", referencedColumnName="id"
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
*/
|
||||
|
||||
#[Entity]
|
||||
#[AttributeOverrides([
|
||||
new AttributeOverride('foo', [
|
||||
'column' => new Column([
|
||||
'name' => 'foo_overridden',
|
||||
'type' => 'integer',
|
||||
'length' => 140,
|
||||
'nullable' => false,
|
||||
'unique' => false,
|
||||
]),
|
||||
]),
|
||||
])]
|
||||
#[AssociationOverrides([
|
||||
new AssociationOverride('bar', [
|
||||
'joinColumns' => new JoinColumn([
|
||||
'name' => 'example_entity_overridden_bar_id',
|
||||
'referencedColumnName' => 'id',
|
||||
]),
|
||||
]),
|
||||
])]
|
||||
class ExampleEntityWithOverride
|
||||
{
|
||||
use ExampleTrait;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
#[Entity]
|
||||
class Bar
|
||||
{
|
||||
/** @Id @Column(type="string") */
|
||||
#[Id, Column(type: 'string')]
|
||||
private $id;
|
||||
}
|
||||
|
||||
The docblock is showing metadata override of the attribute and association type. It
|
||||
basically changes the names of the columns mapped for a property ``foo`` and for
|
||||
the association ``bar`` which relates to Bar class shown above. Here is the trait
|
||||
which has mapping metadata that is overridden by the annotation above:
|
||||
which has mapping metadata that is overridden by the attribute above:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -64,19 +60,15 @@ which has mapping metadata that is overridden by the annotation above:
|
||||
*/
|
||||
trait ExampleTrait
|
||||
{
|
||||
/** @Id @Column(type="string") */
|
||||
private $id;
|
||||
#[Id, Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
|
||||
/**
|
||||
* @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
|
||||
*/
|
||||
protected $foo;
|
||||
#[Column(name: 'trait_foo', type: 'integer', length: 100, nullable: true, unique: true)]
|
||||
protected int $foo;
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
|
||||
* @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $bar;
|
||||
#[OneToOne(targetEntity: Bar::class, cascade: ['persist', 'merge'])]
|
||||
#[JoinColumn(name: 'example_trait_bar_id', referencedColumnName: 'id')]
|
||||
protected Bar|null $bar = null;
|
||||
}
|
||||
|
||||
The case for just extending a class would be just the same but:
|
||||
|
||||
@@ -23,6 +23,7 @@ Mapping Indexed Associations
|
||||
|
||||
You can map indexed associations by adding:
|
||||
|
||||
* ``indexBy`` argument to any ``#[OneToMany]`` or ``#[ManyToMany]`` attribute.
|
||||
* ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation.
|
||||
* ``index-by`` attribute to any ``<one-to-many />`` or ``<many-to-many />`` xml element.
|
||||
* ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files.
|
||||
@@ -30,7 +31,66 @@ You can map indexed associations by adding:
|
||||
The code and mappings for the Market entity looks like this:
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\StockExchange;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
#[Entity]
|
||||
#[Table(name: 'exchange_markets')]
|
||||
class Market
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
#[Column(type: 'string')]
|
||||
private string $name;
|
||||
|
||||
/** @var Collection<string, Stock> */
|
||||
#[OneToMany(targetEntity: Stock::class, mappedBy: 'market', indexBy: 'symbol')]
|
||||
private Collection $stocks;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->stocks = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function addStock(Stock $stock): void
|
||||
{
|
||||
$this->stocks[$stock->getSymbol()] = $stock;
|
||||
}
|
||||
|
||||
public function getStock(string $symbol): Stock
|
||||
{
|
||||
if (!isset($this->stocks[$symbol])) {
|
||||
throw new \InvalidArgumentException("Symbol is not traded on this market.");
|
||||
}
|
||||
|
||||
return $this->stocks[$symbol];
|
||||
}
|
||||
|
||||
/** @return array<string, Stock> */
|
||||
public function getStocks(): array
|
||||
{
|
||||
return $this->stocks->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\StockExchange;
|
||||
@@ -47,19 +107,19 @@ The code and mappings for the Market entity looks like this:
|
||||
* @Id @Column(type="integer") @GeneratedValue
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
private int|null $id = null;
|
||||
|
||||
/**
|
||||
* @Column(type="string")
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
|
||||
* @var Stock[]
|
||||
* @var Collection<int, Stock>
|
||||
*/
|
||||
private $stocks;
|
||||
private Collection $stocks;
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
@@ -67,22 +127,22 @@ The code and mappings for the Market entity looks like this:
|
||||
$this->stocks = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId()
|
||||
public function getId(): int|null
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function addStock(Stock $stock)
|
||||
public function addStock(Stock $stock): void
|
||||
{
|
||||
$this->stocks[$stock->getSymbol()] = $stock;
|
||||
}
|
||||
|
||||
public function getStock($symbol)
|
||||
public function getStock($symbol): Stock
|
||||
{
|
||||
if (!isset($this->stocks[$symbol])) {
|
||||
throw new \InvalidArgumentException("Symbol is not traded on this market.");
|
||||
@@ -91,7 +151,8 @@ The code and mappings for the Market entity looks like this:
|
||||
return $this->stocks[$symbol];
|
||||
}
|
||||
|
||||
public function getStocks()
|
||||
/** @return array<string, Stock> */
|
||||
public function getStocks(): array
|
||||
{
|
||||
return $this->stocks->toArray();
|
||||
}
|
||||
@@ -142,7 +203,38 @@ The ``Stock`` entity doesn't contain any special instructions that are new, but
|
||||
here are the code and mappings for it:
|
||||
|
||||
.. configuration-block::
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\StockExchange;
|
||||
|
||||
#[Entity]
|
||||
#[Table(name: 'exchange_stocks')]
|
||||
class Stock
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
#[Column(type: 'string', unique: true)]
|
||||
private string $symbol;
|
||||
|
||||
#[ManyToOne(targetEntity: Market::class, inversedBy: 'stocks')]
|
||||
private Market|null $market;
|
||||
|
||||
public function __construct(string $symbol, Market $market)
|
||||
{
|
||||
$this->symbol = $symbol;
|
||||
$this->market = $market;
|
||||
$market->addStock($this);
|
||||
}
|
||||
|
||||
public function getSymbol(): string
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
namespace Doctrine\Tests\Models\StockExchange;
|
||||
@@ -157,18 +249,18 @@ here are the code and mappings for it:
|
||||
* @Id @GeneratedValue @Column(type="integer")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
private int|null $id = null;
|
||||
|
||||
/**
|
||||
* @Column(type="string", unique=true)
|
||||
*/
|
||||
private $symbol;
|
||||
private string $symbol;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
|
||||
* @var Market
|
||||
*/
|
||||
private $market;
|
||||
private Market|null $market = null;
|
||||
|
||||
public function __construct($symbol, Market $market)
|
||||
{
|
||||
@@ -177,7 +269,7 @@ here are the code and mappings for it:
|
||||
$market->addStock($this);
|
||||
}
|
||||
|
||||
public function getSymbol()
|
||||
public function getSymbol(): string
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
@@ -249,7 +341,7 @@ now query for the market:
|
||||
// $em is the EntityManager
|
||||
$marketId = 1;
|
||||
$symbol = "AAPL";
|
||||
|
||||
|
||||
$market = $em->find("Doctrine\Tests\Models\StockExchange\Market", $marketId);
|
||||
|
||||
// Access the stocks by symbol now:
|
||||
|
||||
@@ -4,11 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use BackedEnum;
|
||||
use Countable;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
@@ -30,10 +32,13 @@ use function array_map;
|
||||
use function array_shift;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function func_num_args;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_numeric;
|
||||
use function is_object;
|
||||
use function is_scalar;
|
||||
use function is_string;
|
||||
use function iterator_count;
|
||||
use function iterator_to_array;
|
||||
use function ksort;
|
||||
@@ -193,9 +198,7 @@ abstract class AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool TRUE if the query results are enable for second level cache, FALSE otherwise.
|
||||
*/
|
||||
/** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */
|
||||
public function isCacheable()
|
||||
{
|
||||
return $this->cacheable;
|
||||
@@ -223,17 +226,13 @@ abstract class AbstractQuery
|
||||
return $this->cacheRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise.
|
||||
*/
|
||||
/** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */
|
||||
protected function isCacheEnabled()
|
||||
{
|
||||
return $this->cacheable && $this->hasCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
/** @return int */
|
||||
public function getLifetime()
|
||||
{
|
||||
return $this->lifetime;
|
||||
@@ -280,7 +279,7 @@ abstract class AbstractQuery
|
||||
* The returned SQL syntax depends on the connection driver that is used
|
||||
* by this query object at the time of this method call.
|
||||
*
|
||||
* @return string SQL query
|
||||
* @return list<string>|string SQL query
|
||||
*/
|
||||
abstract public function getSQL();
|
||||
|
||||
@@ -322,7 +321,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Gets a query parameter.
|
||||
*
|
||||
* @param mixed $key The key (index or name) of the bound parameter.
|
||||
* @param int|string $key The key (index or name) of the bound parameter.
|
||||
*
|
||||
* @return Parameter|null The value of the bound parameter, or NULL if not available.
|
||||
*/
|
||||
@@ -351,7 +350,6 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setParameters($parameters)
|
||||
{
|
||||
// BC compatibility with 2.3-
|
||||
if (is_array($parameters)) {
|
||||
/** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */
|
||||
$parameterCollection = new ArrayCollection();
|
||||
@@ -399,8 +397,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed[]|string|int|float|bool
|
||||
* @psalm-return array|scalar
|
||||
* @return mixed
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
*/
|
||||
@@ -424,15 +421,20 @@ abstract class AbstractQuery
|
||||
return $value->name;
|
||||
}
|
||||
|
||||
if ($value instanceof BackedEnum) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
if (! is_object($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$class = ClassUtils::getClass($value);
|
||||
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
|
||||
|
||||
if ($value === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
|
||||
}
|
||||
} catch (MappingException | ORMMappingException $e) {
|
||||
/* Silence any mapping exceptions. These can occur if the object in
|
||||
@@ -540,6 +542,15 @@ abstract class AbstractQuery
|
||||
public function setHydrationCacheProfile(?QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ($profile === null) {
|
||||
if (func_num_args() < 1) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9791',
|
||||
'Calling %s without arguments is deprecated, pass null instead.',
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
|
||||
$this->_hydrationCacheProfile = null;
|
||||
|
||||
return $this;
|
||||
@@ -565,9 +576,7 @@ abstract class AbstractQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryCacheProfile|null
|
||||
*/
|
||||
/** @return QueryCacheProfile|null */
|
||||
public function getHydrationCacheProfile()
|
||||
{
|
||||
return $this->_hydrationCacheProfile;
|
||||
@@ -584,6 +593,15 @@ abstract class AbstractQuery
|
||||
public function setResultCacheProfile(?QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ($profile === null) {
|
||||
if (func_num_args() < 1) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9791',
|
||||
'Calling %s without arguments is deprecated, pass null instead.',
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
|
||||
$this->_queryCacheProfile = null;
|
||||
|
||||
return $this;
|
||||
@@ -638,6 +656,15 @@ abstract class AbstractQuery
|
||||
public function setResultCache(?CacheItemPoolInterface $resultCache = null)
|
||||
{
|
||||
if ($resultCache === null) {
|
||||
if (func_num_args() < 1) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9791',
|
||||
'Calling %s without arguments is deprecated, pass null instead.',
|
||||
__METHOD__
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->_queryCacheProfile) {
|
||||
$this->_queryCacheProfile = new QueryCacheProfile($this->_queryCacheProfile->getLifetime(), $this->_queryCacheProfile->getCacheKey());
|
||||
}
|
||||
@@ -799,9 +826,7 @@ abstract class AbstractQuery
|
||||
return $this->_expireResultCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryCacheProfile|null
|
||||
*/
|
||||
/** @return QueryCacheProfile|null */
|
||||
public function getQueryCacheProfile()
|
||||
{
|
||||
return $this->_queryCacheProfile;
|
||||
@@ -810,17 +835,22 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Change the default fetch mode of an association for this query.
|
||||
*
|
||||
* $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $assocName
|
||||
* @param int $fetchMode
|
||||
* @param class-string $class
|
||||
* @param string $assocName
|
||||
* @param int $fetchMode
|
||||
* @psalm-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFetchMode($class, $assocName, $fetchMode)
|
||||
{
|
||||
if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
|
||||
if (! in_array($fetchMode, [Mapping\ClassMetadata::FETCH_EAGER, Mapping\ClassMetadata::FETCH_LAZY], true)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9777',
|
||||
'Calling %s() with something else than ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY is deprecated.',
|
||||
__METHOD__
|
||||
);
|
||||
$fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
|
||||
}
|
||||
|
||||
@@ -1047,7 +1077,8 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
|
||||
* @param string|int|null $hydrationMode The hydration mode to use.
|
||||
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
|
||||
* @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters
|
||||
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
|
||||
*
|
||||
* @return IterableResult
|
||||
*/
|
||||
@@ -1295,7 +1326,8 @@ abstract class AbstractQuery
|
||||
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
|
||||
}
|
||||
|
||||
$sql = $this->getSQL();
|
||||
$sql = $this->getSQL();
|
||||
assert(is_string($sql));
|
||||
$queryCacheProfile = $this->getHydrationCacheProfile();
|
||||
$hints = $this->getHints();
|
||||
$hints['hydrationMode'] = $this->getHydrationMode();
|
||||
@@ -1367,7 +1399,8 @@ abstract class AbstractQuery
|
||||
*/
|
||||
protected function getHash()
|
||||
{
|
||||
$query = $this->getSQL();
|
||||
$query = $this->getSQL();
|
||||
assert(is_string($query));
|
||||
$hints = $this->getHints();
|
||||
$params = array_map(function (Parameter $parameter) {
|
||||
$value = $parameter->getValue();
|
||||
|
||||
@@ -141,6 +141,8 @@ interface Cache
|
||||
* Evicts all cached query results under the given name, or default query cache if the region name is NULL.
|
||||
*
|
||||
* @param string|null $regionName The cache name associated to the queries being cached.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function evictQueryRegion($regionName = null);
|
||||
|
||||
|
||||
@@ -10,20 +10,26 @@ namespace Doctrine\ORM\Cache;
|
||||
class AssociationCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* The entity identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> The entity identifier
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* The entity class name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The entity class name
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $class The entity class.
|
||||
* @param array<string, mixed> $identifier The entity identifier.
|
||||
* @psalm-param class-string $class
|
||||
*/
|
||||
public function __construct($class, array $identifier)
|
||||
{
|
||||
|
||||
@@ -23,41 +23,31 @@ class CacheConfiguration
|
||||
/** @var QueryCacheValidator|null */
|
||||
private $queryValidator;
|
||||
|
||||
/**
|
||||
* @return CacheFactory|null
|
||||
*/
|
||||
/** @return CacheFactory|null */
|
||||
public function getCacheFactory()
|
||||
{
|
||||
return $this->cacheFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
/** @return void */
|
||||
public function setCacheFactory(CacheFactory $factory)
|
||||
{
|
||||
$this->cacheFactory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CacheLogger|null
|
||||
*/
|
||||
/** @return CacheLogger|null */
|
||||
public function getCacheLogger()
|
||||
{
|
||||
return $this->cacheLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
/** @return void */
|
||||
public function setCacheLogger(CacheLogger $logger)
|
||||
{
|
||||
$this->cacheLogger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RegionsConfiguration
|
||||
*/
|
||||
/** @return RegionsConfiguration */
|
||||
public function getRegionsConfiguration()
|
||||
{
|
||||
if ($this->regionsConfig === null) {
|
||||
@@ -67,17 +57,13 @@ class CacheConfiguration
|
||||
return $this->regionsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
/** @return void */
|
||||
public function setRegionsConfiguration(RegionsConfiguration $regionsConfig)
|
||||
{
|
||||
$this->regionsConfig = $regionsConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return QueryCacheValidator
|
||||
*/
|
||||
/** @return QueryCacheValidator */
|
||||
public function getQueryValidator()
|
||||
{
|
||||
if ($this->queryValidator === null) {
|
||||
@@ -89,9 +75,7 @@ class CacheConfiguration
|
||||
return $this->queryValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
/** @return void */
|
||||
public function setQueryValidator(QueryCacheValidator $validator)
|
||||
{
|
||||
$this->queryValidator = $validator;
|
||||
|
||||
@@ -25,6 +25,8 @@ class CacheException extends ORMException
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @param string $entityName
|
||||
*
|
||||
* @return CacheException
|
||||
@@ -45,6 +47,8 @@ class CacheException extends ORMException
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @param string $entityName
|
||||
* @param string $field
|
||||
*
|
||||
|
||||
@@ -31,9 +31,7 @@ interface CacheFactory
|
||||
/**
|
||||
* Build a collection persister for the given relation mapping.
|
||||
*
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
* @param CollectionPersister $persister The collection persister that will be cached.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
*
|
||||
* @return CachedCollectionPersister
|
||||
*/
|
||||
@@ -42,8 +40,7 @@ interface CacheFactory
|
||||
/**
|
||||
* Build a query cache based on the given region name
|
||||
*
|
||||
* @param EntityManagerInterface $em The Entity manager.
|
||||
* @param string $regionName The region name.
|
||||
* @param string|null $regionName The region name.
|
||||
*
|
||||
* @return QueryCache The built query cache.
|
||||
*/
|
||||
@@ -52,9 +49,6 @@ interface CacheFactory
|
||||
/**
|
||||
* Build an entity hydrator
|
||||
*
|
||||
* @param EntityManagerInterface $em The Entity manager.
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
*
|
||||
* @return EntityHydrator The built entity hydrator.
|
||||
*/
|
||||
public function buildEntityHydrator(EntityManagerInterface $em, ClassMetadata $metadata);
|
||||
@@ -62,8 +56,7 @@ interface CacheFactory
|
||||
/**
|
||||
* Build a collection hydrator
|
||||
*
|
||||
* @param EntityManagerInterface $em The Entity manager.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
* @param mixed[] $mapping The association mapping.
|
||||
*
|
||||
* @return CollectionHydrator The built collection hydrator.
|
||||
*/
|
||||
|
||||
@@ -11,8 +11,10 @@ namespace Doctrine\ORM\Cache;
|
||||
abstract class CacheKey
|
||||
{
|
||||
/**
|
||||
* Unique identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string Unique identifier
|
||||
* @var string
|
||||
*/
|
||||
public $hash;
|
||||
}
|
||||
|
||||
@@ -10,14 +10,14 @@ namespace Doctrine\ORM\Cache;
|
||||
class CollectionCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* The list of entity identifiers hold by the collection
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var CacheKey[] The list of entity identifiers hold by the collection
|
||||
* @var CacheKey[]
|
||||
*/
|
||||
public $identifiers;
|
||||
|
||||
/**
|
||||
* @param CacheKey[] $identifiers List of entity identifiers hold by the collection
|
||||
*/
|
||||
/** @param CacheKey[] $identifiers List of entity identifiers hold by the collection */
|
||||
public function __construct(array $identifiers)
|
||||
{
|
||||
$this->identifiers = $identifiers;
|
||||
|
||||
@@ -15,20 +15,27 @@ use function strtolower;
|
||||
class CollectionCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* The owner entity identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> The owner entity identifier
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $ownerIdentifier;
|
||||
|
||||
/**
|
||||
* The owner entity class
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The owner entity class
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* The association name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The association name
|
||||
* @var string
|
||||
*/
|
||||
public $association;
|
||||
|
||||
@@ -36,6 +43,7 @@ class CollectionCacheKey extends CacheKey
|
||||
* @param string $entityClass The entity class.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param array<string, mixed> $ownerIdentifier The identifier of the owning entity.
|
||||
* @psalm-param class-string $entityClass
|
||||
*/
|
||||
public function __construct($entityClass, $association, array $ownerIdentifier)
|
||||
{
|
||||
|
||||
@@ -14,21 +14,12 @@ use Doctrine\ORM\PersistentCollection;
|
||||
interface CollectionHydrator
|
||||
{
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param CollectionCacheKey $key The cached collection key.
|
||||
* @param array|mixed[]|Collection $collection The collection.
|
||||
*
|
||||
* @return CollectionCacheEntry
|
||||
*/
|
||||
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection);
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The owning entity metadata.
|
||||
* @param CollectionCacheKey $key The cached collection key.
|
||||
* @param CollectionCacheEntry $entry The cached collection entry.
|
||||
* @param PersistentCollection $collection The collection to load the cache into.
|
||||
*
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
/** @return mixed[]|null */
|
||||
public function loadCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, CollectionCacheEntry $entry, PersistentCollection $collection);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@ class DefaultCache implements Cache
|
||||
/** @var CacheFactory */
|
||||
private $cacheFactory;
|
||||
|
||||
/** @var QueryCache[] */
|
||||
/**
|
||||
* @var QueryCache[]
|
||||
* @psalm-var array<string, QueryCache>
|
||||
*/
|
||||
private $queryCaches = [];
|
||||
|
||||
/** @var QueryCache|null */
|
||||
@@ -259,10 +262,7 @@ class DefaultCache implements Cache
|
||||
return $this->queryCaches[$regionName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*/
|
||||
/** @param mixed $identifier The entity identifier. */
|
||||
private function buildEntityCacheKey(ClassMetadata $metadata, $identifier): EntityCacheKey
|
||||
{
|
||||
if (! is_array($identifier)) {
|
||||
@@ -272,11 +272,7 @@ class DefaultCache implements Cache
|
||||
return new EntityCacheKey($metadata->rootEntityName, $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param string $association The field name that represents the association.
|
||||
* @param mixed $ownerIdentifier The identifier of the owning entity.
|
||||
*/
|
||||
/** @param mixed $ownerIdentifier The identifier of the owning entity. */
|
||||
private function buildCollectionCacheKey(
|
||||
ClassMetadata $metadata,
|
||||
string $association,
|
||||
@@ -290,18 +286,20 @@ class DefaultCache implements Cache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ClassMetadata $metadata The entity metadata.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function toIdentifierArray(ClassMetadata $metadata, $identifier): array
|
||||
{
|
||||
if (is_object($identifier) && $this->em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($identifier))) {
|
||||
$identifier = $this->uow->getSingleIdentifierValue($identifier);
|
||||
if (is_object($identifier)) {
|
||||
$class = ClassUtils::getClass($identifier);
|
||||
if ($this->em->getMetadataFactory()->hasMetadataFor($class)) {
|
||||
$identifier = $this->uow->getSingleIdentifierValue($identifier);
|
||||
|
||||
if ($identifier === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
if ($identifier === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,9 +49,7 @@ class DefaultCacheFactory implements CacheFactory
|
||||
/** @var string|null */
|
||||
private $fileLockRegionDirectory;
|
||||
|
||||
/**
|
||||
* @param CacheItemPoolInterface $cacheItemPool
|
||||
*/
|
||||
/** @param CacheItemPoolInterface $cacheItemPool */
|
||||
public function __construct(RegionsConfiguration $cacheConfig, $cacheItemPool)
|
||||
{
|
||||
if ($cacheItemPool instanceof LegacyCache) {
|
||||
@@ -89,25 +87,19 @@ class DefaultCacheFactory implements CacheFactory
|
||||
$this->fileLockRegionDirectory = (string) $fileLockRegionDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
/** @return string */
|
||||
public function getFileLockRegionDirectory()
|
||||
{
|
||||
return $this->fileLockRegionDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
/** @return void */
|
||||
public function setRegion(Region $region)
|
||||
{
|
||||
$this->regions[$region->getName()] = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
/** @return void */
|
||||
public function setTimestampRegion(TimestampRegion $region)
|
||||
{
|
||||
$this->timestampRegion = $region;
|
||||
|
||||
@@ -27,9 +27,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
/** @var array<string,mixed> */
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
|
||||
/**
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
*/
|
||||
/** @param EntityManagerInterface $em The entity manager. */
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
|
||||
@@ -12,6 +12,7 @@ use Doctrine\ORM\UnitOfWork;
|
||||
use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
|
||||
use function array_merge;
|
||||
use function assert;
|
||||
use function is_array;
|
||||
use function is_object;
|
||||
use function reset;
|
||||
@@ -37,9 +38,7 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
/** @var array<string,mixed> */
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
|
||||
/**
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
*/
|
||||
/** @param EntityManagerInterface $em The entity manager. */
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
@@ -57,6 +56,7 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
|
||||
if ($metadata->requiresFetchAfterChange) {
|
||||
if ($metadata->isVersioned) {
|
||||
assert($metadata->versionField !== null);
|
||||
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
|
||||
}
|
||||
|
||||
|
||||
@@ -384,7 +384,8 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* @param object $entity
|
||||
*
|
||||
* @return array<object>|object
|
||||
* @return mixed[]|object|null
|
||||
* @psalm-return list<mixed>|object|null
|
||||
*/
|
||||
private function getAssociationValue(
|
||||
ResultSetMapping $rsm,
|
||||
@@ -411,10 +412,11 @@ class DefaultQueryCache implements QueryCache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array<mixed> $path
|
||||
* @param mixed $value
|
||||
* @psalm-param array<array-key, array{field: string, class: string}> $path
|
||||
*
|
||||
* @return mixed
|
||||
* @return mixed[]|object|null
|
||||
* @psalm-return list<mixed>|object|null
|
||||
*/
|
||||
private function getAssociationPathValue($value, array $path)
|
||||
{
|
||||
|
||||
@@ -14,14 +14,18 @@ use function array_map;
|
||||
class EntityCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* The entity map data
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string,mixed> The entity map data
|
||||
* @var array<string,mixed>
|
||||
*/
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* The entity class name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The entity class name
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $class;
|
||||
|
||||
@@ -15,20 +15,26 @@ use function strtolower;
|
||||
class EntityCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* The entity identifier
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> The entity identifier
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $identifier;
|
||||
|
||||
/**
|
||||
* The entity class name
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var string The entity class name
|
||||
* @var string
|
||||
* @psalm-var class-string
|
||||
*/
|
||||
public $entityClass;
|
||||
|
||||
/**
|
||||
* @param string $entityClass The entity class name. In a inheritance hierarchy it should always be the root entity class.
|
||||
* @param array<string, mixed> $identifier The entity identifier
|
||||
* @psalm-param class-string $entityClass
|
||||
*/
|
||||
public function __construct($entityClass, array $identifier)
|
||||
{
|
||||
|
||||
@@ -4,9 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Cache\Exception;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
/** @deprecated */
|
||||
final class InvalidResultCacheDriver extends CacheException
|
||||
{
|
||||
public static function create(): self
|
||||
|
||||
@@ -21,9 +21,7 @@ class Lock
|
||||
$this->time = $time ?: time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lock
|
||||
*/
|
||||
/** @return Lock */
|
||||
public static function createLockRead()
|
||||
{
|
||||
return new self(uniqid((string) time(), true));
|
||||
|
||||
@@ -18,6 +18,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param EntityCacheKey $key The cache key of the entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function entityCachePut($regionName, EntityCacheKey $key);
|
||||
|
||||
@@ -26,6 +28,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param EntityCacheKey $key The cache key of the entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function entityCacheHit($regionName, EntityCacheKey $key);
|
||||
|
||||
@@ -34,6 +38,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param EntityCacheKey $key The cache key of the entity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function entityCacheMiss($regionName, EntityCacheKey $key);
|
||||
|
||||
@@ -42,6 +48,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param CollectionCacheKey $key The cache key of the collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectionCachePut($regionName, CollectionCacheKey $key);
|
||||
|
||||
@@ -50,6 +58,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param CollectionCacheKey $key The cache key of the collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectionCacheHit($regionName, CollectionCacheKey $key);
|
||||
|
||||
@@ -58,6 +68,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param CollectionCacheKey $key The cache key of the collection.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function collectionCacheMiss($regionName, CollectionCacheKey $key);
|
||||
|
||||
@@ -66,6 +78,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param QueryCacheKey $key The cache key of the query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queryCachePut($regionName, QueryCacheKey $key);
|
||||
|
||||
@@ -74,6 +88,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param QueryCacheKey $key The cache key of the query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queryCacheHit($regionName, QueryCacheKey $key);
|
||||
|
||||
@@ -82,6 +98,8 @@ interface CacheLogger
|
||||
*
|
||||
* @param string $regionName The name of the cache region.
|
||||
* @param QueryCacheKey $key The cache key of the query.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
|
||||
class CacheLoggerChain implements CacheLogger
|
||||
{
|
||||
/** @var array<CacheLogger> */
|
||||
/** @var array<string, CacheLogger> */
|
||||
private $loggers = [];
|
||||
|
||||
/**
|
||||
@@ -33,9 +33,7 @@ class CacheLoggerChain implements CacheLogger
|
||||
return $this->loggers[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<CacheLogger>
|
||||
*/
|
||||
/** @return array<string, CacheLogger> */
|
||||
public function getLoggers()
|
||||
{
|
||||
return $this->loggers;
|
||||
|
||||
@@ -15,13 +15,13 @@ use function array_sum;
|
||||
*/
|
||||
class StatisticsCacheLogger implements CacheLogger
|
||||
{
|
||||
/** @var int[] */
|
||||
/** @var array<string, int> */
|
||||
private $cacheMissCountMap = [];
|
||||
|
||||
/** @var int[] */
|
||||
/** @var array<string, int> */
|
||||
private $cacheHitCountMap = [];
|
||||
|
||||
/** @var int[] */
|
||||
/** @var array<string, int> */
|
||||
private $cachePutCountMap = [];
|
||||
|
||||
/**
|
||||
@@ -29,9 +29,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function collectionCacheMiss($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheMissCountMap[$regionName]
|
||||
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,9 +38,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function collectionCacheHit($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheHitCountMap[$regionName]
|
||||
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +47,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function collectionCachePut($regionName, CollectionCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cachePutCountMap[$regionName]
|
||||
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,9 +56,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function entityCacheMiss($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheMissCountMap[$regionName]
|
||||
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,9 +65,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function entityCacheHit($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheHitCountMap[$regionName]
|
||||
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,9 +74,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function entityCachePut($regionName, EntityCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cachePutCountMap[$regionName]
|
||||
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,9 +83,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function queryCacheHit($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cacheHitCountMap[$regionName] = isset($this->cacheHitCountMap[$regionName])
|
||||
? $this->cacheHitCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheHitCountMap[$regionName]
|
||||
= ($this->cacheHitCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,9 +92,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function queryCacheMiss($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cacheMissCountMap[$regionName] = isset($this->cacheMissCountMap[$regionName])
|
||||
? $this->cacheMissCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cacheMissCountMap[$regionName]
|
||||
= ($this->cacheMissCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,9 +101,8 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function queryCachePut($regionName, QueryCacheKey $key)
|
||||
{
|
||||
$this->cachePutCountMap[$regionName] = isset($this->cachePutCountMap[$regionName])
|
||||
? $this->cachePutCountMap[$regionName] + 1
|
||||
: 1;
|
||||
$this->cachePutCountMap[$regionName]
|
||||
= ($this->cachePutCountMap[$regionName] ?? 0) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,25 +141,19 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
return $this->cachePutCountMap[$regionName] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
/** @return array<string, int> */
|
||||
public function getRegionsMiss()
|
||||
{
|
||||
return $this->cacheMissCountMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
/** @return array<string, int> */
|
||||
public function getRegionsHit()
|
||||
{
|
||||
return $this->cacheHitCountMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
/** @return array<string, int> */
|
||||
public function getRegionsPut()
|
||||
{
|
||||
return $this->cachePutCountMap;
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Doctrine\ORM\Cache;
|
||||
* Defines a region that supports multi-get reading.
|
||||
*
|
||||
* With one method call we can get multiple items.
|
||||
*
|
||||
* @deprecated Implement {@see Region} instead.
|
||||
*/
|
||||
interface MultiGetRegion
|
||||
{
|
||||
|
||||
@@ -13,11 +13,15 @@ interface CachedPersister
|
||||
{
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the transaction.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function afterTransactionComplete();
|
||||
|
||||
/**
|
||||
* Perform whatever processing is encapsulated here after completion of the rolled-back.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function afterTransactionRolledBack();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Doctrine\ORM\Cache\Persister\Collection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\CollectionHydrator;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
@@ -108,7 +109,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object[]|null
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key)
|
||||
{
|
||||
@@ -224,10 +225,18 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
/**
|
||||
* Clears cache entries related to the current collection
|
||||
*
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function evictCollectionCache(PersistentCollection $collection)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9512',
|
||||
'The method %s() is deprecated and will be removed without replacement.'
|
||||
);
|
||||
|
||||
$key = new CollectionCacheKey(
|
||||
$this->sourceEntity->rootEntityName,
|
||||
$this->association['fieldName'],
|
||||
@@ -242,13 +251,22 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This method is not used anymore.
|
||||
*
|
||||
* @param string $targetEntity
|
||||
* @param object $element
|
||||
* @psalm-param class-string $targetEntity
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function evictElementCache($targetEntity, $element)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9512',
|
||||
'The method %s() is deprecated and will be removed without replacement.'
|
||||
);
|
||||
|
||||
$targetPersister = $this->uow->getEntityPersister($targetEntity);
|
||||
assert($targetPersister instanceof CachedEntityPersister);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
|
||||
@@ -16,27 +16,23 @@ use Doctrine\ORM\Persisters\Collection\CollectionPersister;
|
||||
*/
|
||||
interface CachedCollectionPersister extends CachedPersister, CollectionPersister
|
||||
{
|
||||
/**
|
||||
* @return ClassMetadata
|
||||
*/
|
||||
/** @return ClassMetadata */
|
||||
public function getSourceEntityMetadata();
|
||||
|
||||
/**
|
||||
* @return ClassMetadata
|
||||
*/
|
||||
/** @return ClassMetadata */
|
||||
public function getTargetEntityMetadata();
|
||||
|
||||
/**
|
||||
* Loads a collection from cache
|
||||
*
|
||||
* @return PersistentCollection|null
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
public function loadCollectionCache(PersistentCollection $collection, CollectionCacheKey $key);
|
||||
|
||||
/**
|
||||
* Stores a collection into cache
|
||||
*
|
||||
* @param array|mixed[]|Collection $elements
|
||||
* @param mixed[]|Collection $elements
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
@@ -14,12 +14,7 @@ use function spl_object_id;
|
||||
|
||||
class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
{
|
||||
/**
|
||||
* @param CollectionPersister $persister The collection persister that will be cached.
|
||||
* @param ConcurrentRegion $region The collection region.
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
* @param mixed[] $association The association mapping.
|
||||
*/
|
||||
/** @param mixed[] $association The association mapping. */
|
||||
public function __construct(CollectionPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, array $association)
|
||||
{
|
||||
parent::__construct($persister, $region, $em, $association);
|
||||
|
||||
@@ -177,9 +177,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
return $this->region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityHydrator
|
||||
*/
|
||||
/** @return EntityHydrator */
|
||||
public function getEntityHydrator()
|
||||
{
|
||||
return $this->hydrator;
|
||||
@@ -207,9 +205,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
return $cached;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
*/
|
||||
/** @param object $entity */
|
||||
private function storeJoinedAssociations($entity): void
|
||||
{
|
||||
if ($this->joinedAssociations === null) {
|
||||
@@ -251,8 +247,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
* @param string $query
|
||||
* @param string[]|Criteria $criteria
|
||||
* @param string[] $orderBy
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -14,9 +14,7 @@ use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
*/
|
||||
interface CachedEntityPersister extends CachedPersister, EntityPersister
|
||||
{
|
||||
/**
|
||||
* @return EntityHydrator
|
||||
*/
|
||||
/** @return EntityHydrator */
|
||||
public function getEntityHydrator();
|
||||
|
||||
/**
|
||||
|
||||
@@ -82,9 +82,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
$this->queuedCache['update'][] = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
*/
|
||||
/** @param object $entity */
|
||||
private function updateCache($entity, bool $isChanged): bool
|
||||
{
|
||||
$class = $this->metadataFactory->getMetadataFor(get_class($entity));
|
||||
|
||||
@@ -15,12 +15,6 @@ use Doctrine\ORM\Persisters\Entity\EntityPersister;
|
||||
*/
|
||||
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
{
|
||||
/**
|
||||
* @param EntityPersister $persister The entity persister to cache.
|
||||
* @param ConcurrentRegion $region The entity cache region.
|
||||
* @param EntityManagerInterface $em The entity manager.
|
||||
* @param ClassMetadata $class The entity metadata.
|
||||
*/
|
||||
public function __construct(EntityPersister $persister, ConcurrentRegion $region, EntityManagerInterface $em, ClassMetadata $class)
|
||||
{
|
||||
parent::__construct($persister, $region, $em, $class);
|
||||
|
||||
@@ -12,9 +12,7 @@ use Doctrine\ORM\Query\ResultSetMapping;
|
||||
*/
|
||||
interface QueryCache
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
/** @return bool */
|
||||
public function clear();
|
||||
|
||||
/**
|
||||
@@ -32,8 +30,6 @@ interface QueryCache
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []);
|
||||
|
||||
/**
|
||||
* @return Region
|
||||
*/
|
||||
/** @return Region */
|
||||
public function getRegion();
|
||||
}
|
||||
|
||||
@@ -12,20 +12,24 @@ use function microtime;
|
||||
class QueryCacheEntry implements CacheEntry
|
||||
{
|
||||
/**
|
||||
* List of entity identifiers
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var array<string, mixed> List of entity identifiers
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public $result;
|
||||
|
||||
/**
|
||||
* Time creation of this cache entry
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var float Time creation of this cache entry
|
||||
* @var float
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $result
|
||||
* @param float $time
|
||||
* @param float|null $time
|
||||
*/
|
||||
public function __construct($result, $time = null)
|
||||
{
|
||||
|
||||
@@ -12,8 +12,10 @@ use Doctrine\ORM\Cache;
|
||||
class QueryCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* Cache key lifetime
|
||||
*
|
||||
* @readonly Public only for performance reasons, it should be considered immutable.
|
||||
* @var int Cache key lifetime
|
||||
* @var int
|
||||
*/
|
||||
public $lifetime;
|
||||
|
||||
@@ -32,9 +34,7 @@ class QueryCacheKey extends CacheKey
|
||||
*/
|
||||
public $timestampKey;
|
||||
|
||||
/**
|
||||
* @psalm-param Cache::MODE_* $cacheMode
|
||||
*/
|
||||
/** @psalm-param Cache::MODE_* $cacheMode */
|
||||
public function __construct(
|
||||
string $cacheId,
|
||||
int $lifetime = 0,
|
||||
|
||||
@@ -31,9 +31,7 @@ use function strtr;
|
||||
*/
|
||||
class DefaultRegion implements Region
|
||||
{
|
||||
/**
|
||||
* @internal since 2.11, this constant will be private in 3.0.
|
||||
*/
|
||||
/** @internal since 2.11, this constant will be private in 3.0. */
|
||||
public const REGION_KEY_SEPARATOR = '_';
|
||||
private const REGION_PREFIX = 'DC2_REGION_';
|
||||
|
||||
@@ -61,9 +59,7 @@ class DefaultRegion implements Region
|
||||
/** @var CacheItemPoolInterface */
|
||||
private $cacheItemPool;
|
||||
|
||||
/**
|
||||
* @param CacheItemPoolInterface $cacheItemPool
|
||||
*/
|
||||
/** @param CacheItemPoolInterface $cacheItemPool */
|
||||
public function __construct(string $name, $cacheItemPool, int $lifetime = 0)
|
||||
{
|
||||
if ($cacheItemPool instanceof LegacyCache) {
|
||||
|
||||
@@ -103,17 +103,13 @@ class FileLockRegion implements ConcurrentRegion
|
||||
return $this->directory . DIRECTORY_SEPARATOR . $key->hash . '.' . self::LOCK_EXTENSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|false
|
||||
*/
|
||||
/** @return string|false */
|
||||
private function getLockContent(string $filename)
|
||||
{
|
||||
return @file_get_contents($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|false
|
||||
*/
|
||||
/** @return int|false */
|
||||
private function getLockTime(string $filename)
|
||||
{
|
||||
return @fileatime($filename);
|
||||
|
||||
@@ -31,9 +31,7 @@ class RegionsConfiguration
|
||||
$this->defaultLockLifetime = (int) $defaultLockLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
/** @return int */
|
||||
public function getDefaultLifetime()
|
||||
{
|
||||
return $this->defaultLifetime;
|
||||
@@ -49,9 +47,7 @@ class RegionsConfiguration
|
||||
$this->defaultLifetime = (int) $defaultLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
/** @return int */
|
||||
public function getDefaultLockLifetime()
|
||||
{
|
||||
return $this->defaultLockLifetime;
|
||||
|
||||
@@ -17,9 +17,7 @@ class TimestampCacheEntry implements CacheEntry
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param float $time
|
||||
*/
|
||||
/** @param float|null $time */
|
||||
public function __construct($time = null)
|
||||
{
|
||||
$this->time = $time ? (float) $time : microtime(true);
|
||||
|
||||
@@ -9,9 +9,7 @@ namespace Doctrine\ORM\Cache;
|
||||
*/
|
||||
class TimestampCacheKey extends CacheKey
|
||||
{
|
||||
/**
|
||||
* @param string $space Result cache id
|
||||
*/
|
||||
/** @param string $space Result cache id */
|
||||
public function __construct($space)
|
||||
{
|
||||
$this->hash = (string) $space;
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Doctrine\ORM\Cache;
|
||||
interface TimestampRegion extends Region
|
||||
{
|
||||
/**
|
||||
* Update an specific key into the cache region.
|
||||
* Update a specific key into the cache region.
|
||||
*
|
||||
* @param CacheKey $key The key of the item to update the timestamp.
|
||||
* @return void
|
||||
*
|
||||
* @throws LockException Indicates a problem accessing the region.
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,7 @@ use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache as CacheDriver;
|
||||
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
||||
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
|
||||
use Doctrine\Common\Persistence\PersistentObject;
|
||||
use Doctrine\Common\Proxy\AbstractProxyFactory;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
@@ -23,6 +24,7 @@ use Doctrine\ORM\Cache\Exception\QueryCacheUsesNonPersistentCache;
|
||||
use Doctrine\ORM\Exception\InvalidEntityRepository;
|
||||
use Doctrine\ORM\Exception\NamedNativeQueryNotFound;
|
||||
use Doctrine\ORM\Exception\NamedQueryNotFound;
|
||||
use Doctrine\ORM\Exception\NotSupported;
|
||||
use Doctrine\ORM\Exception\ProxyClassesAlwaysRegenerating;
|
||||
use Doctrine\ORM\Exception\UnknownEntityNamespace;
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
@@ -34,6 +36,9 @@ use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
|
||||
use Doctrine\ORM\Mapping\EntityListenerResolver;
|
||||
use Doctrine\ORM\Mapping\NamingStrategy;
|
||||
use Doctrine\ORM\Mapping\QuoteStrategy;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Filter\SQLFilter;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
|
||||
use Doctrine\ORM\Repository\RepositoryFactory;
|
||||
@@ -41,9 +46,9 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use LogicException;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use ReflectionClass;
|
||||
|
||||
use function class_exists;
|
||||
use function is_a;
|
||||
use function method_exists;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
@@ -54,6 +59,8 @@ use function trim;
|
||||
* It combines all configuration options from DBAL & ORM.
|
||||
*
|
||||
* Internal note: When adding a new configuration option just write a getter/setter pair.
|
||||
*
|
||||
* @psalm-import-type AutogenerateMode from ProxyFactory
|
||||
*/
|
||||
class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
@@ -75,10 +82,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the directory where Doctrine generates any necessary proxy class files.
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
*
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProxyDir()
|
||||
@@ -89,11 +92,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
*
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*
|
||||
* @return int Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
|
||||
* @psalm-return AutogenerateMode
|
||||
*/
|
||||
public function getAutoGenerateProxyClasses()
|
||||
{
|
||||
@@ -104,6 +104,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets the strategy for automatically generating proxy classes.
|
||||
*
|
||||
* @param bool|int $autoGenerate Possible values are constants of Doctrine\Common\Proxy\AbstractProxyFactory.
|
||||
* @psalm-param bool|AutogenerateMode $autoGenerate
|
||||
* True is converted to AUTOGENERATE_ALWAYS, false to AUTOGENERATE_NEVER.
|
||||
*
|
||||
* @return void
|
||||
@@ -116,10 +117,6 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
/**
|
||||
* Gets the namespace where proxy classes reside.
|
||||
*
|
||||
* @deprecated 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer
|
||||
*
|
||||
* @see https://github.com/Ocramius/ProxyManager
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getProxyNamespace()
|
||||
@@ -156,6 +153,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Adds a new default annotation driver with a correctly configured annotation reader. If $useSimpleAnnotationReader
|
||||
* is true, the notation `@Entity` will work, otherwise, the notation `@ORM\Entity` will be supported.
|
||||
*
|
||||
* @deprecated Use {@see ORMSetup::createDefaultAnnotationDriver()} instead.
|
||||
*
|
||||
* @param string|string[] $paths
|
||||
* @param bool $useSimpleAnnotationReader
|
||||
* @psalm-param string|list<string> $paths
|
||||
@@ -164,6 +163,14 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9443',
|
||||
'%s is deprecated, call %s::createDefaultAnnotationDriver() instead.',
|
||||
__METHOD__,
|
||||
ORMSetup::class
|
||||
);
|
||||
|
||||
if (! class_exists(AnnotationReader::class)) {
|
||||
throw new LogicException(sprintf(
|
||||
'The annotation metadata driver cannot be enabled because the "doctrine/annotations" library'
|
||||
@@ -193,6 +200,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated No replacement planned.
|
||||
*
|
||||
* Adds a namespace under a certain alias.
|
||||
*
|
||||
* @param string $alias
|
||||
@@ -202,6 +211,21 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function addEntityNamespace($alias, $namespace)
|
||||
{
|
||||
if (class_exists(PersistentObject::class)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/issues/8818',
|
||||
'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.',
|
||||
$alias
|
||||
);
|
||||
} else {
|
||||
NotSupported::createForPersistence3(sprintf(
|
||||
'Using short namespace alias "%s" by calling %s',
|
||||
$alias,
|
||||
__METHOD__
|
||||
));
|
||||
}
|
||||
|
||||
$this->_attributes['entityNamespaces'][$alias] = $namespace;
|
||||
}
|
||||
|
||||
@@ -548,7 +572,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
throw QueryCacheUsesNonPersistentCache::fromDriver($queryCacheImpl);
|
||||
}
|
||||
|
||||
if ($this->getAutoGenerateProxyClasses()) {
|
||||
if ($this->getAutoGenerateProxyClasses() !== AbstractProxyFactory::AUTOGENERATE_NEVER) {
|
||||
throw ProxyClassesAlwaysRegenerating::create();
|
||||
}
|
||||
|
||||
@@ -570,8 +594,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* DQL function names are case-insensitive.
|
||||
*
|
||||
* @param string $name Function name.
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
* @param string $name Function name.
|
||||
* @param class-string|callable $className Class name or a callable that returns the function.
|
||||
* @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -585,8 +610,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string|null
|
||||
* @psalm-return ?class-string
|
||||
* @return string|callable|null
|
||||
* @psalm-return class-string<FunctionNode>|callable(string):FunctionNode|null
|
||||
*/
|
||||
public function getCustomStringFunction($name)
|
||||
{
|
||||
@@ -603,7 +628,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* Any previously added string functions are discarded.
|
||||
*
|
||||
* @psalm-param array<string, class-string> $functions The map of custom
|
||||
* @psalm-param array<string, class-string<FunctionNode>|callable(string):FunctionNode> $functions The map of custom
|
||||
* DQL string functions.
|
||||
*
|
||||
* @return void
|
||||
@@ -622,8 +647,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* DQL function names are case-insensitive.
|
||||
*
|
||||
* @param string $name Function name.
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
* @param string $name Function name.
|
||||
* @param class-string|callable $className Class name or a callable that returns the function.
|
||||
* @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -637,8 +663,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string|null
|
||||
* @psalm-return ?class-string
|
||||
* @return string|callable|null
|
||||
* @psalm-return class-string|callable|null
|
||||
*/
|
||||
public function getCustomNumericFunction($name)
|
||||
{
|
||||
@@ -676,7 +702,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name Function name.
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
* @psalm-param class-string|callable $className
|
||||
* @psalm-param class-string<FunctionNode>|callable(string):FunctionNode $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -690,8 +716,8 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string|null
|
||||
* @psalm-return ?class-string $name
|
||||
* @return string|callable|null
|
||||
* @psalm-return class-string|callable|null $name
|
||||
*/
|
||||
public function getCustomDatetimeFunction($name)
|
||||
{
|
||||
@@ -709,7 +735,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Any previously added date/time functions are discarded.
|
||||
*
|
||||
* @param array $functions The map of custom DQL date/time functions.
|
||||
* @psalm-param array<string, string> $functions
|
||||
* @psalm-param array<string, class-string<FunctionNode>|callable(string):FunctionNode> $functions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -794,6 +820,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name The name of the filter.
|
||||
* @param string $className The class name of the filter.
|
||||
* @psalm-param class-string<SQLFilter> $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -809,7 +836,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @return string|null The class name of the filter, or null if it is not
|
||||
* defined.
|
||||
* @psalm-return ?class-string
|
||||
* @psalm-return class-string<SQLFilter>|null
|
||||
*/
|
||||
public function getFilterClassName($name)
|
||||
{
|
||||
@@ -820,6 +847,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Sets default repository class.
|
||||
*
|
||||
* @param string $className
|
||||
* @psalm-param class-string<EntityRepository> $className
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
@@ -827,12 +855,20 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setDefaultRepositoryClassName($className)
|
||||
{
|
||||
$reflectionClass = new ReflectionClass($className);
|
||||
|
||||
if (! $reflectionClass->implementsInterface(ObjectRepository::class)) {
|
||||
if (! class_exists($className) || ! is_a($className, ObjectRepository::class, true)) {
|
||||
throw InvalidEntityRepository::fromClassName($className);
|
||||
}
|
||||
|
||||
if (! is_a($className, EntityRepository::class, true)) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/orm/pull/9533',
|
||||
'Configuring %s as default repository class is deprecated because it does not extend %s.',
|
||||
$className,
|
||||
EntityRepository::class
|
||||
);
|
||||
}
|
||||
|
||||
$this->_attributes['defaultRepositoryClassName'] = $className;
|
||||
}
|
||||
|
||||
@@ -840,7 +876,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* Get default repository class.
|
||||
*
|
||||
* @return string
|
||||
* @psalm-return class-string
|
||||
* @psalm-return class-string<EntityRepository>
|
||||
*/
|
||||
public function getDefaultRepositoryClassName()
|
||||
{
|
||||
@@ -939,9 +975,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
return $this->_attributes['repositoryFactory'] ?? new DefaultRepositoryFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
/** @return bool */
|
||||
public function isSecondLevelCacheEnabled()
|
||||
{
|
||||
return $this->_attributes['isSecondLevelCacheEnabled'] ?? false;
|
||||
@@ -957,17 +991,13 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
$this->_attributes['isSecondLevelCacheEnabled'] = (bool) $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
/** @return void */
|
||||
public function setSecondLevelCacheConfiguration(CacheConfiguration $cacheConfig)
|
||||
{
|
||||
$this->_attributes['secondLevelCacheConfiguration'] = $cacheConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CacheConfiguration|null
|
||||
*/
|
||||
/** @return CacheConfiguration|null */
|
||||
public function getSecondLevelCacheConfiguration()
|
||||
{
|
||||
if (! isset($this->_attributes['secondLevelCacheConfiguration']) && $this->isSecondLevelCacheEnabled()) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user