mirror of
https://github.com/doctrine/orm.git
synced 2026-03-24 15:02:22 +01:00
Compare commits
1487 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f82485e651 | ||
|
|
c4835cca0d | ||
|
|
82a406332e | ||
|
|
8093c2eef6 | ||
|
|
099e51d899 | ||
|
|
5df84d4ec0 | ||
|
|
f94cb9a5e6 | ||
|
|
c23220b68a | ||
|
|
6255461b84 | ||
|
|
0aa5946286 | ||
|
|
eda7558674 | ||
|
|
13bab31da6 | ||
|
|
f960bc2c11 | ||
|
|
15058ca83e | ||
|
|
9b14786738 | ||
|
|
83f6356f25 | ||
|
|
4e6cb908f6 | ||
|
|
497ee166bd | ||
|
|
9b723a88aa | ||
|
|
db18161a1a | ||
|
|
bd11475615 | ||
|
|
90f1f54e73 | ||
|
|
b25561ad96 | ||
|
|
284e81403b | ||
|
|
3ea8550ca6 | ||
|
|
aa4b62ce78 | ||
|
|
e5e674c686 | ||
|
|
b391431a0e | ||
|
|
d5a6b36e6f | ||
|
|
a2b5bae923 | ||
|
|
aeed977d6e | ||
|
|
f66b008b8b | ||
|
|
5a8541b450 | ||
|
|
fa18e130cb | ||
|
|
2ed3f55c01 | ||
|
|
2dfb4f44e2 | ||
|
|
24bf06725b | ||
|
|
4b577e7a18 | ||
|
|
d5ef6be4cc | ||
|
|
65da1fe8cb | ||
|
|
54336840e6 | ||
|
|
74986f1d53 | ||
|
|
1df221860f | ||
|
|
16cd49ba89 | ||
|
|
57ac275137 | ||
|
|
e958046c4a | ||
|
|
3038f6aeef | ||
|
|
28bf6cb1fa | ||
|
|
e864c4cbc2 | ||
|
|
f9d5a89a39 | ||
|
|
db7333cc84 | ||
|
|
47b4ccc4e6 | ||
|
|
5a7fce12b8 | ||
|
|
cc9e456ed8 | ||
|
|
da356316c1 | ||
|
|
a5a6cc6630 | ||
|
|
1ce806fcb7 | ||
|
|
958d0b6193 | ||
|
|
843f3c3b23 | ||
|
|
82e4c644f9 | ||
|
|
9399f1f3a8 | ||
|
|
fc3201bded | ||
|
|
ad69810775 | ||
|
|
3178b4ec4f | ||
|
|
6c7a5e6faa | ||
|
|
dcc1c26826 | ||
|
|
465c02fe68 | ||
|
|
8afb644a18 | ||
|
|
953e42d059 | ||
|
|
318af0a666 | ||
|
|
7e45ad935c | ||
|
|
90ececcc85 | ||
|
|
88b36e07e1 | ||
|
|
a37c2cc05f | ||
|
|
40b34b03c1 | ||
|
|
8d9ab72613 | ||
|
|
12f0674b1a | ||
|
|
069206ba14 | ||
|
|
e8a4d2e91b | ||
|
|
284baf890e | ||
|
|
a1f9b28cdc | ||
|
|
2b7485af97 | ||
|
|
edb6332359 | ||
|
|
48c1eef1b8 | ||
|
|
5d88dc9be4 | ||
|
|
0b14c01b93 | ||
|
|
c97f0a1078 | ||
|
|
474f76fc8b | ||
|
|
25ce9b9101 | ||
|
|
75340b68b2 | ||
|
|
543be3fe35 | ||
|
|
552d98d554 | ||
|
|
8f605c652a | ||
|
|
1edfa91714 | ||
|
|
75e42abfdf | ||
|
|
3133bf06c2 | ||
|
|
7cf4074d3a | ||
|
|
bb1deba510 | ||
|
|
c828a3814b | ||
|
|
a5553a0786 | ||
|
|
cf91ce63d3 | ||
|
|
f3f453286f | ||
|
|
7cb96fcf0e | ||
|
|
ac94d826dc | ||
|
|
f33919d7d6 | ||
|
|
f256d996cc | ||
|
|
d617323a48 | ||
|
|
6b61e26238 | ||
|
|
edad800711 | ||
|
|
0b9c949590 | ||
|
|
3ee7d96179 | ||
|
|
fba05675b6 | ||
|
|
8160e89c5a | ||
|
|
a76b776802 | ||
|
|
ad1d1ca942 | ||
|
|
be835bb8e2 | ||
|
|
9f926f04ba | ||
|
|
4e445feb6c | ||
|
|
d79e61f8d9 | ||
|
|
06eafd82ac | ||
|
|
bac784c9ba | ||
|
|
5f4b76b88f | ||
|
|
794777b21f | ||
|
|
119c378a3a | ||
|
|
90bc6dc300 | ||
|
|
1ad936a448 | ||
|
|
e93e8e0bdf | ||
|
|
7e75807918 | ||
|
|
bdf067b58a | ||
|
|
f08b67f0cc | ||
|
|
5f8504b5cf | ||
|
|
16e25656d9 | ||
|
|
cacdc56b6e | ||
|
|
90f82202a8 | ||
|
|
97aa5b37e6 | ||
|
|
13d1c7b286 | ||
|
|
1a9f40c785 | ||
|
|
06c77cebb5 | ||
|
|
1ed0057276 | ||
|
|
7ce9a6fe5c | ||
|
|
8e062955d5 | ||
|
|
5b6f3cd598 | ||
|
|
38271d4aeb | ||
|
|
84213b9f05 | ||
|
|
0e65b0c3dc | ||
|
|
e750360bd5 | ||
|
|
e14e9bebcc | ||
|
|
e8ac1169ad | ||
|
|
9422260efd | ||
|
|
d46512332c | ||
|
|
0d56ffc261 | ||
|
|
dd8c7003b8 | ||
|
|
9bec416bb0 | ||
|
|
021722acc7 | ||
|
|
5a528bef5d | ||
|
|
5fbcb18334 | ||
|
|
8280f41fa5 | ||
|
|
d95d8d0a19 | ||
|
|
d36004f825 | ||
|
|
18366db578 | ||
|
|
95c818928e | ||
|
|
f27034880a | ||
|
|
3735822662 | ||
|
|
52f7ddc680 | ||
|
|
de32d8239f | ||
|
|
1b5bef3d4d | ||
|
|
880b622fe6 | ||
|
|
397751ee65 | ||
|
|
b0038eeea6 | ||
|
|
f06f9f843e | ||
|
|
cbf141f7ed | ||
|
|
5d3fc62023 | ||
|
|
154e7130f5 | ||
|
|
536bb4f678 | ||
|
|
3f2f42277e | ||
|
|
99f044cbc7 | ||
|
|
e8ca7b4abf | ||
|
|
b05fb96ad3 | ||
|
|
bb020320ca | ||
|
|
1e0ac8899c | ||
|
|
757d950128 | ||
|
|
f07840b10c | ||
|
|
6e8afeeb60 | ||
|
|
9130b27aca | ||
|
|
2e7c2bb385 | ||
|
|
d69a0fa2cf | ||
|
|
4a04c8c2db | ||
|
|
a8b02fd70f | ||
|
|
60adc6b7d9 | ||
|
|
c65ff6651c | ||
|
|
5f29fcdea2 | ||
|
|
470e2ddd05 | ||
|
|
6ec5ab9145 | ||
|
|
2b8ac15813 | ||
|
|
86000d9c70 | ||
|
|
6dd07e4c76 | ||
|
|
328bf707cc | ||
|
|
0d043059b9 | ||
|
|
bb3ce7e802 | ||
|
|
bc7e252f00 | ||
|
|
498da2ff98 | ||
|
|
73e1e42ab5 | ||
|
|
7a9037e9d7 | ||
|
|
5d11648767 | ||
|
|
8d03f8f089 | ||
|
|
0ecac1b255 | ||
|
|
8dfe8b8782 | ||
|
|
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 | ||
|
|
9c351e0444 | ||
|
|
5ed5383338 | ||
|
|
eb1d54871b | ||
|
|
e148c838b0 | ||
|
|
c73df2a7b4 | ||
|
|
bc6c6c9f0c | ||
|
|
89d0a6a67c | ||
|
|
38682e93db | ||
|
|
1febeaca7f | ||
|
|
229dcb082b | ||
|
|
3849aed6fb | ||
|
|
f82db6a894 | ||
|
|
a8a859cf5e | ||
|
|
84df37de97 | ||
|
|
7be96f64ab | ||
|
|
947935e4c9 | ||
|
|
40af1fcfc6 | ||
|
|
021444b322 | ||
|
|
ec7c637cf2 | ||
|
|
0a0779c4a9 | ||
|
|
856c3143f8 | ||
|
|
79f73a23f3 | ||
|
|
a52d9880cc | ||
|
|
4af912f712 | ||
|
|
65f48e0ecd | ||
|
|
08eaba44ca | ||
|
|
05c35c398f | ||
|
|
193c3abf0e | ||
|
|
dac1875a79 | ||
|
|
5b8263e8fb | ||
|
|
26e85b8c88 | ||
|
|
152c04c03d | ||
|
|
12ab6fa43f | ||
|
|
5a55772559 | ||
|
|
e8e61cbbd5 | ||
|
|
d78fa52ad7 | ||
|
|
4ddaa5fc20 | ||
|
|
8f847cb5aa | ||
|
|
d7abcb01bc | ||
|
|
599832cb81 | ||
|
|
8cff7dcdaf | ||
|
|
530f515556 | ||
|
|
601728045c | ||
|
|
b18cd893be | ||
|
|
21390a12b9 | ||
|
|
182bcbae23 | ||
|
|
1c55025b12 | ||
|
|
0900d4bc97 | ||
|
|
be2518d784 | ||
|
|
978f687df9 | ||
|
|
fd1690431f | ||
|
|
3cfcd4ad13 | ||
|
|
69b0b764e3 | ||
|
|
e11cef5fca | ||
|
|
395c02caf4 | ||
|
|
0c4e739e94 | ||
|
|
7a72526e47 | ||
|
|
bdd8883d12 | ||
|
|
5f882b1cdd | ||
|
|
b3d849dd38 | ||
|
|
536b65f02b | ||
|
|
103c42cdb7 | ||
|
|
aa1dd881d8 | ||
|
|
92d27f2fea | ||
|
|
f8de44c35f | ||
|
|
cdaf7b5308 | ||
|
|
f81980e1fa | ||
|
|
e9e54d8f65 | ||
|
|
4b88ce787d | ||
|
|
04bfdf85de | ||
|
|
f9c3470a8d | ||
|
|
c1b131b67e | ||
|
|
16b82ea061 | ||
|
|
f8f370ace6 | ||
|
|
43f67c6164 | ||
|
|
d5c69fb73f | ||
|
|
93f9eb7af2 | ||
|
|
f5be4183ce | ||
|
|
eed031fab0 | ||
|
|
6d5da83c68 | ||
|
|
328f36846e | ||
|
|
5f01dd8d09 | ||
|
|
b596e6a665 | ||
|
|
79d3cf5880 | ||
|
|
f7822c775d | ||
|
|
8c08792f0e | ||
|
|
026bba23f1 | ||
|
|
4305cb9230 | ||
|
|
d7b7c28ae5 | ||
|
|
2886d0dc92 | ||
|
|
d6fd510c49 | ||
|
|
a2a7d5bb01 | ||
|
|
223b2650c4 | ||
|
|
07f1c4e8f8 | ||
|
|
01c1644d9c | ||
|
|
3eff2d4b3f | ||
|
|
9ddf8b96f8 | ||
|
|
3d00fa817a | ||
|
|
0809a2b671 | ||
|
|
c0a1404e4c | ||
|
|
bfed8cb6ed | ||
|
|
09a2648f7e | ||
|
|
ee591195cf | ||
|
|
e974313523 | ||
|
|
1e972b6e0e | ||
|
|
e369cb6e73 | ||
|
|
ec391be4f2 | ||
|
|
697e23422f | ||
|
|
e487b6fe2b | ||
|
|
656f881756 | ||
|
|
cd2aa487a5 | ||
|
|
b7d822972e | ||
|
|
ec63f5d32a | ||
|
|
952ccc5fc8 | ||
|
|
9a2f1f380d | ||
|
|
580b9196e6 | ||
|
|
0d911b9381 | ||
|
|
c6d8aecc0f | ||
|
|
fdd3d112b0 | ||
|
|
2fecb3cb1a | ||
|
|
f3630ea16b | ||
|
|
fd19444761 | ||
|
|
4b1afb41b3 | ||
|
|
f9f453f4d7 | ||
|
|
f508a4bb71 | ||
|
|
5d0fbc47d0 | ||
|
|
1e977426eb | ||
|
|
2640f88f8a | ||
|
|
fa731b10ec | ||
|
|
4117ca349f | ||
|
|
2d475c9bb3 | ||
|
|
6f54011e7b | ||
|
|
760397c429 | ||
|
|
7190ac5127 | ||
|
|
ceaefcb18d | ||
|
|
844ce77cae | ||
|
|
cf3a185b62 | ||
|
|
efc982a48d | ||
|
|
96bc214acd | ||
|
|
15999758a7 | ||
|
|
44aa8c2c5b | ||
|
|
8c6fc5ae52 | ||
|
|
c4561571aa | ||
|
|
40a203843d | ||
|
|
8b5ee54c6a | ||
|
|
03fa495fbc | ||
|
|
5901848944 | ||
|
|
d40f9e57ff | ||
|
|
133cc95f33 | ||
|
|
d30e748e64 | ||
|
|
98d77043d8 | ||
|
|
40d1e7bbfc | ||
|
|
e8275f6e4d | ||
|
|
70dcffa025 | ||
|
|
c94a9b1d8b | ||
|
|
6a9393e8ed | ||
|
|
ab98d0ffc6 | ||
|
|
2ddeb79431 | ||
|
|
92ff9c9108 | ||
|
|
7c58dc89c3 | ||
|
|
b513f7c935 | ||
|
|
f1483f848c | ||
|
|
ea4c9b21b7 | ||
|
|
72edfbc270 | ||
|
|
5ccf2eac40 | ||
|
|
6696b0dfbf | ||
|
|
aead77d597 | ||
|
|
130c27c1da | ||
|
|
f6e1dd44f0 | ||
|
|
1e9973a0c0 | ||
|
|
91761738fd | ||
|
|
cccb2e2fdf | ||
|
|
18138d895e | ||
|
|
95d434d003 | ||
|
|
70c651ebb7 | ||
|
|
8cb62a616a | ||
|
|
fa2b52c974 | ||
|
|
6d306c1946 | ||
|
|
5bf814032f | ||
|
|
bea5e7166c | ||
|
|
003090b70c | ||
|
|
02a4e4099d | ||
|
|
56e0ac02af | ||
|
|
12a70bbefb | ||
|
|
5a4ddb2870 | ||
|
|
42195060e6 | ||
|
|
68fa55f310 | ||
|
|
0b0c3e7e58 | ||
|
|
92434f91c7 | ||
|
|
6414ad4cbb | ||
|
|
ac5aea1c81 | ||
|
|
a75605b8c3 | ||
|
|
7b24275346 | ||
|
|
ed1a576305 | ||
|
|
66c95a65c5 | ||
|
|
62a0d7359b | ||
|
|
2c7d7ebb48 | ||
|
|
8b6fe52f74 | ||
|
|
eabb7f84e9 | ||
|
|
f0a20dbc9c | ||
|
|
15ec77fa79 | ||
|
|
32cd2106d0 | ||
|
|
6857a2e8d4 | ||
|
|
5e8b34ae30 | ||
|
|
a9b682b7c0 | ||
|
|
0aadc456dc | ||
|
|
cac2acae07 | ||
|
|
146b465ec1 | ||
|
|
5aba762a33 | ||
|
|
77b7107d05 | ||
|
|
a663dda869 | ||
|
|
db14f0fa89 | ||
|
|
2488b4c50c | ||
|
|
ed642c72c9 | ||
|
|
9a74ae6280 | ||
|
|
32eb38ebd9 | ||
|
|
2dde65c4ba | ||
|
|
176fbedc69 | ||
|
|
1b15af44b6 | ||
|
|
8336420a26 | ||
|
|
1e971d85c4 | ||
|
|
a6b7569d7a | ||
|
|
9e37c788ef | ||
|
|
ca0a6bbf71 | ||
|
|
a3da3d78d4 | ||
|
|
e1c2d2e65d | ||
|
|
6f194eeabf | ||
|
|
16cbc16998 | ||
|
|
5e6608b48e | ||
|
|
94bc137526 | ||
|
|
276a0f55ee | ||
|
|
dbaf99f3d9 | ||
|
|
97411f5567 | ||
|
|
641330baa6 | ||
|
|
2074fc3ff9 | ||
|
|
35e680cd3f | ||
|
|
705d88eaba | ||
|
|
8fef44333b | ||
|
|
3271d8f6e2 | ||
|
|
3622381f8c | ||
|
|
f2729b0610 | ||
|
|
cd44547573 | ||
|
|
3361691d0a | ||
|
|
81d472f6f9 | ||
|
|
d458968cee | ||
|
|
b6a2257758 | ||
|
|
5eb01da0a0 | ||
|
|
5aaf361139 | ||
|
|
6a8dcbc392 | ||
|
|
12babcc1c2 | ||
|
|
416aa1d2d7 | ||
|
|
06d9c584a3 | ||
|
|
8e16bb4ddc | ||
|
|
e1dee439bb | ||
|
|
e313d012ae | ||
|
|
dede619b9e | ||
|
|
142cfb39fc | ||
|
|
53d41a456a | ||
|
|
95b34ca940 | ||
|
|
3eaf76eebd | ||
|
|
5c12d36be3 | ||
|
|
1ee68eb318 | ||
|
|
705c7f0a4b | ||
|
|
8c5e49efc0 | ||
|
|
483e09cf1c | ||
|
|
bbb68d0072 | ||
|
|
b0381b3705 | ||
|
|
f346379c7b | ||
|
|
b1c31e1aac | ||
|
|
02b6f9c335 | ||
|
|
d14d9919c7 | ||
|
|
3984f74eb4 | ||
|
|
ebdced6175 | ||
|
|
1a702075ba | ||
|
|
bd79e3d383 | ||
|
|
10f72417c9 | ||
|
|
87ad869a8a | ||
|
|
bc4659b73c | ||
|
|
4eab6536c3 | ||
|
|
1571c8a781 | ||
|
|
20a65cbe32 | ||
|
|
07e15a0038 | ||
|
|
5918cfaa20 | ||
|
|
73fa465c26 | ||
|
|
e8a221d227 | ||
|
|
b734a7d155 | ||
|
|
bbe4022566 | ||
|
|
63f3abfbe8 | ||
|
|
22added5fa | ||
|
|
3a3b53e11d | ||
|
|
ddcea63d0f | ||
|
|
e800f90d7c | ||
|
|
6d16147d60 | ||
|
|
9ed1fe59f2 | ||
|
|
f805526336 | ||
|
|
2e86134c0b | ||
|
|
5f768742a0 | ||
|
|
7a8c086d44 | ||
|
|
1d4e12bc6b | ||
|
|
70b0f50d13 | ||
|
|
149c4308bb | ||
|
|
9d4fac088c | ||
|
|
eb27acaa65 | ||
|
|
2362aa1a7a | ||
|
|
f414e57d82 | ||
|
|
13543df649 | ||
|
|
1d7fdde81d | ||
|
|
5326736571 | ||
|
|
78d07b0bd2 | ||
|
|
51ff4713b3 | ||
|
|
c0f70204d1 | ||
|
|
2575aa5120 | ||
|
|
1f6401ee0a | ||
|
|
248ff82f83 | ||
|
|
f1db7d7fa2 | ||
|
|
0bcc3ee4e9 | ||
|
|
0bd651abda | ||
|
|
334ca18171 | ||
|
|
ff978ce4d8 | ||
|
|
128ebe630b | ||
|
|
6371081593 | ||
|
|
31d8bd7a5e | ||
|
|
dee58cfefd | ||
|
|
71f1fdb668 | ||
|
|
85488d69e2 | ||
|
|
5c7e6689fc | ||
|
|
5b3fb6ac56 | ||
|
|
65839235ce | ||
|
|
d1cd8047fa | ||
|
|
90ed9f5387 | ||
|
|
04d28a9362 | ||
|
|
fb89129fb2 | ||
|
|
399b69a309 | ||
|
|
01ab70d204 | ||
|
|
45553556d5 | ||
|
|
996fa777bd | ||
|
|
dc1336dbc2 | ||
|
|
b1f89a5cb8 | ||
|
|
48f7abf697 | ||
|
|
2159fbee56 | ||
|
|
7fcab3d52e | ||
|
|
2d42d7835d | ||
|
|
ed83825223 | ||
|
|
beee34055a | ||
|
|
7abd106c8a | ||
|
|
be2208f208 | ||
|
|
316ba5f75e | ||
|
|
a08b6306d3 | ||
|
|
21e71af13f | ||
|
|
f352b2a7ed | ||
|
|
7bf1ad1a5a | ||
|
|
9de601f377 | ||
|
|
df5086196f | ||
|
|
a427d7d852 | ||
|
|
efbcca3cb6 | ||
|
|
c65cc91f5b | ||
|
|
d5f65ba62e | ||
|
|
ef9c984bcd | ||
|
|
3074a4b02d | ||
|
|
fdbc6b6c13 | ||
|
|
ea584992d5 | ||
|
|
96b4c763e4 | ||
|
|
0b55275418 | ||
|
|
1963733311 | ||
|
|
a06bbafd6a | ||
|
|
250f7acc98 | ||
|
|
82f8a7c56a | ||
|
|
b345488272 | ||
|
|
1de4020dc9 | ||
|
|
dc6ed8716d | ||
|
|
a8a9b2ae75 | ||
|
|
e03a30bd85 | ||
|
|
16357c5666 | ||
|
|
131cc17384 | ||
|
|
c18b474bbf | ||
|
|
e3b1ad5591 | ||
|
|
e6f1bb7dad | ||
|
|
730143e39b | ||
|
|
5cd00a50b2 | ||
|
|
3b8b3f9034 | ||
|
|
bbe0b17b93 | ||
|
|
7446569cf4 | ||
|
|
930c2e093c | ||
|
|
2154b513af | ||
|
|
93508438fa | ||
|
|
1490b2c3bb | ||
|
|
760abfc316 | ||
|
|
a1c15778ae | ||
|
|
4f9c104ec9 | ||
|
|
77cc86ed88 | ||
|
|
627113dc60 | ||
|
|
234829644c | ||
|
|
af4ecbadab | ||
|
|
b20743b352 | ||
|
|
e546cdef51 | ||
|
|
5f3e17152b | ||
|
|
0d6ff230da | ||
|
|
14da92cf6b | ||
|
|
563f3bdd85 | ||
|
|
8bed63090b | ||
|
|
4f28ad6ccc | ||
|
|
3f98633704 | ||
|
|
e7758866c9 | ||
|
|
1b1d1a246f | ||
|
|
db175020e0 | ||
|
|
1de28c2cab | ||
|
|
565987f583 | ||
|
|
44bea09b10 | ||
|
|
ae10af0259 | ||
|
|
d636d79686 | ||
|
|
aee197f027 | ||
|
|
38c0f2b205 | ||
|
|
3dfbce1f40 | ||
|
|
ad2cbd6afe | ||
|
|
f34215d56a | ||
|
|
163aef158b | ||
|
|
40f613199a | ||
|
|
ed230264fb | ||
|
|
b19a13f4ed | ||
|
|
03948f891e | ||
|
|
7dfa140542 | ||
|
|
01e7e45744 | ||
|
|
1e2c0ce72d | ||
|
|
6a6bcc1e2b | ||
|
|
e2f54f6fa6 | ||
|
|
e16a768916 | ||
|
|
25135d429f | ||
|
|
3b9e04e971 | ||
|
|
7c1593742c | ||
|
|
fca1f5240d | ||
|
|
5685dc05f6 | ||
|
|
4fa2f6baa4 | ||
|
|
245563e1cf | ||
|
|
f980682829 | ||
|
|
b600c01bca | ||
|
|
ad43cc04ff | ||
|
|
c45402c1eb | ||
|
|
a35ce43a61 | ||
|
|
ed32b4c812 | ||
|
|
a5436be939 | ||
|
|
1246b3b5c3 | ||
|
|
4ad5d3edbd | ||
|
|
355a4a126b | ||
|
|
3464591763 | ||
|
|
ae4bcd61ee | ||
|
|
dc960d7d96 | ||
|
|
7736429e9b | ||
|
|
edaa05a217 | ||
|
|
7c6bea1307 | ||
|
|
c4456a2863 | ||
|
|
2bf0f64295 | ||
|
|
12705b5c3e | ||
|
|
913700b116 | ||
|
|
106ed8009a | ||
|
|
b2e00f6086 | ||
|
|
0540485b14 | ||
|
|
e5228ba66f | ||
|
|
e09f126abf | ||
|
|
055b646d9a | ||
|
|
acdbbdacab | ||
|
|
6a267f588c | ||
|
|
c1c3c89836 | ||
|
|
692277e72c | ||
|
|
47267b0da5 | ||
|
|
6fc0176f87 | ||
|
|
8bb1454d5d | ||
|
|
42126dc1bd | ||
|
|
5861b0575d | ||
|
|
afe0d1c810 | ||
|
|
bc9e0b3d2c | ||
|
|
0b6ab2d1a7 | ||
|
|
aa9d0148d5 | ||
|
|
183f4a5211 | ||
|
|
86703cbc73 | ||
|
|
0504c535f1 | ||
|
|
84afd6c937 | ||
|
|
7065070838 | ||
|
|
3e18990e90 | ||
|
|
6b481be074 | ||
|
|
52f5528d3a | ||
|
|
0db4a3936f | ||
|
|
ccc2993610 | ||
|
|
b7c0e97e71 | ||
|
|
6c7e854797 | ||
|
|
efb74f3ba3 | ||
|
|
270e7a4234 | ||
|
|
decbd93af4 | ||
|
|
4770008cb7 | ||
|
|
1413111099 | ||
|
|
60cf2c785f | ||
|
|
ee7ddac7a2 | ||
|
|
e7de028d2d | ||
|
|
3c4009df38 | ||
|
|
0a1be2cc21 | ||
|
|
e39a9ba199 | ||
|
|
65a55cea7e | ||
|
|
9e6bc35944 | ||
|
|
c289b79fb2 | ||
|
|
03a728dfc8 | ||
|
|
558ebcdc83 | ||
|
|
4b06fb2424 | ||
|
|
474218395a | ||
|
|
70092b9800 | ||
|
|
cd13addcfc | ||
|
|
5b6a1d7a40 | ||
|
|
74ffc25b50 | ||
|
|
91de49e6a6 | ||
|
|
102484dea8 | ||
|
|
f9b9a14275 | ||
|
|
8b3d5848a2 | ||
|
|
9c5d676111 | ||
|
|
84ad007de3 | ||
|
|
e88d261dca | ||
|
|
95408cd8e4 | ||
|
|
182bdaac6b | ||
|
|
3c805b22b4 | ||
|
|
7a56ca13f8 | ||
|
|
6a41ab56ce | ||
|
|
802dd54f07 | ||
|
|
be2d99e5f6 | ||
|
|
0a663da5b6 | ||
|
|
0b3fc57458 | ||
|
|
836c0d3803 | ||
|
|
aa3ed91dd2 | ||
|
|
f542dde131 | ||
|
|
a165d4af7c | ||
|
|
ff13059ba2 | ||
|
|
c3953435dd | ||
|
|
37f60be836 | ||
|
|
9f5b38f539 | ||
|
|
665e5c49ea | ||
|
|
3a10e07dfc | ||
|
|
909de45c5c | ||
|
|
5aa7f75f77 | ||
|
|
8f2aef5fa3 | ||
|
|
05be0e8bbd | ||
|
|
64cf6edea6 | ||
|
|
7608a40463 | ||
|
|
b1f6f9bfc3 | ||
|
|
71af666c38 | ||
|
|
3fe980de96 | ||
|
|
f5d988de4e | ||
|
|
e840aef630 | ||
|
|
2936bac7ec | ||
|
|
55971d2f05 | ||
|
|
1f6bfe1754 | ||
|
|
0723e664e1 | ||
|
|
b0c7993dd6 | ||
|
|
796af72650 | ||
|
|
02174fb68f | ||
|
|
92c51de734 | ||
|
|
233d9b0276 | ||
|
|
b6d7826dc7 | ||
|
|
ce8de6df7f | ||
|
|
b3ee7141eb | ||
|
|
73aa6e8352 | ||
|
|
d1e5c59af9 | ||
|
|
e792777187 | ||
|
|
afa837a361 | ||
|
|
5a9bd0904b | ||
|
|
b50d06ae58 | ||
|
|
220201cf14 | ||
|
|
5fa3a92ecb | ||
|
|
fef46263b5 | ||
|
|
e6d9f99ac0 | ||
|
|
f3e87d2c2f | ||
|
|
244e7c7c29 | ||
|
|
53ba6b9732 | ||
|
|
b3f580bf5e | ||
|
|
774b5cbdd4 | ||
|
|
7fa3e6ec7c | ||
|
|
e743981f8d | ||
|
|
a469514ef0 | ||
|
|
9fb13dbe28 | ||
|
|
fc97041e49 | ||
|
|
f8f3b196a1 | ||
|
|
c01961840e | ||
|
|
6ef1367cde | ||
|
|
3572b49e6a | ||
|
|
587c5f5ad6 | ||
|
|
4f0a04e0eb | ||
|
|
4451019dc0 | ||
|
|
c021426613 | ||
|
|
243c8bff1f | ||
|
|
5bbad8c403 | ||
|
|
6c6b919788 | ||
|
|
b37c433080 | ||
|
|
173f31a14a | ||
|
|
319acb1076 | ||
|
|
242d2c1c41 | ||
|
|
5c95ce5c21 | ||
|
|
fa583f6533 | ||
|
|
7a78fd2900 | ||
|
|
67fc57b5e1 | ||
|
|
b4547888d9 | ||
|
|
f233e4cf6b | ||
|
|
7277afefb9 | ||
|
|
b9f7e09401 | ||
|
|
5e91eea726 | ||
|
|
10922a5329 | ||
|
|
fbf793af0e | ||
|
|
a26ae0648f | ||
|
|
8bb564d5fe | ||
|
|
08149ea0b9 | ||
|
|
06844484dd | ||
|
|
7827869191 | ||
|
|
23602784f8 | ||
|
|
82e77cf508 | ||
|
|
1518b40dd2 | ||
|
|
10e41ec8bc | ||
|
|
303e346390 | ||
|
|
fc7db8f59e | ||
|
|
ae7f04ea53 | ||
|
|
b8808099ea | ||
|
|
6432a3eeb2 | ||
|
|
3a0f60d6c6 | ||
|
|
ee19cf5cfd | ||
|
|
66daafd597 | ||
|
|
249c4fe61b | ||
|
|
89673c60bf | ||
|
|
f8bcd2d200 | ||
|
|
75b4b88c5b | ||
|
|
d9e59d6862 | ||
|
|
5fa94969de | ||
|
|
f2c3ddac97 | ||
|
|
46f0da9ffa | ||
|
|
1e832a6782 | ||
|
|
56bdb44efd | ||
|
|
fffac44991 | ||
|
|
e42b3d6584 | ||
|
|
7ab2c3abbd | ||
|
|
498c816b65 | ||
|
|
eec740079d | ||
|
|
c359715a97 | ||
|
|
f3e55fae9f | ||
|
|
91c3bd4121 | ||
|
|
e6cf12c66f | ||
|
|
99d67cb77d | ||
|
|
43f66d5808 | ||
|
|
a6577b89a2 | ||
|
|
0ca87566a9 | ||
|
|
5d01f94a36 | ||
|
|
3d02b02636 | ||
|
|
6de321cb09 | ||
|
|
535bc92dc8 | ||
|
|
ebb5d03f7a | ||
|
|
8e13369621 | ||
|
|
8eff4b775a | ||
|
|
b85403d0a2 | ||
|
|
22ce3adfce | ||
|
|
3a194ad699 | ||
|
|
d52dab54dd | ||
|
|
b5ac7714bc | ||
|
|
590551d5c3 | ||
|
|
c9fb9fdb40 | ||
|
|
965926dcc8 | ||
|
|
a6e30c5f4c | ||
|
|
30ab6f4cea | ||
|
|
5e5a44dce2 | ||
|
|
d7bf30b291 | ||
|
|
ce8da6623f | ||
|
|
2ecec0c5d6 | ||
|
|
6f128e4515 | ||
|
|
e24b0f0be7 | ||
|
|
6753b26f73 | ||
|
|
4ccc4e19fc | ||
|
|
4e2009433b | ||
|
|
c25b822217 | ||
|
|
c3dcc5af91 | ||
|
|
b2f404b25f | ||
|
|
d141f27875 | ||
|
|
4691839201 | ||
|
|
91387382b7 | ||
|
|
f634c64b7a | ||
|
|
7ba9c980b5 | ||
|
|
dacdcf2c7b | ||
|
|
f296fee9e4 | ||
|
|
8555fc1d34 | ||
|
|
b0826fd746 | ||
|
|
fe93c2e9d5 | ||
|
|
850d57827f | ||
|
|
e1388fa986 | ||
|
|
9a48450481 | ||
|
|
cff8b96dd6 | ||
|
|
996c1c74b3 | ||
|
|
48612e6dc6 | ||
|
|
ddfee26f80 | ||
|
|
eb860a704e | ||
|
|
51ffcb4891 | ||
|
|
72f500318a | ||
|
|
55f030f66b | ||
|
|
95af30eb72 | ||
|
|
9ea0769d78 | ||
|
|
22413453da | ||
|
|
06fadcdd8c | ||
|
|
7c56aa2141 | ||
|
|
4cdcb5f760 | ||
|
|
b542b36e45 | ||
|
|
e5a7a13e1e | ||
|
|
8336dd3779 | ||
|
|
b04d7a62ae | ||
|
|
a959a474fd | ||
|
|
ce128e742b | ||
|
|
dac87dae06 | ||
|
|
a2230485b2 | ||
|
|
a68aa580c5 | ||
|
|
5ee71c54d4 | ||
|
|
dc37c2cd2f | ||
|
|
261a405970 | ||
|
|
1ea51d88c4 | ||
|
|
da3a9fa361 | ||
|
|
4fd81d26ff | ||
|
|
f8e06ad31e | ||
|
|
559c1ba806 | ||
|
|
4665758c44 | ||
|
|
e2e9f8fa97 | ||
|
|
f7249ec709 | ||
|
|
87dbcca454 | ||
|
|
ceeea8ccd1 | ||
|
|
6e16ef8c31 | ||
|
|
305e0d6664 | ||
|
|
199be94e6d | ||
|
|
09a7d9f18a | ||
|
|
f57f33b67f | ||
|
|
e86cddb360 | ||
|
|
fa588af3b1 | ||
|
|
d4741720fa | ||
|
|
343385d060 | ||
|
|
6d04dced03 | ||
|
|
22fa3a8556 | ||
|
|
eb05756dc3 | ||
|
|
5bb7e20708 | ||
|
|
a9076313c7 | ||
|
|
2a87821b28 | ||
|
|
da5877d60c | ||
|
|
67dfe8e1af | ||
|
|
2dfe51b396 | ||
|
|
5ac036de02 | ||
|
|
fda0d7b440 | ||
|
|
23e1fd8ad6 | ||
|
|
f8fa0fe069 | ||
|
|
a588555ecd | ||
|
|
501057da83 | ||
|
|
7de84537f6 | ||
|
|
97f8325dad | ||
|
|
0ebd7052d7 | ||
|
|
5d73378b92 | ||
|
|
10572ec441 | ||
|
|
76278d801d | ||
|
|
ca80830b26 | ||
|
|
1ed89c756a | ||
|
|
bb078b5cb7 | ||
|
|
bcb4889a2c | ||
|
|
961da8b0cc | ||
|
|
657a30f8ce | ||
|
|
c3f8996af5 | ||
|
|
0655083e50 | ||
|
|
0b25d4d8b0 | ||
|
|
a88242ee6c | ||
|
|
fe4964008d | ||
|
|
3f3de70c3e | ||
|
|
eb4e317144 | ||
|
|
c8f2f61ea1 | ||
|
|
c9502d3d0b | ||
|
|
b6b3c97436 | ||
|
|
8f6d146bc4 | ||
|
|
3358ccde39 | ||
|
|
1f4e6ebeeb | ||
|
|
a94db4f5c0 | ||
|
|
47475f3a67 | ||
|
|
61c4a5da0a | ||
|
|
dd34bca4eb | ||
|
|
3e21c50f61 | ||
|
|
bc3592bcc8 | ||
|
|
4fccec1322 | ||
|
|
0177133385 | ||
|
|
8df5cb84fa | ||
|
|
3b7275e183 | ||
|
|
5247c56fce | ||
|
|
cc37c490c2 | ||
|
|
95824efd61 | ||
|
|
44d4712e64 | ||
|
|
930f44c02f | ||
|
|
6e3c011e65 | ||
|
|
a82de0d422 | ||
|
|
9917488179 | ||
|
|
93f31d2c33 | ||
|
|
77356b954f | ||
|
|
92f764206e | ||
|
|
141539673e | ||
|
|
23dc804c9b | ||
|
|
9e3baa7baa | ||
|
|
322ea51ecf | ||
|
|
21b046452b | ||
|
|
c57b81ada4 | ||
|
|
4fa7c9c6de | ||
|
|
2685b65c2b | ||
|
|
3902a4eb6e | ||
|
|
b3ed525d4d | ||
|
|
3580517aac | ||
|
|
4cdc6b1a71 | ||
|
|
38ccbd8638 | ||
|
|
f9e7c3c2d8 | ||
|
|
3600c0fbca | ||
|
|
f779513042 | ||
|
|
b4e6530d2d | ||
|
|
07d426edf5 | ||
|
|
4afd4069be | ||
|
|
239215c2e5 | ||
|
|
e6f11652d2 | ||
|
|
2910a73927 | ||
|
|
3959b2743c | ||
|
|
658e54027e | ||
|
|
ba882451b0 | ||
|
|
1ed9840123 | ||
|
|
71044894a1 | ||
|
|
1a41d6b87c | ||
|
|
5dfcb08999 | ||
|
|
284bd6fd03 | ||
|
|
e40ac3e1d0 | ||
|
|
0bce2472f2 | ||
|
|
89f57de884 | ||
|
|
ae19f40958 | ||
|
|
c2d69a3c48 | ||
|
|
6ce91dd37b | ||
|
|
57e6ba25c9 | ||
|
|
9d2e67bbb4 | ||
|
|
2dce5b20ad | ||
|
|
930859f803 | ||
|
|
a70c73ae3a | ||
|
|
074346b8d5 | ||
|
|
9ed4a8c043 | ||
|
|
9c917811e5 | ||
|
|
f883820257 | ||
|
|
a32045dd51 | ||
|
|
672b04a55d | ||
|
|
261334aca2 | ||
|
|
7f6ed094cd | ||
|
|
a792655813 | ||
|
|
fb71204910 | ||
|
|
b918661cf1 | ||
|
|
7971a53164 | ||
|
|
a175f96ae8 | ||
|
|
7c1cde6471 | ||
|
|
1ffc0cacf4 | ||
|
|
e979d0d50f | ||
|
|
553ea03079 | ||
|
|
149014879d | ||
|
|
b991c58988 | ||
|
|
ee9627b82e | ||
|
|
e3f03414f9 | ||
|
|
1f406fd3df | ||
|
|
0ae53a6703 | ||
|
|
49864a7f57 | ||
|
|
b747bf15ff | ||
|
|
5fe85bfc03 | ||
|
|
0dccf05ca8 | ||
|
|
ebae57eb96 | ||
|
|
30a7c2aa67 | ||
|
|
3a9b8fde9b | ||
|
|
4f864bc178 | ||
|
|
d76cbd755f | ||
|
|
4aece04ae7 | ||
|
|
f31dbf8d4e | ||
|
|
416f35dba9 | ||
|
|
c29370e061 | ||
|
|
4e0f6837d0 | ||
|
|
e45d212f02 | ||
|
|
8f62bd39b5 | ||
|
|
5e11afcdf1 | ||
|
|
f833222017 | ||
|
|
c7f39ebbde | ||
|
|
15f08ed006 | ||
|
|
b6fd4b5ef3 | ||
|
|
e2e59e94f5 | ||
|
|
5e4dae88f3 | ||
|
|
7312ddeda7 | ||
|
|
9a67b6f699 | ||
|
|
f59a0c349b | ||
|
|
4c8831f716 | ||
|
|
6fe388a705 | ||
|
|
172a8d9414 | ||
|
|
01ca442be7 | ||
|
|
01374ca2ab | ||
|
|
94e8b1d43c | ||
|
|
61d0f96c17 | ||
|
|
41729be80a | ||
|
|
6dbaa39016 | ||
|
|
58c95a92d1 | ||
|
|
4958180b02 | ||
|
|
d00dbf7e2d | ||
|
|
8312ff0cb5 | ||
|
|
17012f1fea | ||
|
|
324ac3972f | ||
|
|
fb9b9b276e | ||
|
|
323469cdb7 | ||
|
|
792a9a9149 | ||
|
|
0f655f9fb6 | ||
|
|
2d7acbd07f | ||
|
|
835030297a | ||
|
|
b06679cc14 | ||
|
|
2693a93aed | ||
|
|
8724589c6e | ||
|
|
4cc78d9478 | ||
|
|
424305ef38 | ||
|
|
9d01f6a45c | ||
|
|
7ed487b534 | ||
|
|
68d24288ce | ||
|
|
40f3925589 | ||
|
|
f92c3dba32 | ||
|
|
5abad7c0af | ||
|
|
bcbd4401b8 | ||
|
|
d6aca8e146 | ||
|
|
8f1911a4fe | ||
|
|
7f30cd3102 | ||
|
|
f01fe3e050 | ||
|
|
210c2ee6a4 | ||
|
|
497dfd1a84 | ||
|
|
d9f0e2a27f | ||
|
|
9a40ac6e2a | ||
|
|
1687d9c479 | ||
|
|
1a46ed8901 | ||
|
|
36d0352c01 | ||
|
|
15eacd787b | ||
|
|
5b3f9bdd7b | ||
|
|
e00dba94f4 | ||
|
|
ab0e4007a5 | ||
|
|
32266c54f9 | ||
|
|
dd2120cd41 | ||
|
|
8991df0785 | ||
|
|
ca31923a39 | ||
|
|
68bc00b6c6 | ||
|
|
5b55b8c6cf | ||
|
|
10f381bc95 | ||
|
|
40aa8fe5db | ||
|
|
5801474ba3 | ||
|
|
9dbd960631 | ||
|
|
544df89055 | ||
|
|
f0ad5f72b2 | ||
|
|
378944dd27 | ||
|
|
8b749642cd | ||
|
|
277b53a970 | ||
|
|
2febb4509a | ||
|
|
cbc252f3b7 | ||
|
|
21d2c88013 | ||
|
|
e7d33eb1a9 | ||
|
|
cab7a4558d | ||
|
|
242cf1a33d | ||
|
|
da225a0db8 | ||
|
|
3ef5a30102 | ||
|
|
418587bc25 | ||
|
|
01187c9260 | ||
|
|
35cf4810c1 | ||
|
|
404edd418b | ||
|
|
011d3c21eb | ||
|
|
3d46e07887 | ||
|
|
b1ac293a50 | ||
|
|
f4ebded63c | ||
|
|
51bc596502 | ||
|
|
95f1b48422 | ||
|
|
385b5a2f80 | ||
|
|
f7d8b155db | ||
|
|
fa6fe09647 | ||
|
|
539ffea390 | ||
|
|
c270eba678 | ||
|
|
2f0eb95c90 | ||
|
|
b13b2e8bab | ||
|
|
4bfc84f035 | ||
|
|
ca27cc3f72 | ||
|
|
53dc5b2ac3 | ||
|
|
f1219f1418 | ||
|
|
072066f746 | ||
|
|
5fde5801c1 | ||
|
|
18d96fcc02 | ||
|
|
4d2908a065 | ||
|
|
8d250f5921 | ||
|
|
59fd9b5ea7 | ||
|
|
8fcc70cfbe | ||
|
|
f1365b78d5 | ||
|
|
60cd524443 | ||
|
|
045d1f3bf2 | ||
|
|
1ae6f18fe9 | ||
|
|
91b9dd90f4 | ||
|
|
505d658e3d | ||
|
|
bee8decd18 | ||
|
|
6bce7e9cab | ||
|
|
4d8418fe6f | ||
|
|
850d57e791 | ||
|
|
c3dd71704b | ||
|
|
0b305e5bd3 | ||
|
|
061207861b | ||
|
|
3c91792dd8 | ||
|
|
431d0a3c5e | ||
|
|
1da002ca2f | ||
|
|
ef639d4de6 | ||
|
|
31f4dd671a | ||
|
|
60c4867ed3 | ||
|
|
0ee1716b26 | ||
|
|
a236a83fa8 | ||
|
|
37f1bd7606 | ||
|
|
af4cb282ba | ||
|
|
ce4914ba0e | ||
|
|
bdfd6c1677 | ||
|
|
8e0157d97d | ||
|
|
1767f4b8e7 | ||
|
|
ca95b0ee13 | ||
|
|
4aa09861dd | ||
|
|
24e9a7caaf | ||
|
|
d90df59118 | ||
|
|
f9103a7b41 | ||
|
|
9891477094 | ||
|
|
59e3a55110 |
@@ -7,26 +7,62 @@
|
||||
"versions": [
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "master",
|
||||
"branchName": "3.0.x",
|
||||
"slug": "latest",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.8",
|
||||
"branchName": "2.8.x",
|
||||
"slug": "2.8",
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
"slug": "2.14",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.7",
|
||||
"branchName": "2.7",
|
||||
"slug": "2.7",
|
||||
"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",
|
||||
"slug": "2.10",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.9",
|
||||
"branchName": "2.9.x",
|
||||
"slug": "2.9",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.8",
|
||||
"branchName": "2.8.x",
|
||||
"slug": "2.8",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.7",
|
||||
"branchName": "2.7",
|
||||
"slug": "2.7",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.6",
|
||||
"branchName": "2.6",
|
||||
|
||||
12
.gitattributes
vendored
12
.gitattributes
vendored
@@ -1,10 +1,11 @@
|
||||
/.github export-ignore
|
||||
/ci export-ignore
|
||||
/docs export-ignore
|
||||
/tests export-ignore
|
||||
/tools export-ignore
|
||||
/.github export-ignore
|
||||
.doctrine-project.json export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.gitmodules export-ignore
|
||||
.travis.yml export-ignore
|
||||
build.properties export-ignore
|
||||
build.properties.dev export-ignore
|
||||
build.xml export-ignore
|
||||
@@ -14,4 +15,9 @@ run-all.sh export-ignore
|
||||
phpcs.xml.dist export-ignore
|
||||
phpbench.json export-ignore
|
||||
phpstan.neon export-ignore
|
||||
phpstan-baseline.neon export-ignore
|
||||
phpstan-dbal2.neon export-ignore
|
||||
phpstan-params.neon export-ignore
|
||||
phpstan-persistence2.neon export-ignore
|
||||
psalm.xml export-ignore
|
||||
psalm-baseline.xml export-ignore
|
||||
|
||||
37
.github/ISSUE_TEMPLATE/BC_Break.md
vendored
Normal file
37
.github/ISSUE_TEMPLATE/BC_Break.md
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: 💥 BC Break
|
||||
about: Have you encountered an issue during upgrade? 💣
|
||||
---
|
||||
|
||||
<!--
|
||||
Before reporting a BC break, please consult the upgrading document to make sure it's not an expected change: https://github.com/doctrine/orm/blob/2.9.x/UPGRADE.md
|
||||
-->
|
||||
|
||||
### BC Break Report
|
||||
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
| Q | A
|
||||
|------------ | ------
|
||||
| BC Break | yes
|
||||
| Version | x.y.z
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary describing the problem you are experiencing. -->
|
||||
|
||||
#### Previous behavior
|
||||
|
||||
<!-- What was the previous (working) behavior? -->
|
||||
|
||||
#### Current behavior
|
||||
|
||||
<!-- What is the current (broken) behavior? -->
|
||||
|
||||
#### How to reproduce
|
||||
|
||||
<!--
|
||||
Provide steps to reproduce the BC break.
|
||||
If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc.
|
||||
Adding a failing Unit or Functional Test would help us a lot - you can submit it in a Pull Request separately, referencing this bug report.
|
||||
-->
|
||||
34
.github/ISSUE_TEMPLATE/Bug.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/Bug.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: 🐞 Bug Report
|
||||
about: Something is broken? 🔨
|
||||
---
|
||||
|
||||
### Bug Report
|
||||
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
| Q | A
|
||||
|------------ | ------
|
||||
| BC Break | yes/no
|
||||
| Version | x.y.z
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary describing the problem you are experiencing. -->
|
||||
|
||||
#### Current behavior
|
||||
|
||||
<!-- What is the current (buggy) behavior? -->
|
||||
|
||||
#### How to reproduce
|
||||
|
||||
<!--
|
||||
Provide steps to reproduce the bug.
|
||||
If possible, also add a code snippet with relevant configuration, entity mappings, DQL etc.
|
||||
Adding a failing Unit or Functional Test would help us a lot - you can submit one in a Pull Request separately, referencing this bug report.
|
||||
-->
|
||||
|
||||
#### Expected behavior
|
||||
|
||||
<!-- What was the expected (correct) behavior? -->
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/Feature_Request.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: 🎉 Feature Request
|
||||
about: You have a neat idea that should be implemented? 🎩
|
||||
---
|
||||
|
||||
### Feature Request
|
||||
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
| Q | A
|
||||
|------------ | ------
|
||||
| New Feature | yes
|
||||
| RFC | yes/no
|
||||
| BC Break | yes/no
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary of the feature you would like to see implemented. -->
|
||||
6
.github/ISSUE_TEMPLATE/Support_Question.md
vendored
Normal file
6
.github/ISSUE_TEMPLATE/Support_Question.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
name: ❓ Support Question
|
||||
about: Have a problem that you can't figure out? 🤔
|
||||
---
|
||||
|
||||
Please use https://github.com/doctrine/orm/discussions instead.
|
||||
19
.github/PULL_REQUEST_TEMPLATE/Failing_Test.md
vendored
Normal file
19
.github/PULL_REQUEST_TEMPLATE/Failing_Test.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: 🐞 Failing Test
|
||||
about: You found a bug and have a failing Unit or Functional test? 🔨
|
||||
---
|
||||
|
||||
### Failing Test
|
||||
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
| Q | A
|
||||
|------------ | ------
|
||||
| BC Break | yes/no
|
||||
| Version | x.y.z
|
||||
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary of the failing scenario. -->
|
||||
|
||||
18
.github/PULL_REQUEST_TEMPLATE/Improvement.md
vendored
Normal file
18
.github/PULL_REQUEST_TEMPLATE/Improvement.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: ⚙ Improvement
|
||||
about: You have some improvement to make Doctrine better? 🎁
|
||||
---
|
||||
|
||||
### Improvement
|
||||
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
| Q | A
|
||||
|------------ | ------
|
||||
| New Feature | yes
|
||||
| RFC | yes/no
|
||||
| BC Break | yes/no
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary of the improvement you are submitting. -->
|
||||
26
.github/PULL_REQUEST_TEMPLATE/New_Feature.md
vendored
Normal file
26
.github/PULL_REQUEST_TEMPLATE/New_Feature.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: 🎉 New Feature
|
||||
about: You have implemented some neat idea that you want to make part of Doctrine? 🎩
|
||||
---
|
||||
|
||||
<!--
|
||||
Thank you for submitting new feature!
|
||||
Pick the target branch based according to these criteria:
|
||||
* submitting a bugfix: target the lowest active stable branch: 2.9.x
|
||||
* submitting a new feature: target the next minor branch: 2.10.x
|
||||
* submitting a BC-breaking change: target the next major branch: 3.0.x
|
||||
-->
|
||||
|
||||
### New Feature
|
||||
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
| Q | A
|
||||
|------------ | ------
|
||||
| New Feature | yes
|
||||
| RFC | yes/no
|
||||
| BC Break | yes/no
|
||||
|
||||
#### Summary
|
||||
|
||||
<!-- Provide a summary of the feature you have implemented. -->
|
||||
111
.github/workflows/ci.yml
vendored
111
.github/workflows/ci.yml
vendored
@@ -1,111 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
static-analysis-phpstan:
|
||||
name: "Static Analysis with PHPStan"
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v2"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
tools: cs2pr
|
||||
|
||||
- name: "Cache dependencies installed with composer"
|
||||
uses: "actions/cache@v1"
|
||||
with:
|
||||
path: "~/.composer/cache"
|
||||
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
|
||||
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
|
||||
|
||||
- name: "Install dependencies with composer"
|
||||
run: "composer install --no-progress --no-suggest --no-interaction --prefer-dist"
|
||||
|
||||
- name: "Run a static analysis with phpstan/phpstan"
|
||||
run: "php vendor/bin/phpstan analyse --error-format=checkstyle | cs2pr"
|
||||
|
||||
static-analysis-psalm:
|
||||
name: "Static Analysis with Psalm"
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v2"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
|
||||
- name: "Cache dependencies installed with composer"
|
||||
uses: "actions/cache@v1"
|
||||
with:
|
||||
path: "~/.composer/cache"
|
||||
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
|
||||
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
|
||||
|
||||
- name: "Install dependencies with composer"
|
||||
run: "composer install --no-interaction --no-progress --no-suggest"
|
||||
|
||||
- name: "Run a static analysis with vimeo/psalm"
|
||||
run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)"
|
||||
|
||||
coding-standards:
|
||||
name: "Coding Standards"
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
tools: "cs2pr"
|
||||
|
||||
- name: "Cache dependencies installed with composer"
|
||||
uses: "actions/cache@v1"
|
||||
with:
|
||||
path: "~/.composer/cache"
|
||||
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
|
||||
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
|
||||
|
||||
- name: "Install dependencies with composer"
|
||||
run: "composer install --no-interaction --no-progress --no-suggest"
|
||||
|
||||
- name: "Install git-phpcs"
|
||||
run: "wget https://github.com/diff-sniffer/git/releases/download/0.3.2/git-phpcs.phar"
|
||||
|
||||
- name: "Fetch head branch"
|
||||
run: "git remote set-branches --add origin $GITHUB_BASE_REF && git fetch origin $GITHUB_BASE_REF"
|
||||
|
||||
- name: "Run git-phpcs"
|
||||
run: "php git-phpcs.phar origin/$GITHUB_BASE_REF...$GITHUB_SHA --report=checkstyle | cs2pr"
|
||||
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@3.0.0"
|
||||
384
.github/workflows/continuous-integration.yml
vendored
Normal file
384
.github/workflows/continuous-integration.yml
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
name: "Continuous Integration"
|
||||
|
||||
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
|
||||
|
||||
jobs:
|
||||
phpunit-smoke-check:
|
||||
name: "PHPUnit with SQLite"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.2"
|
||||
- "7.3"
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
extension:
|
||||
- "pdo_sqlite"
|
||||
proxy:
|
||||
- "common"
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
extension: "pdo_sqlite"
|
||||
- php-version: "8.2"
|
||||
dbal-version: "3@dev"
|
||||
extension: "pdo_sqlite"
|
||||
- php-version: "8.2"
|
||||
dbal-version: "default"
|
||||
extension: "sqlite3"
|
||||
- php-version: "8.1"
|
||||
dbal-version: "default"
|
||||
proxy: "lazy-ghost"
|
||||
extension: "pdo_sqlite"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
extensions: "apcu, pdo, ${{ matrix.extension }}"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Require specific DBAL version"
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Uninstall PHPBench"
|
||||
run: "composer remove --dev --no-update phpbench/phpbench"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
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"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
ORM_PROXY_IMPLEMENTATION: "${{ matrix.proxy }}"
|
||||
|
||||
- name: "Upload coverage file"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
phpunit-postgres:
|
||||
name: "PHPUnit with PostgreSQL"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
postgres-version:
|
||||
- "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:
|
||||
image: "postgres:${{ matrix.postgres-version }}"
|
||||
env:
|
||||
POSTGRES_PASSWORD: "postgres"
|
||||
|
||||
options: >-
|
||||
--health-cmd "pg_isready"
|
||||
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Require specific DBAL version"
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Uninstall PHPBench"
|
||||
run: "composer remove --dev --no-update phpbench/phpbench"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
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@v3"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
phpunit-mariadb:
|
||||
name: "PHPUnit with MariaDB"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
mariadb-version:
|
||||
- "10.9"
|
||||
extension:
|
||||
- "mysqli"
|
||||
- "pdo_mysql"
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
mariadb-version: "10.6"
|
||||
extension: "pdo_mysql"
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: "mariadb:${{ matrix.mariadb-version }}"
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
MYSQL_DATABASE: "doctrine_tests"
|
||||
|
||||
options: >-
|
||||
--health-cmd "mysqladmin ping --silent"
|
||||
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Require specific DBAL version"
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Uninstall PHPBench"
|
||||
run: "composer remove --dev --no-update phpbench/phpbench"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
extensions: "${{ matrix.extension }}"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
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@v3"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
phpunit-mysql:
|
||||
name: "PHPUnit with MySQL"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
mysql-version:
|
||||
- "5.7"
|
||||
- "8.0"
|
||||
extension:
|
||||
- "mysqli"
|
||||
- "pdo_mysql"
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
mysql-version: "8.0"
|
||||
extension: "pdo_mysql"
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: "mysql:${{ matrix.mysql-version }}"
|
||||
|
||||
options: >-
|
||||
--health-cmd "mysqladmin ping --silent"
|
||||
-e MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
-e MYSQL_DATABASE=doctrine_tests
|
||||
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
extensions: "${{ matrix.extension }}"
|
||||
|
||||
- name: "Require specific DBAL version"
|
||||
run: "composer require doctrine/dbal ^${{ matrix.dbal-version }} --no-update"
|
||||
if: "${{ matrix.dbal-version != 'default' }}"
|
||||
|
||||
- name: "Uninstall PHPBench"
|
||||
run: "composer remove --dev --no-update phpbench/phpbench"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
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"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 0
|
||||
|
||||
- name: "Run PHPUnit with Second Level Cache"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/${{ matrix.extension }}.xml --exclude-group performance,non-cacheable,locking_functional --coverage-clover=coverage-no-cache.xml"
|
||||
env:
|
||||
ENABLE_SECOND_LEVEL_CACHE: 1
|
||||
|
||||
- name: "Upload coverage files"
|
||||
uses: "actions/upload-artifact@v3"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
phpunit-lower-php-versions:
|
||||
name: "PHPUnit with SQLite"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.1"
|
||||
deps:
|
||||
- "highest"
|
||||
- "lowest"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
dependency-versions: "${{ matrix.deps }}"
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: "vendor/bin/phpunit -c ci/github/phpunit/pdo_sqlite.xml"
|
||||
|
||||
|
||||
upload_coverage:
|
||||
name: "Upload coverage to Codecov"
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs:
|
||||
- "phpunit-smoke-check"
|
||||
- "phpunit-postgres"
|
||||
- "phpunit-mariadb"
|
||||
- "phpunit-mysql"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v3"
|
||||
with:
|
||||
path: "reports"
|
||||
|
||||
- name: "Upload to Codecov"
|
||||
uses: "codecov/codecov-action@v3"
|
||||
with:
|
||||
directory: reports
|
||||
61
.github/workflows/phpbench.yml
vendored
Normal file
61
.github/workflows/phpbench.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
name: "Performance benchmark"
|
||||
|
||||
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
|
||||
|
||||
jobs:
|
||||
phpbench:
|
||||
name: "PHPBench"
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.4"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v3"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
coverage: "pcov"
|
||||
ini-values: "zend.assertions=1, apc.enable_cli=1"
|
||||
|
||||
- name: "Cache dependencies installed with composer"
|
||||
uses: "actions/cache@v3"
|
||||
with:
|
||||
path: "~/.composer/cache"
|
||||
key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}"
|
||||
restore-keys: "php-${{ matrix.php-version }}-composer-locked-"
|
||||
|
||||
- name: "Install dependencies with composer"
|
||||
run: "composer update --no-interaction --no-progress"
|
||||
|
||||
- name: "Run PHPBench"
|
||||
run: "vendor/bin/phpbench run --report=default"
|
||||
@@ -7,49 +7,9 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: "Git tag, release & create merge-up PR"
|
||||
runs-on: "ubuntu-20.04"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v2"
|
||||
|
||||
- name: "Release"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:release"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
|
||||
- name: "Create Merge-Up Pull Request"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:create-merge-up-pull-request"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
|
||||
- name: "Create and/or Switch to new Release Branch"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:switch-default-branch-to-next-minor"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
|
||||
- name: "Create new milestones"
|
||||
uses: "laminas/automatic-releases@v1"
|
||||
with:
|
||||
command-name: "laminas:automatic-releases:create-milestones"
|
||||
env:
|
||||
"GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }}
|
||||
"SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
"GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
"GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@3.0.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
|
||||
SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
|
||||
109
.github/workflows/static-analysis.yml
vendored
Normal file
109
.github/workflows/static-analysis.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: "Static Analysis"
|
||||
|
||||
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-22.04"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
dbal-version:
|
||||
- "default"
|
||||
persistence-version:
|
||||
- "default"
|
||||
include:
|
||||
- dbal-version: "2.13"
|
||||
persistence-version: "default"
|
||||
- dbal-version: "default"
|
||||
persistence-version: "2.5"
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v3"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
|
||||
- name: "Require specific DBAL version"
|
||||
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 }} = default ] && echo '3.1' || echo ${{ matrix.persistence-version }}) --no-update"
|
||||
|
||||
- name: "Uninstall PHPBench"
|
||||
run: "composer remove --dev --no-update phpbench/phpbench"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
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' && 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-22.04"
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: "actions/checkout@v3"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.2"
|
||||
|
||||
- name: "Require specific persistence version"
|
||||
run: "composer require doctrine/persistence ^3.1 --no-update"
|
||||
|
||||
- name: "Uninstall PHPBench"
|
||||
run: "composer remove --dev --no-update phpbench/phpbench"
|
||||
|
||||
- name: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v2"
|
||||
with:
|
||||
dependency-versions: "highest"
|
||||
|
||||
- name: "Run a static analysis with vimeo/psalm"
|
||||
run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,3 +14,6 @@ lib/Doctrine/DBAL
|
||||
vendor/
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
composer.lock
|
||||
.phpunit.result.cache
|
||||
/*.phpunit.xml
|
||||
|
||||
106
.travis.yml
106
.travis.yml
@@ -1,106 +0,0 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- 7.4
|
||||
|
||||
env:
|
||||
- DB=sqlite
|
||||
- DB=mysql
|
||||
- DB=pgsql
|
||||
|
||||
before_install:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
||||
- composer self-update
|
||||
|
||||
install: travis_retry composer validate --strict && composer update --prefer-dist
|
||||
|
||||
script:
|
||||
- if [[ "$DB" == "mysql" || "$DB" == "mariadb" ]]; then mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'"; fi
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml
|
||||
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Test
|
||||
env: DB=mariadb
|
||||
addons:
|
||||
mariadb: 10.1
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.1
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.2
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
dist: xenial
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.4
|
||||
services:
|
||||
- mysql
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite DEPENDENCIES=low
|
||||
install: travis_retry composer update --prefer-dist --prefer-lowest
|
||||
|
||||
- stage: Test
|
||||
if: type = cron
|
||||
php: 7.3
|
||||
env: DB=sqlite DEV_DEPENDENCIES
|
||||
install:
|
||||
- composer config minimum-stability dev
|
||||
- travis_retry composer update --prefer-dist
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite COVERAGE
|
||||
before_script:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
|
||||
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
|
||||
script:
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --coverage-clover ./build/logs/clover.xml
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash) -f ./build/logs/clover.xml
|
||||
|
||||
- stage: Code Quality
|
||||
env: DB=none BENCHMARK
|
||||
php: 7.4
|
||||
before_script: wget https://phpbench.github.io/phpbench/phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar.pubkey
|
||||
script: php phpbench.phar run -l dots --report=default
|
||||
|
||||
- stage: Code Quality
|
||||
if: NOT type = pull_request
|
||||
env: DB=none CODING_STANDARDS
|
||||
php: 7.4
|
||||
install: travis_retry composer install --prefer-dist
|
||||
script:
|
||||
- ./vendor/bin/phpcs
|
||||
|
||||
allow_failures:
|
||||
- stage: Code Quality
|
||||
env: DB=none CODING_STANDARDS
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
@@ -6,30 +6,17 @@ Before we can merge your Pull-Request here are some guidelines that you need to
|
||||
These guidelines exist not to annoy you, but to keep the code base clean,
|
||||
unified and future proof.
|
||||
|
||||
## We only accept PRs to "master"
|
||||
Doctrine has [general contributing guidelines][contributor workflow], make
|
||||
sure you follow them.
|
||||
|
||||
Our branching strategy is "everything to master first", even
|
||||
bugfixes and we then merge them into the stable branches. You should only
|
||||
open pull requests against the master branch. Otherwise we cannot accept the PR.
|
||||
|
||||
There is one exception to the rule, when we merged a bug into some stable branches
|
||||
we do occasionally accept pull requests that merge the same bug fix into earlier
|
||||
branches.
|
||||
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html
|
||||
|
||||
## Coding Standard
|
||||
|
||||
We use PSR-1 and PSR-2:
|
||||
This project follows [`doctrine/coding-standard`][coding standard homepage].
|
||||
You may fix many some of the issues with `vendor/bin/phpcbf`.
|
||||
|
||||
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
|
||||
* https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
|
||||
|
||||
with some exceptions/differences:
|
||||
|
||||
* Keep the nesting of control structures per method as small as possible
|
||||
* Align equals (=) signs
|
||||
* Add spaces between assignment, control and return statements
|
||||
* Prefer early exit over nesting conditions
|
||||
* Add spaces around a negation if condition ``if ( ! $cond)``
|
||||
[coding standard homepage]: https://github.com/doctrine/coding-standard
|
||||
|
||||
## Unit-Tests
|
||||
|
||||
@@ -50,31 +37,24 @@ 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``
|
||||
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
|
||||
take a look at the ``tests/travis`` folder for some examples. Then run:
|
||||
take a look at the ``ci/github/phpunit`` directory for some examples. Then run:
|
||||
|
||||
vendor/bin/phpunit -c mysql.phpunit.xml
|
||||
|
||||
|
||||
If you do not provide these parameters, the test suite will use an in-memory
|
||||
sqlite database.
|
||||
|
||||
Tips for creating unit tests:
|
||||
|
||||
1. If you put a test into the `Ticket` namespace as described above, put the testcase and all entities into the same class.
|
||||
See `https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
See `https://github.com/doctrine/orm/tree/2.8.x/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
example.
|
||||
|
||||
## Travis
|
||||
|
||||
We automatically run your pull request through [Travis CI](http://www.travis-ci.org)
|
||||
against SQLite, MySQL and PostgreSQL. If you break the tests, we cannot merge your code,
|
||||
so please make sure that your code is working before opening up a Pull-Request.
|
||||
|
||||
## Getting merged
|
||||
|
||||
Please allow us time to review your pull requests. We will give our best to review
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2006-2015 Doctrine Project
|
||||
Copyright (c) Doctrine Project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
34
README.md
34
README.md
@@ -1,9 +1,11 @@
|
||||
| [Master][Master] | [2.7][2.7] |
|
||||
|:----------------:|:----------:|
|
||||
| [![Build status][Master image]][Master] | [![Build status][2.7 image]][2.7] |
|
||||
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.7 coverage image]][2.7 coverage] |
|
||||
| [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.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,14 +15,18 @@ without requiring unnecessary code duplication.
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org)
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
|
||||
|
||||
|
||||
[Master image]: https://img.shields.io/travis/doctrine/orm/master.svg?style=flat-square
|
||||
[Master]: https://travis-ci.org/doctrine/orm
|
||||
[Master coverage image]: https://codecov.io/gh/doctrine/orm/branch/master/graph/badge.svg
|
||||
[Master coverage]: https://codecov.io/gh/doctrine/orm/branch/master
|
||||
[2.7 image]: https://img.shields.io/travis/doctrine/orm/2.7.svg?style=flat-square
|
||||
[2.7]: https://github.com/doctrine/orm/tree/2.7
|
||||
[2.7 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.7/graph/badge.svg
|
||||
[2.7 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.7
|
||||
[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.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://github.com/doctrine/dbal/blob/master/docs/en/reference/security.rst)
|
||||
- [ORM Security Page](https://github.com/doctrine/orm/blob/master/docs/en/reference/security.rst)
|
||||
- [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
|
||||
|
||||
601
UPGRADE.md
601
UPGRADE.md
@@ -1,11 +1,571 @@
|
||||
# Upgrade to 2.14
|
||||
|
||||
## Deprecated constants of `Doctrine\ORM\Internal\CommitOrderCalculator`
|
||||
|
||||
The following public constants have been deprecated:
|
||||
|
||||
* `CommitOrderCalculator::NOT_VISITED`
|
||||
* `CommitOrderCalculator::IN_PROGRESS`
|
||||
* `CommitOrderCalculator::VISITED`
|
||||
|
||||
These constants were used for internal purposes. Relying on them is discouraged.
|
||||
|
||||
## Deprecated `Doctrine\ORM\Query\AST\InExpression`
|
||||
|
||||
The AST parser will create a `InListExpression` or a `InSubselectExpression` when
|
||||
encountering an `IN ()` DQL expression instead of a generic `InExpression`.
|
||||
|
||||
As a consequence, `SqlWalker::walkInExpression()` has been deprecated in favor of
|
||||
`SqlWalker::walkInListExpression()` and `SqlWalker::walkInSubselectExpression()`.
|
||||
|
||||
## Deprecated constructing a `CacheKey` without `$hash`
|
||||
|
||||
The `Doctrine\ORM\Cache\CacheKey` class has an explicit constructor now with
|
||||
an optional parameter `$hash`. That parameter will become mandatory in 3.0.
|
||||
|
||||
## Deprecated `AttributeDriver::$entityAnnotationClasses`
|
||||
|
||||
If you need to change the behavior of `AttributeDriver::isTransient()`,
|
||||
override that method instead.
|
||||
|
||||
## Deprecated incomplete schema updates
|
||||
|
||||
Using `orm:schema-tool:update` without passing the `--complete` flag is
|
||||
deprecated. Use schema asset filtering if you need to preserve assets not
|
||||
managed by DBAL.
|
||||
|
||||
Likewise, calling `SchemaTool::updateSchema()` or
|
||||
`SchemaTool::getUpdateSchemaSql()` with a second argument is deprecated.
|
||||
|
||||
## Deprecated annotation mapping driver.
|
||||
|
||||
Please switch to one of the other mapping drivers. Native attributes which PHP
|
||||
supports since version 8.0 are probably your best option.
|
||||
|
||||
As a consequence, the following methods are deprecated:
|
||||
- `ORMSetup::createAnnotationMetadataConfiguration`
|
||||
- `ORMSetup::createDefaultAnnotationDriver`
|
||||
|
||||
The marker interface `Doctrine\ORM\Mapping\Annotation` is deprecated as well.
|
||||
All annotation/attribute classes implement
|
||||
`Doctrine\ORM\Mapping\MappingAttribute` now.
|
||||
|
||||
## Deprecated `Doctrine\ORM\Proxy\Proxy` interface.
|
||||
|
||||
Use `Doctrine\Persistence\Proxy` instead to check whether proxies are initialized.
|
||||
|
||||
## Deprecated `Doctrine\ORM\Event\LifecycleEventArgs` class.
|
||||
|
||||
It will be removed in 3.0. Use one of the dedicated event classes instead:
|
||||
|
||||
* `Doctrine\ORM\Event\PrePersistEventArgs`
|
||||
* `Doctrine\ORM\Event\PreUpdateEventArgs`
|
||||
* `Doctrine\ORM\Event\PreRemoveEventArgs`
|
||||
* `Doctrine\ORM\Event\PostPersistEventArgs`
|
||||
* `Doctrine\ORM\Event\PostUpdateEventArgs`
|
||||
* `Doctrine\ORM\Event\PostRemoveEventArgs`
|
||||
* `Doctrine\ORM\Event\PostLoadEventArgs`
|
||||
|
||||
# Upgrade to 2.13
|
||||
|
||||
## Deprecated `EntityManager::create()`
|
||||
|
||||
The constructor of `EntityManager` is now public and should be used instead of the `create()` method.
|
||||
However, the constructor expects a `Connection` while `create()` accepted an array with connection parameters.
|
||||
You can pass that array to DBAL's `Doctrine\DBAL\DriverManager::getConnection()` method to bootstrap the
|
||||
connection.
|
||||
|
||||
## 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::addNamedNativeQueryResultSetMapping()`
|
||||
- `Doctrine\ORM\Query\ResultSetMappingBuilder::addNamedNativeQueryEntityResultMapping()`
|
||||
|
||||
## 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()`
|
||||
|
||||
Implementations of `AbstractIdGenerator` have to override the method
|
||||
`generateId()` without calling the parent implementation. Not doing so is
|
||||
deprecated. Calling `generate()` on any `AbstractIdGenerator` implementation
|
||||
is deprecated.
|
||||
|
||||
## PSR-6-based second level cache
|
||||
|
||||
The second level cache has been reworked to consume a PSR-6 cache. Using a
|
||||
Doctrine Cache instance is deprecated.
|
||||
|
||||
* `DefaultCacheFactory`: The constructor expects a PSR-6 cache item pool as
|
||||
second argument now.
|
||||
* `DefaultMultiGetRegion`: This class is deprecated in favor of `DefaultRegion`.
|
||||
* `DefaultRegion`:
|
||||
* The constructor expects a PSR-6 cache item pool as second argument now.
|
||||
* The protected `$cache` property is deprecated.
|
||||
* The properties `$name` and `$lifetime` as well as the constant
|
||||
`REGION_KEY_SEPARATOR` and the method `getCacheEntryKey()` are flagged as
|
||||
`@internal` now. They all will become `private` in 3.0.
|
||||
* The method `getCache()` is deprecated without replacement.
|
||||
|
||||
## Deprecated: `Doctrine\ORM\Mapping\Driver\PHPDriver`
|
||||
|
||||
Use `StaticPHPDriver` instead when you want to programmatically configure
|
||||
entity metadata.
|
||||
|
||||
You can convert mappings with the `orm:convert-mapping` command or more simply
|
||||
in this case, `include` the metadata file from the `loadMetadata` static method
|
||||
used by the `StaticPHPDriver`.
|
||||
|
||||
## Deprecated: `Setup::registerAutoloadDirectory()`
|
||||
|
||||
Use Composer's autoloader instead.
|
||||
|
||||
## Deprecated: `AbstractHydrator::hydrateRow()`
|
||||
|
||||
Following the deprecation of the method `AbstractHydrator::iterate()`, the
|
||||
method `hydrateRow()` has been deprecated as well.
|
||||
|
||||
## Deprecate cache settings inspection
|
||||
|
||||
Doctrine does not provide its own cache implementation anymore and relies on
|
||||
the PSR-6 standard instead. As a consequence, we cannot determine anymore
|
||||
whether a given cache adapter is suitable for a production environment.
|
||||
Because of that, functionality that aims to do so has been deprecated:
|
||||
|
||||
* `Configuration::ensureProductionSettings()`
|
||||
* the `orm:ensure-production-settings` console command
|
||||
|
||||
# Upgrade to 2.10
|
||||
|
||||
## BC Break: `UnitOfWork` now relies on SPL object IDs, not hashes
|
||||
|
||||
When calling the following methods, you are now supposed to use the result of
|
||||
`spl_object_id()`, and not `spl_object_hash()`:
|
||||
- `UnitOfWork::clearEntityChangeSet()`
|
||||
- `UnitOfWork::setOriginalEntityProperty()`
|
||||
|
||||
## BC Break: Removed `TABLE` id generator strategy
|
||||
|
||||
The implementation was unfinished for 14 years.
|
||||
It is now deprecated to rely on:
|
||||
- `Doctrine\ORM\Id\TableGenerator`;
|
||||
- `Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_TABLE`;
|
||||
- `Doctrine\ORM\Mapping\ClassMetadata::$tableGeneratorDefinition`;
|
||||
- or `Doctrine\ORM\Mapping\ClassMetadata::isIdGeneratorTable()`.
|
||||
|
||||
## New method `Doctrine\ORM\EntityManagerInterface#wrapInTransaction($func)`
|
||||
|
||||
Works the same as `Doctrine\ORM\EntityManagerInterface#transactional()` but returns any value returned from `$func` closure rather than just _non-empty value returned from the closure or true_.
|
||||
|
||||
Because of BC policy, the method does not exist on the interface yet. This is the example of safe usage:
|
||||
|
||||
```php
|
||||
function foo(EntityManagerInterface $entityManager, callable $func) {
|
||||
if (method_exists($entityManager, 'wrapInTransaction')) {
|
||||
return $entityManager->wrapInTransaction($func);
|
||||
}
|
||||
|
||||
return $entityManager->transactional($func);
|
||||
}
|
||||
```
|
||||
|
||||
`Doctrine\ORM\EntityManagerInterface#transactional()` has been deprecated.
|
||||
|
||||
## Minor BC BREAK: some exception methods have been removed
|
||||
|
||||
The following methods were not in use and are very unlikely to be used by
|
||||
downstream packages or applications, and were consequently removed:
|
||||
|
||||
- `ORMException::entityMissingForeignAssignedId`
|
||||
- `ORMException::entityMissingAssignedIdForField`
|
||||
- `ORMException::invalidFlushMode`
|
||||
|
||||
## Deprecated: database-side UUID generation
|
||||
|
||||
[DB-generated UUIDs are deprecated as of `doctrine/dbal` 2.8][DBAL deprecation].
|
||||
As a consequence, using the `UUID` strategy for generating identifiers is deprecated as well.
|
||||
Furthermore, relying on the following classes and methods is deprecated:
|
||||
|
||||
- `Doctrine\ORM\Id\UuidGenerator`
|
||||
- `Doctrine\ORM\Mapping\ClassMetadataInfo::isIdentifierUuid()`
|
||||
|
||||
[DBAL deprecation]: https://github.com/doctrine/dbal/pull/3212
|
||||
|
||||
## Minor BC BREAK: Custom hydrators and `toIterable()`
|
||||
|
||||
The type declaration of the `$stmt` parameter of `AbstractHydrator::toIterable()` has been removed. This change might
|
||||
break custom hydrator implementations that override this very method.
|
||||
|
||||
Overriding this method is not recommended, which is why the method is documented as `@final` now.
|
||||
|
||||
```diff
|
||||
- public function toIterable(ResultStatement $stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable
|
||||
+ public function toIterable($stmt, ResultSetMapping $resultSetMapping, array $hints = []): iterable
|
||||
```
|
||||
|
||||
## Deprecated: Entity Namespace Aliases
|
||||
|
||||
Entity namespace aliases are deprecated, use the magic ::class constant to abbreviate full class names
|
||||
in EntityManager, EntityRepository and DQL.
|
||||
|
||||
```diff
|
||||
- $entityManager->find('MyBundle:User', $id);
|
||||
+ $entityManager->find(User::class, $id);
|
||||
```
|
||||
|
||||
# Upgrade to 2.9
|
||||
|
||||
## Minor BC BREAK: Setup tool needs cache implementation
|
||||
|
||||
With the deprecation of doctrine/cache, the setup tool might no longer work as expected without a different cache
|
||||
implementation. To work around this:
|
||||
* Install symfony/cache: `composer require symfony/cache`. This will keep previous behaviour without any changes
|
||||
* Instantiate caches yourself: to use a different cache implementation, pass a cache instance when calling any
|
||||
configuration factory in the setup tool:
|
||||
```diff
|
||||
- $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir);
|
||||
+ $cache = \Doctrine\Common\Cache\Psr6\DoctrineProvider::wrap($anyPsr6Implementation);
|
||||
+ $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir, $cache);
|
||||
```
|
||||
* As a quick workaround, you can lock the doctrine/cache dependency to work around this: `composer require doctrine/cache ^1.11`.
|
||||
Note that this is only recommended as a bandaid fix, as future versions of ORM will no longer work with doctrine/cache
|
||||
1.11.
|
||||
|
||||
## Deprecated: doctrine/cache for metadata caching
|
||||
|
||||
The `Doctrine\ORM\Configuration#setMetadataCacheImpl()` method is deprecated and should no longer be used. Please use
|
||||
`Doctrine\ORM\Configuration#setMetadataCache()` with any PSR-6 cache adapter instead.
|
||||
|
||||
## Removed: flushing metadata cache
|
||||
|
||||
To support PSR-6 caches, the `--flush` option for the `orm:clear-cache:metadata` command is ignored. Metadata cache is
|
||||
now always cleared regardless of the cache adapter being used.
|
||||
|
||||
# Upgrade to 2.8
|
||||
|
||||
## Minor BC BREAK: Failed commit now throw OptimisticLockException
|
||||
|
||||
Method `Doctrine\ORM\UnitOfWork#commit()` can throw an OptimisticLockException when a commit silently fails and returns false
|
||||
since `Doctrine\DBAL\Connection#commit()` signature changed from returning void to boolean
|
||||
|
||||
## Deprecated: `Doctrine\ORM\AbstractQuery#iterate()`
|
||||
|
||||
The method `Doctrine\ORM\AbstractQuery#iterate()` is deprecated in favor of `Doctrine\ORM\AbstractQuery#toIterable()`.
|
||||
Note that `toIterable()` yields results of the query, unlike `iterate()` which yielded each result wrapped into an array.
|
||||
|
||||
# Upgrade to 2.7
|
||||
|
||||
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
|
||||
## Added `Doctrine\ORM\AbstractQuery#enableResultCache()` and `Doctrine\ORM\AbstractQuery#disableResultCache()` methods
|
||||
|
||||
Method `Doctrine\ORM\AbstractQuery#useResultCache()` which could be used for both enabling and disabling the cache
|
||||
(depending on passed flag) was split into two.
|
||||
(depending on passed flag) was split into two.
|
||||
|
||||
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
|
||||
## Minor BC BREAK: paginator output walkers aren't be called anymore on sub-queries for queries without max results
|
||||
|
||||
To optimize DB interaction, `Doctrine\ORM\Tools\Pagination\Paginator` no longer fetches identifiers to be able to
|
||||
perform the pagination with join collections when max results isn't set in the query.
|
||||
@@ -24,13 +584,13 @@ In the last patch of the `v2.6.x` series, we fixed a bug that was not converting
|
||||
In order to not break BC we've introduced a way to enable the fixed behavior using a boolean constructor argument. This
|
||||
argument will be removed in 3.0 and the default behavior will be the fixed one.
|
||||
|
||||
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
|
||||
## Deprecated: `Doctrine\ORM\AbstractQuery#useResultCache()`
|
||||
|
||||
Method `Doctrine\ORM\AbstractQuery#useResultCache()` is deprecated because it is split into `enableResultCache()`
|
||||
and `disableResultCache()`. It will be removed in 3.0.
|
||||
|
||||
## Deprecated code generators and related console commands
|
||||
|
||||
|
||||
These console commands have been deprecated:
|
||||
|
||||
* `orm:convert-mapping`
|
||||
@@ -54,7 +614,7 @@ These related classes have been deprecated:
|
||||
|
||||
* `Doctrine\ORM\Proxy\ProxyFactory`
|
||||
* `Doctrine\ORM\Proxy\Autoloader` - we suggest using the composer autoloader instead
|
||||
|
||||
|
||||
These methods have been deprecated:
|
||||
|
||||
* `Doctrine\ORM\Configuration#getAutoGenerateProxyClasses()`
|
||||
@@ -64,27 +624,18 @@ These methods have been deprecated:
|
||||
## Deprecated `Doctrine\ORM\Version`
|
||||
|
||||
The `Doctrine\ORM\Version` class is now deprecated and will be removed in Doctrine ORM 3.0:
|
||||
please refrain from checking the ORM version at runtime or use
|
||||
[ocramius/package-versions](https://github.com/Ocramius/PackageVersions/).
|
||||
please refrain from checking the ORM version at runtime or use Composer's [runtime API](https://getcomposer.org/doc/07-runtime.md#knowing-whether-package-x-is-installed-in-version-y).
|
||||
|
||||
## Deprecated `EntityManager#merge()` and `EntityManager#detach()` methods
|
||||
## Deprecated `EntityManager#merge()` method
|
||||
|
||||
Merge and detach semantics were a poor fit for the PHP "share-nothing" architecture.
|
||||
In addition to that, merging/detaching caused multiple issues with data integrity
|
||||
Merge semantics was a poor fit for the PHP "share-nothing" architecture.
|
||||
In addition to that, merging caused multiple issues with data integrity
|
||||
in the managed entity graph, which was constantly spawning more edge-case bugs/scenarios.
|
||||
|
||||
The following API methods were therefore deprecated:
|
||||
|
||||
* `EntityManager#merge()`
|
||||
* `EntityManager#detach()`
|
||||
* `UnitOfWork#merge()`
|
||||
* `UnitOfWork#detach()`
|
||||
|
||||
Users are encouraged to migrate `EntityManager#detach()` calls to `EntityManager#clear()`.
|
||||
|
||||
In order to maintain performance on batch processing jobs, it is endorsed to enable
|
||||
the second level cache (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/second-level-cache.html)
|
||||
on entities that are frequently reused across multiple `EntityManager#clear()` calls.
|
||||
|
||||
An alternative to `EntityManager#merge()` will not be provided by ORM 3.0, since the merging
|
||||
semantics should be part of the business domain rather than the persistence domain of an
|
||||
@@ -112,7 +663,7 @@ If your code relies on single entity flushing optimisations via
|
||||
|
||||
Said API was affected by multiple data integrity bugs due to the fact
|
||||
that change tracking was being restricted upon a subset of the managed
|
||||
entities. The ORM cannot support committing subsets of the managed
|
||||
entities. The ORM cannot support committing subsets of the managed
|
||||
entities while also guaranteeing data integrity, therefore this
|
||||
utility was removed.
|
||||
|
||||
@@ -127,8 +678,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.
|
||||
|
||||
@@ -213,8 +764,8 @@ either:
|
||||
- map those classes as `MappedSuperclass`
|
||||
|
||||
## Minor BC BREAK: ``EntityManagerInterface`` instead of ``EntityManager`` in type-hints
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
|
||||
As of 2.5, classes requiring the ``EntityManager`` in any method signature will now require
|
||||
an ``EntityManagerInterface`` instead.
|
||||
If you are extending any of the following classes, then you need to check following
|
||||
signatures:
|
||||
@@ -307,7 +858,7 @@ the `Doctrine\ORM\Repository\DefaultRepositoryFactory`.
|
||||
When executing DQL queries with new object expressions, instead of returning DTOs numerically indexes, it will now respect user provided aliases. Consider the following query:
|
||||
|
||||
SELECT new UserDTO(u.id,u.name) as user,new AddressDTO(a.street,a.postalCode) as address, a.id as addressId FROM User u INNER JOIN u.addresses a WITH a.isPrimary = true
|
||||
|
||||
|
||||
Previously, your result would be similar to this:
|
||||
|
||||
array(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
include('doctrine.php');
|
||||
include(__DIR__ . '/doctrine.php');
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the LGPL. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@echo off
|
||||
|
||||
if "%PHPBIN%" == "" set PHPBIN=@php_bin@
|
||||
if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
|
||||
GOTO RUN
|
||||
:USE_PEAR_PATH
|
||||
set PHPBIN=%PHP_PEAR_PHP_BIN%
|
||||
:RUN
|
||||
"%PHPBIN%" "@bin_dir@\doctrine" %*
|
||||
@echo off
|
||||
|
||||
if "%PHPBIN%" == "" set PHPBIN=@php_bin@
|
||||
if not exist "%PHPBIN%" if "%PHP_PEAR_PHP_BIN%" neq "" goto USE_PEAR_PATH
|
||||
GOTO RUN
|
||||
:USE_PEAR_PATH
|
||||
set PHPBIN=%PHP_PEAR_PHP_BIN%
|
||||
:RUN
|
||||
"%PHPBIN%" "@bin_dir@\doctrine" %*
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
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'
|
||||
|
||||
40
ci/github/phpunit/mysqli.xml
Normal file
40
ci/github/phpunit/mysqli.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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" />
|
||||
<var name="db_driver" value="mysqli"/>
|
||||
<var name="db_host" value="127.0.0.1" />
|
||||
<var name="db_port" value="3306"/>
|
||||
<var name="db_user" value="root" />
|
||||
<var name="db_dbname" value="doctrine_tests" />
|
||||
|
||||
<!-- 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>
|
||||
41
ci/github/phpunit/pdo_mysql.xml
Normal file
41
ci/github/phpunit/pdo_mysql.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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" />
|
||||
<var name="db_driver" value="pdo_mysql"/>
|
||||
<var name="db_host" value="127.0.0.1" />
|
||||
<var name="db_port" value="3306"/>
|
||||
<var name="db_user" value="root" />
|
||||
<var name="db_dbname" value="doctrine_tests" />
|
||||
|
||||
<!-- 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>
|
||||
40
ci/github/phpunit/pdo_pgsql.xml
Normal file
40
ci/github/phpunit/pdo_pgsql.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?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" />
|
||||
<var name="db_driver" value="pdo_pgsql"/>
|
||||
<var name="db_host" value="localhost" />
|
||||
<var name="db_user" value="postgres" />
|
||||
<var name="db_password" value="postgres" />
|
||||
<var name="db_dbname" value="doctrine_tests" />
|
||||
|
||||
<!-- 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>
|
||||
38
ci/github/phpunit/pdo_sqlite.xml
Normal file
38
ci/github/phpunit/pdo_sqlite.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="pdo_sqlite"/>
|
||||
<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>
|
||||
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>
|
||||
@@ -13,35 +13,50 @@
|
||||
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
|
||||
],
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.1.3"
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1",
|
||||
"ext-pdo": "*",
|
||||
"composer/package-versions-deprecated": "^1.8",
|
||||
"doctrine/annotations": "^1.8",
|
||||
"doctrine/cache": "^1.9.1",
|
||||
"doctrine/collections": "^1.5",
|
||||
"doctrine/common": "^2.11 || ^3.0",
|
||||
"doctrine/dbal": "^2.9.3",
|
||||
"doctrine/event-manager": "^1.1",
|
||||
"doctrine/inflector": "^1.0",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"composer-runtime-api": "^2",
|
||||
"ext-ctype": "*",
|
||||
"doctrine/cache": "^1.12.1 || ^2.1.1",
|
||||
"doctrine/collections": "^1.5 || ^2.0",
|
||||
"doctrine/common": "^3.0.3",
|
||||
"doctrine/dbal": "^2.13.1 || ^3.2",
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"doctrine/event-manager": "^1.2 || ^2",
|
||||
"doctrine/inflector": "^1.4 || ^2.0",
|
||||
"doctrine/instantiator": "^1.3",
|
||||
"doctrine/lexer": "^1.0",
|
||||
"doctrine/persistence": "^1.3.3 || ^2.0",
|
||||
"symfony/console": "^3.0|^4.0|^5.0"
|
||||
"doctrine/lexer": "^1.2.3 || ^2",
|
||||
"doctrine/persistence": "^2.4 || ^3",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^4.2 || ^5.0 || ^6.0",
|
||||
"symfony/polyfill-php72": "^1.23",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^5.0",
|
||||
"phpstan/phpstan": "^0.12.18",
|
||||
"phpunit/phpunit": "^7.5",
|
||||
"symfony/yaml": "^3.4|^4.0|^5.0",
|
||||
"vimeo/psalm": "^3.11"
|
||||
"doctrine/annotations": "^1.13 || ^2",
|
||||
"doctrine/coding-standard": "^9.0.2 || ^11.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/phpstan": "~1.4.10 || 1.9.4",
|
||||
"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/var-exporter": "^4.4 || ^5.4 || ^6.2",
|
||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0",
|
||||
"vimeo/psalm": "4.30.0 || 5.3.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >= 3.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"
|
||||
},
|
||||
"autoload": {
|
||||
@@ -50,16 +65,12 @@
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
|
||||
"Doctrine\\StaticAnalysis\\": "tests/Doctrine/StaticAnalysis",
|
||||
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
|
||||
}
|
||||
},
|
||||
"bin": ["bin/doctrine"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7.x-dev"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
|
||||
}
|
||||
}
|
||||
|
||||
3634
composer.lock
generated
3634
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
The Doctrine2 documentation is licensed under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
|
||||
The Doctrine ORM documentation is licensed under [CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_US)
|
||||
|
||||
Creative Commons Legal Code
|
||||
|
||||
@@ -359,5 +359,4 @@ Creative Commons Notice
|
||||
available upon request from time to time. For the avoidance of doubt,
|
||||
this trademark restriction does not form part of this License.
|
||||
|
||||
Creative Commons may be contacted at http://creativecommons.org/.
|
||||
|
||||
Creative Commons may be contacted at https://creativecommons.org/.
|
||||
|
||||
@@ -7,4 +7,4 @@ rm build -Rf
|
||||
sphinx-build en build
|
||||
|
||||
sphinx-build -b latex en build/pdf
|
||||
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex
|
||||
rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments
|
||||
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments
|
||||
|
||||
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
|
||||
@@ -4,7 +4,7 @@ Advanced field value conversion using custom mapping types
|
||||
.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>
|
||||
|
||||
When creating entities, you sometimes have the need to transform field values
|
||||
before they are saved to the database. In Doctrine you can use Custom Mapping
|
||||
before they are saved to the database. In Doctrine you can use Custom Mapping
|
||||
Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`).
|
||||
|
||||
There are several ways to achieve this: converting the value inside the Type
|
||||
@@ -15,7 +15,7 @@ type `Point <https://dev.mysql.com/doc/refman/8.0/en/gis-class-point.html>`_.
|
||||
|
||||
The ``Point`` type is part of the `Spatial extension <https://dev.mysql.com/doc/refman/8.0/en/spatial-extensions.html>`_
|
||||
of MySQL and enables you to store a single location in a coordinate space by
|
||||
using x and y coordinates. You can use the Point type to store a
|
||||
using x and y coordinates. You can use the Point type to store a
|
||||
longitude/latitude pair to represent a geographic location.
|
||||
|
||||
The entity
|
||||
@@ -29,62 +29,42 @@ The entity class:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
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:
|
||||
@@ -92,34 +72,23 @@ The point class:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
namespace Geo\ValueObject;
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -193,10 +162,10 @@ object into a string representation before saving to the database (in the
|
||||
value from the database (in the ``convertToPHPValue`` method).
|
||||
|
||||
The format of the string representation format is called
|
||||
`Well-known text (WKT) <http://en.wikipedia.org/wiki/Well-known_text>`_.
|
||||
`Well-known text (WKT) <https://en.wikipedia.org/wiki/Well-known_text>`_.
|
||||
The advantage of this format is, that it is both human readable and parsable by MySQL.
|
||||
|
||||
Internally, MySQL stores geometry values in a binary format that is not
|
||||
Internally, MySQL stores geometry values in a binary format that is not
|
||||
identical to the WKT format. So, we need to let MySQL transform the WKT
|
||||
representation into its internal format.
|
||||
|
||||
@@ -210,13 +179,13 @@ which convert WKT strings to and from the internal format of MySQL.
|
||||
|
||||
.. note::
|
||||
|
||||
When using DQL queries, the ``convertToPHPValueSQL`` and
|
||||
When using DQL queries, the ``convertToPHPValueSQL`` and
|
||||
``convertToDatabaseValueSQL`` methods only apply to identification variables
|
||||
and path expressions in SELECT clauses. Expressions in WHERE clauses are
|
||||
and path expressions in SELECT clauses. Expressions in WHERE clauses are
|
||||
**not** wrapped!
|
||||
|
||||
If you want to use Point values in WHERE clauses, you have to implement a
|
||||
:doc:`user defined function <dql-user-defined-functions>` for
|
||||
:doc:`user defined function <dql-user-defined-functions>` for
|
||||
``PointFromText``.
|
||||
|
||||
Example usage
|
||||
@@ -227,7 +196,7 @@ Example usage
|
||||
<?php
|
||||
|
||||
// Bootstrapping stuff...
|
||||
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
|
||||
// $em = new \Doctrine\ORM\EntityManager($connection, $config);
|
||||
|
||||
// Setup custom mapping type
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
@@ -252,5 +221,5 @@ Example usage
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$location = $query->getSingleResult();
|
||||
|
||||
/* @var Geo\ValueObject\Point */
|
||||
/** @var Geo\ValueObject\Point */
|
||||
$point = $location->getPoint();
|
||||
|
||||
@@ -6,7 +6,7 @@ Aggregate Fields
|
||||
You will often come across the requirement to display aggregate
|
||||
values of data that can be computed by using the MIN, MAX, COUNT or
|
||||
SUM SQL functions. For any ORM this is a tricky issue
|
||||
traditionally. Doctrine 2 offers several ways to get access to
|
||||
traditionally. Doctrine ORM offers several ways to get access to
|
||||
these values and this article will describe all of them from
|
||||
different perspectives.
|
||||
|
||||
@@ -32,30 +32,39 @@ Our entities look like:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Bank\Entities;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Account
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer") */
|
||||
private $id;
|
||||
|
||||
/** @Column(type="string", unique=true) */
|
||||
private $no;
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"})
|
||||
* @ORM\Column(type="string", unique=true)
|
||||
*/
|
||||
private $entries;
|
||||
private string $no;
|
||||
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
* @ORM\OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"})
|
||||
*/
|
||||
private $maxCredit = 0;
|
||||
private array $entries;
|
||||
|
||||
public function __construct($no, $maxCredit = 0)
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private int $maxCredit = 0;
|
||||
|
||||
public function __construct(string $no, int $maxCredit = 0)
|
||||
{
|
||||
$this->no = $no;
|
||||
$this->maxCredit = $maxCredit;
|
||||
@@ -64,31 +73,35 @@ Our entities look like:
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Entry
|
||||
{
|
||||
/** @Id @GeneratedValue @Column(type="integer") */
|
||||
private $id;
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private ?int $id;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Account", inversedBy="entries")
|
||||
* @ORM\ManyToOne(targetEntity="Account", inversedBy="entries")
|
||||
*/
|
||||
private $account;
|
||||
private Account $account;
|
||||
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private $amount;
|
||||
private int $amount;
|
||||
|
||||
public function __construct($account, $amount)
|
||||
public function __construct(Account $account, int $amount)
|
||||
{
|
||||
$this->account = $account;
|
||||
$this->amount = $amount;
|
||||
// more stuff here, from/to whom, stated reason, execution date and such
|
||||
}
|
||||
|
||||
public function getAmount()
|
||||
public function getAmount(): Amount
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
@@ -146,12 +159,14 @@ collection, which means we can compute this value at runtime:
|
||||
class Account
|
||||
{
|
||||
// .. previous code
|
||||
public function getBalance()
|
||||
|
||||
public function getBalance(): int
|
||||
{
|
||||
$balance = 0;
|
||||
foreach ($this->entries as $entry) {
|
||||
$balance += $entry->getAmount();
|
||||
}
|
||||
|
||||
return $balance;
|
||||
}
|
||||
}
|
||||
@@ -175,13 +190,12 @@ relation with this method:
|
||||
<?php
|
||||
class Account
|
||||
{
|
||||
public function addEntry($amount)
|
||||
public function addEntry(int $amount): void
|
||||
{
|
||||
$this->assertAcceptEntryAllowed($amount);
|
||||
|
||||
$e = new Entry($this, $amount);
|
||||
$this->entries[] = $e;
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +204,10 @@ Now look at the following test-code for our entities:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class AccountTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AccountTest extends TestCase
|
||||
{
|
||||
public function testAddEntry()
|
||||
{
|
||||
@@ -208,7 +225,7 @@ Now look at the following test-code for our entities:
|
||||
{
|
||||
$account = new Account("123456", $maxCredit = 200);
|
||||
|
||||
$this->setExpectedException("Exception");
|
||||
$this->expectException(Exception::class);
|
||||
$account->addEntry(-1000);
|
||||
}
|
||||
}
|
||||
@@ -219,9 +236,12 @@ To enforce our rule we can now implement the assertion in
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Account
|
||||
{
|
||||
private function assertAcceptEntryAllowed($amount)
|
||||
// .. previous code
|
||||
|
||||
private function assertAcceptEntryAllowed(int $amount): void
|
||||
{
|
||||
$futureBalance = $this->getBalance() + $amount;
|
||||
$allowedMinimalBalance = ($this->maxCredit * -1);
|
||||
@@ -266,23 +286,22 @@ entries collection) we want to add an aggregate field called
|
||||
class Account
|
||||
{
|
||||
/**
|
||||
* @Column(type="integer")
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private $balance = 0;
|
||||
private int $balance = 0;
|
||||
|
||||
public function getBalance()
|
||||
public function getBalance(): int
|
||||
{
|
||||
return $this->balance;
|
||||
}
|
||||
|
||||
public function addEntry($amount)
|
||||
public function addEntry(int $amount): void
|
||||
{
|
||||
$this->assertAcceptEntryAllowed($amount);
|
||||
|
||||
$e = new Entry($this, $amount);
|
||||
$this->entries[] = $e;
|
||||
$this->balance += $amount;
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,12 +325,15 @@ potentially lead to inconsistent state. See this example:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Bank\Entities\Account;
|
||||
|
||||
// The Account $accId has a balance of 0 and a max credit limit of 200:
|
||||
// request 1 account
|
||||
$account1 = $em->find('Bank\Entities\Account', $accId);
|
||||
$account1 = $em->find(Account::class, $accId);
|
||||
|
||||
// request 2 account
|
||||
$account2 = $em->find('Bank\Entities\Account', $accId);
|
||||
$account2 = $em->find(Account::class, $accId);
|
||||
|
||||
$account1->addEntry(-200);
|
||||
$account2->addEntry(-200);
|
||||
@@ -332,10 +354,14 @@ Optimistic locking is as easy as adding a version column:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Account
|
||||
{
|
||||
/** @Column(type="integer") @Version */
|
||||
private $version;
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\Version
|
||||
*/
|
||||
private int $version;
|
||||
}
|
||||
|
||||
The previous example would then throw an exception in the face of
|
||||
@@ -349,9 +375,11 @@ the database using a FOR UPDATE.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Bank\Entities\Account;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
|
||||
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
|
||||
|
||||
$account = $em->find(Account::class, $accId, LockMode::PESSIMISTIC_READ);
|
||||
|
||||
Keeping Updates and Deletes in Sync
|
||||
-----------------------------------
|
||||
@@ -372,5 +400,3 @@ field that offers serious performance benefits over iterating all
|
||||
the related objects that make up an aggregate value. Finally I
|
||||
showed how you can ensure that your aggregate fields do not get out
|
||||
of sync due to race-conditions and concurrent access.
|
||||
|
||||
|
||||
|
||||
@@ -3,98 +3,82 @@ Persisting the Decorator Pattern
|
||||
|
||||
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
|
||||
|
||||
This recipe will show you a simple example of how you can use
|
||||
Doctrine 2 to persist an implementation of the
|
||||
`Decorator Pattern <http://en.wikipedia.org/wiki/Decorator_pattern>`_
|
||||
This recipe will show you a simple example of how you can use
|
||||
Doctrine ORM to persist an implementation of the
|
||||
`Decorator Pattern <https://en.wikipedia.org/wiki/Decorator_pattern>`_
|
||||
|
||||
Component
|
||||
---------
|
||||
|
||||
The ``Component`` class needs to be persisted, so it's going to
|
||||
be an ``Entity``. As the top of the inheritance hierarchy, it's going
|
||||
to have to define the persistent inheritance. For this example, we
|
||||
will use Single Table Inheritance, but Class Table Inheritance
|
||||
would work as well. In the discriminator map, we will define two
|
||||
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
|
||||
The ``Component`` class needs to be persisted, so it's going to
|
||||
be an ``Entity``. As the top of the inheritance hierarchy, it's going
|
||||
to have to define the persistent inheritance. For this example, we
|
||||
will use Single Table Inheritance, but Class Table Inheritance
|
||||
would work as well. In the discriminator map, we will define two
|
||||
concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
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;
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
|
||||
#[Id, Column]
|
||||
#[GeneratedValue(strategy: 'AUTO')]
|
||||
protected int|null $id = null;
|
||||
|
||||
#[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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
ConcreteComponent
|
||||
-----------------
|
||||
|
||||
The ``ConcreteComponent`` class is pretty simple and doesn't do much
|
||||
more than extend the abstract ``Component`` class (only for the
|
||||
The ``ConcreteComponent`` class is pretty simple and doesn't do much
|
||||
more than extend the abstract ``Component`` class (only for the
|
||||
purpose of keeping this example simple).
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
namespace Test\Component;
|
||||
|
||||
|
||||
use Test\Component;
|
||||
|
||||
/** @Entity */
|
||||
|
||||
#[Entity]
|
||||
class ConcreteComponent extends Component
|
||||
{}
|
||||
|
||||
|
||||
Decorator
|
||||
---------
|
||||
|
||||
The ``Decorator`` class doesn't need to be persisted, but it does
|
||||
need to define an association with a persisted ``Entity``. We can
|
||||
The ``Decorator`` class doesn't need to be persisted, but it does
|
||||
need to define an association with a persisted ``Entity``. We can
|
||||
use a ``MappedSuperclass`` for this.
|
||||
|
||||
.. code-block:: php
|
||||
@@ -102,17 +86,14 @@ use a ``MappedSuperclass`` for this.
|
||||
<?php
|
||||
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* initialize the decorator
|
||||
* @param Component $c
|
||||
@@ -121,153 +102,138 @@ use a ``MappedSuperclass`` for this.
|
||||
{
|
||||
$this->setDecorates($c);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
|
||||
cascade from the ``Decorator`` to the ``Component``. This means that
|
||||
when we persist a ``Decorator``, Doctrine will take care of
|
||||
persisting the chain of decorated objects for us. A ``Decorator`` can
|
||||
be treated exactly as a ``Component`` when it comes time to
|
||||
All operations on the ``Decorator`` (i.e. persist, remove, etc) will
|
||||
cascade from the ``Decorator`` to the ``Component``. This means that
|
||||
when we persist a ``Decorator``, Doctrine will take care of
|
||||
persisting the chain of decorated objects for us. A ``Decorator`` can
|
||||
be treated exactly as a ``Component`` when it comes time to
|
||||
persisting it.
|
||||
|
||||
The ``Decorator's`` constructor accepts an instance of a
|
||||
``Component``, as defined by the ``Decorator`` pattern. The
|
||||
setDecorates/getDecorates methods have been defined as protected to
|
||||
hide the fact that a ``Decorator`` is decorating a ``Component`` and
|
||||
keeps the ``Component`` interface and the ``Decorator`` interface
|
||||
|
||||
The ``Decorator's`` constructor accepts an instance of a
|
||||
``Component``, as defined by the ``Decorator`` pattern. The
|
||||
setDecorates/getDecorates methods have been defined as protected to
|
||||
hide the fact that a ``Decorator`` is decorating a ``Component`` and
|
||||
keeps the ``Component`` interface and the ``Decorator`` interface
|
||||
identical.
|
||||
|
||||
To illustrate the intended result of the ``Decorator`` pattern, the
|
||||
getName() method has been overridden to append a string to the
|
||||
To illustrate the intended result of the ``Decorator`` pattern, the
|
||||
getName() method has been overridden to append a string to the
|
||||
``Component's`` getName() method.
|
||||
|
||||
ConcreteDecorator
|
||||
-----------------
|
||||
|
||||
The final class required to complete a simple implementation of the
|
||||
Decorator pattern is the ``ConcreteDecorator``. In order to further
|
||||
illustrate how the ``Decorator`` can alter data as it moves through
|
||||
the chain of decoration, a new field, "special", has been added to
|
||||
this class. The getName() has been overridden and appends the value
|
||||
of the getSpecial() method to its return value.
|
||||
The final class required to complete a simple implementation of the
|
||||
Decorator pattern is the ``ConcreteDecorator``. In order to further
|
||||
illustrate how the ``Decorator`` can alter data as it moves through
|
||||
the chain of decoration, a new field, "special", has been added to
|
||||
this class. The getName() has been overridden and appends the value
|
||||
of the getSpecial() method to its return value.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
namespace Test\Decorator;
|
||||
|
||||
|
||||
use Test\Decorator;
|
||||
|
||||
/** @Entity */
|
||||
|
||||
#[Entity]
|
||||
class ConcreteDecorator extends Decorator
|
||||
{
|
||||
|
||||
/** @Column(type="string", nullable=true) */
|
||||
protected $special;
|
||||
|
||||
/**
|
||||
* Set special
|
||||
* @param string $special
|
||||
*/
|
||||
public function setSpecial($special)
|
||||
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
protected string|null $special = null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* (non-PHPdoc)
|
||||
* @see Test.Component::getName()
|
||||
*/
|
||||
public function getName()
|
||||
public function getName(): string
|
||||
{
|
||||
return '[' . $this->getSpecial()
|
||||
. '] ' . parent::getName();
|
||||
. '] ' . parent::getName();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Here is an example of how to persist and retrieve your decorated
|
||||
Here is an example of how to persist and retrieve your decorated
|
||||
objects
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
use Test\Component\ConcreteComponent,
|
||||
Test\Decorator\ConcreteDecorator;
|
||||
|
||||
// assumes Doctrine 2 is configured and an instance of
|
||||
|
||||
// assumes Doctrine ORM is configured and an instance of
|
||||
// an EntityManager is available as $em
|
||||
|
||||
|
||||
// create a new concrete component
|
||||
$c = new ConcreteComponent();
|
||||
$c->setName('Test Component 1');
|
||||
$em->persist($c); // assigned unique ID = 1
|
||||
|
||||
|
||||
// create a new concrete decorator
|
||||
$c = new ConcreteComponent();
|
||||
$c->setName('Test Component 2');
|
||||
|
||||
|
||||
$d = new ConcreteDecorator($c);
|
||||
$d->setSpecial('Really');
|
||||
$em->persist($d);
|
||||
$em->persist($d);
|
||||
// assigns c as unique ID = 2, and d as unique ID = 3
|
||||
|
||||
|
||||
$em->flush();
|
||||
|
||||
$c = $em->find('Test\Component', 1);
|
||||
$d = $em->find('Test\Component', 3);
|
||||
|
||||
|
||||
echo get_class($c);
|
||||
// prints: Test\Component\ConcreteComponent
|
||||
|
||||
|
||||
echo $c->getName();
|
||||
// prints: Test Component 1
|
||||
|
||||
echo get_class($d)
|
||||
// prints: Test Component 1
|
||||
|
||||
echo get_class($d)
|
||||
// prints: Test\Component\ConcreteDecorator
|
||||
|
||||
|
||||
echo $d->getName();
|
||||
// prints: [Really] Decorated Test Component 2
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Extending DQL in Doctrine 2: Custom AST Walkers
|
||||
Extending DQL in Doctrine ORM: Custom AST Walkers
|
||||
===============================================
|
||||
|
||||
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||
@@ -12,9 +12,9 @@ the Doctrine ORM.
|
||||
|
||||
In Doctrine 1 the DQL language was not implemented using a real
|
||||
parser. This made modifications of the DQL by the user impossible.
|
||||
Doctrine 2 in contrast has a real parser for the DQL language,
|
||||
Doctrine ORM in contrast has a real parser for the DQL language,
|
||||
which transforms the DQL statement into an
|
||||
`Abstract Syntax Tree <http://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
|
||||
`Abstract Syntax Tree <https://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
|
||||
and generates the appropriate SQL statement for it. Since this
|
||||
process is deterministic Doctrine heavily caches the SQL that is
|
||||
generated from any given DQL query, which reduces the performance
|
||||
@@ -33,8 +33,8 @@ the DQL parser:
|
||||
is only ever one of them. We implemented the default SqlWalker
|
||||
implementation for it.
|
||||
- A tree walker. There can be many tree walkers, they cannot
|
||||
generate the sql, however they can modify the AST before its
|
||||
rendered to sql.
|
||||
generate the SQL, however they can modify the AST before its
|
||||
rendered to SQL.
|
||||
|
||||
Now this is all awfully technical, so let me come to some use-cases
|
||||
fast to keep you motivated. Using walker implementation you can for
|
||||
@@ -50,7 +50,7 @@ example:
|
||||
- Modify the Output walker to pretty print the SQL for debugging
|
||||
purposes.
|
||||
|
||||
In this cookbook-entry I will show examples on the first two
|
||||
In this cookbook-entry I will show examples of the first two
|
||||
points. There are probably much more use-cases.
|
||||
|
||||
Generic count query for pagination
|
||||
@@ -64,7 +64,7 @@ like:
|
||||
|
||||
SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
|
||||
|
||||
Now in this query the blog post is the root entity, meaning its the
|
||||
Now in this query the blog post is the root entity, meaning it's the
|
||||
one that is hydrated directly from the query and returned as an
|
||||
array of blog posts. In contrast the comment and author are loaded
|
||||
for deeper use in the object tree.
|
||||
@@ -79,7 +79,7 @@ query for pagination would look like:
|
||||
SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...
|
||||
|
||||
Now you could go and write each of these queries by hand, or you
|
||||
can use a tree walker to modify the AST for you. Lets see how the
|
||||
can use a tree walker to modify the AST for you. Let's see how the
|
||||
API would look for this use-case:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -88,7 +88,7 @@ API would look for this use-case:
|
||||
$pageNum = 1;
|
||||
$query = $em->createQuery($dql);
|
||||
$query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20);
|
||||
|
||||
|
||||
$totalResults = Paginate::count($query);
|
||||
$results = $query->getResult();
|
||||
|
||||
@@ -101,12 +101,12 @@ The ``Paginate::count(Query $query)`` looks like:
|
||||
{
|
||||
static public function count(Query $query)
|
||||
{
|
||||
/* @var $countQuery Query */
|
||||
/** @var Query $countQuery */
|
||||
$countQuery = clone $query;
|
||||
|
||||
|
||||
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
|
||||
$countQuery->setFirstResult(null)->setMaxResults(null);
|
||||
|
||||
|
||||
return $countQuery->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
@@ -137,13 +137,13 @@ implementation is:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$pathExpression = new PathExpression(
|
||||
PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName,
|
||||
$parent['metadata']->getSingleIdentifierFieldName()
|
||||
);
|
||||
$pathExpression->type = PathExpression::TYPE_STATE_FIELD;
|
||||
|
||||
|
||||
$AST->selectClause->selectExpressions = array(
|
||||
new SelectExpression(
|
||||
new AggregateExpression('count', $pathExpression, true), null
|
||||
@@ -196,7 +196,7 @@ modify the generation of the SELECT clause, adding the
|
||||
public function walkSelectClause($selectClause)
|
||||
{
|
||||
$sql = parent::walkSelectClause($selectClause);
|
||||
|
||||
|
||||
if ($this->getQuery()->getHint('mysqlWalker.sqlNoCache') === true) {
|
||||
if ($selectClause->isDistinct) {
|
||||
$sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql);
|
||||
@@ -204,7 +204,7 @@ modify the generation of the SELECT clause, adding the
|
||||
$sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ change it during the life of your project. This decision for a
|
||||
specific vendor potentially allows you to make use of powerful SQL
|
||||
features that are unique to the vendor.
|
||||
|
||||
It is worth to mention that Doctrine 2 also allows you to handwrite
|
||||
It is worth to mention that Doctrine ORM also allows you to handwrite
|
||||
your SQL instead of extending the DQL parser. Extending DQL is sort of an
|
||||
advanced extension point. You can map arbitrary SQL to your objects
|
||||
and gain access to vendor specific functionalities using the
|
||||
@@ -45,8 +45,8 @@ configuration:
|
||||
$config->addCustomStringFunction($name, $class);
|
||||
$config->addCustomNumericFunction($name, $class);
|
||||
$config->addCustomDatetimeFunction($name, $class);
|
||||
|
||||
$em = EntityManager::create($dbParams, $config);
|
||||
|
||||
$em = new EntityManager($connection, $config);
|
||||
|
||||
The ``$name`` is the name the function will be referred to in the
|
||||
DQL query. ``$class`` is a string of a class-name which has to
|
||||
@@ -96,7 +96,7 @@ discuss it step by step:
|
||||
// (1)
|
||||
public $firstDateExpression = null;
|
||||
public $secondDateExpression = null;
|
||||
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER); // (2)
|
||||
@@ -106,7 +106,7 @@ discuss it step by step:
|
||||
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
|
||||
}
|
||||
|
||||
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'DATEDIFF(' .
|
||||
@@ -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 <http://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
|
||||
@@ -180,28 +180,28 @@ I'll skip the blah and show the code for this function:
|
||||
public $firstDateExpression = null;
|
||||
public $intervalExpression = null;
|
||||
public $unit = null;
|
||||
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
|
||||
$this->firstDateExpression = $parser->ArithmeticPrimary();
|
||||
|
||||
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
|
||||
$this->intervalExpression = $parser->ArithmeticPrimary();
|
||||
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
/* @var $lexer Lexer */
|
||||
|
||||
/** @var Lexer $lexer */
|
||||
$lexer = $parser->getLexer();
|
||||
$this->unit = $lexer->token['value'];
|
||||
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
|
||||
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'DATE_ADD(' .
|
||||
@@ -240,12 +240,10 @@ functionalities in DQL, we would be excited to see user extensions
|
||||
that add vendor specific function packages, for example more math
|
||||
functions, XML + GIS Support, Hashing functions and so on.
|
||||
|
||||
For 2.0 we will come with the current set of functions, however for
|
||||
For ORM we will come with the current set of functions, however for
|
||||
a future version we will re-evaluate if we can abstract even more
|
||||
vendor sql functions and extend the DQL languages scope.
|
||||
|
||||
Code for this Extension to DQL and other Doctrine Extensions can be
|
||||
found
|
||||
`in the GitHub DoctrineExtensions repository <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||
|
||||
|
||||
`in the GitHub DoctrineExtensions repository <https://github.com/beberlei/DoctrineExtensions>`_.
|
||||
|
||||
@@ -3,42 +3,69 @@ Entities in the Session
|
||||
|
||||
There are several use-cases to save entities in the session, for example:
|
||||
|
||||
1. User object
|
||||
1. User data
|
||||
2. Multi-step forms
|
||||
|
||||
To achieve this with Doctrine you have to pay attention to some details to get
|
||||
this working.
|
||||
|
||||
Merging entity into an EntityManager
|
||||
------------------------------------
|
||||
Updating an entity
|
||||
------------------
|
||||
|
||||
In Doctrine an entity objects has to be "managed" by an EntityManager to be
|
||||
updateable. Entities saved into the session are not managed in the next request
|
||||
anymore. This means that you have to register these entities with an
|
||||
EntityManager again if you want to change them or use them as part of
|
||||
references between other entities. You can achieve this by calling
|
||||
``EntityManager#merge()``.
|
||||
updatable. Entities saved into the session are not managed in the next request
|
||||
anymore. This means that you have to update the entities with the stored session
|
||||
data after you fetch the entities from the EntityManager again.
|
||||
|
||||
For a representative User object the code to get turn an instance from
|
||||
the session into a managed Doctrine object looks like this:
|
||||
For a representative User object the code to get data from the session into a
|
||||
managed Doctrine object can look like these examples:
|
||||
|
||||
Working with scalars
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In simpler applications there is no need to work with objects in sessions and you can use
|
||||
separate session elements.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
require_once 'bootstrap.php';
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
|
||||
session_start();
|
||||
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) {
|
||||
$user = $_SESSION['user'];
|
||||
$user = $em->merge($user);
|
||||
if (isset($_SESSION['userId']) && is_int($_SESSION['userId'])) {
|
||||
$userId = $_SESSION['userId'];
|
||||
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
$user = $em->find(User::class, $userId);
|
||||
|
||||
$user->setValue($_SESSION['storedValue']);
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
.. note::
|
||||
Working with custom data transfer objects
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A frequent mistake is not to get the merged user object from the return
|
||||
value of ``EntityManager#merge()``. The entity object passed to merge is
|
||||
not necessarily the same object that is returned from the method.
|
||||
If objects are needed, we discourage the storage of entity objects in the session. It's
|
||||
preferable to use a `DTO (data transfer object) <https://en.wikipedia.org/wiki/Data_transfer_object>`_
|
||||
instead and merge the DTO data later with the entity.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
session_start();
|
||||
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof UserDto) {
|
||||
$userDto = $_SESSION['user'];
|
||||
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
$userEntity = $em->find(User::class, $userDto->getId());
|
||||
|
||||
$userEntity->populateFromDto($userDto);
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
Serializing entity into the session
|
||||
-----------------------------------
|
||||
@@ -47,22 +74,20 @@ Entities that are serialized into the session normally contain references to
|
||||
other entities as well. Think of the user entity has a reference to their
|
||||
articles, groups, photos or many other different entities. If you serialize
|
||||
this object into the session then you don't want to serialize the related
|
||||
entities as well. This is why you should call ``EntityManager#detach()`` on this
|
||||
object or implement the __sleep() magic method on your entity.
|
||||
entities as well. This is why you shouldn't serialize an entity and use
|
||||
only the needed values of it. This can happen with the help of a DTO.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
|
||||
$user = $em->find("User", 1);
|
||||
$em->detach($user);
|
||||
$_SESSION['user'] = $user;
|
||||
$userDto = new UserDto($user->getId(), $user->getFirstName(), $user->getLastName());
|
||||
// or "UserDto::createFrom($user);", but don't store an entity in a property. Only its values without relations.
|
||||
|
||||
.. note::
|
||||
$_SESSION['user'] = $userDto;
|
||||
|
||||
When you called detach on your objects they get "unmanaged" with that
|
||||
entity manager. This means you cannot use them as part of write operations
|
||||
during ``EntityManager#flush()`` anymore in this request.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Implementing ArrayAccess for Domain Objects
|
||||
This recipe will show you how to implement ArrayAccess for your
|
||||
domain objects in order to allow more uniform access, for example
|
||||
in templates. In these examples we will implement ArrayAccess on a
|
||||
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||
`Layer Supertype <https://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||
for all our domain objects.
|
||||
|
||||
Option 1
|
||||
|
||||
@@ -7,9 +7,14 @@ The NOTIFY change-tracking policy is the most effective
|
||||
change-tracking policy provided by Doctrine but it requires some
|
||||
boilerplate code. This recipe will show you how this boilerplate
|
||||
code should look like. We will implement it on a
|
||||
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||
`Layer Supertype <https://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||
for all our domain objects.
|
||||
|
||||
.. note::
|
||||
|
||||
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
|
||||
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
|
||||
|
||||
Implementing NotifyPropertyChanged
|
||||
----------------------------------
|
||||
|
||||
@@ -24,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) {
|
||||
@@ -50,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);
|
||||
@@ -68,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.
|
||||
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
Implementing Wakeup or Clone
|
||||
============================
|
||||
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
As explained in the
|
||||
`restrictions for entity classes in the manual <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#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
|
||||
way by guarding the custom wakeup or clone code with an entity
|
||||
identity check, as demonstrated in the following sections.
|
||||
|
||||
Safely implementing __wakeup
|
||||
----------------------------
|
||||
|
||||
To safely implement ``__wakeup``, simply enclose your
|
||||
implementation code in an identity check as follows:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntity
|
||||
{
|
||||
private $id; // This is the identifier of the entity.
|
||||
//...
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
// If the entity has an identity, proceed as normal.
|
||||
if ($this->id) {
|
||||
// ... Your code here as normal ...
|
||||
}
|
||||
// otherwise do nothing, do NOT throw an exception!
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
|
||||
Safely implementing __clone
|
||||
---------------------------
|
||||
|
||||
Safely implementing ``__clone`` is pretty much the same:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class MyEntity
|
||||
{
|
||||
private $id; // This is the identifier of the entity.
|
||||
//...
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
// If the entity has an identity, proceed as normal.
|
||||
if ($this->id) {
|
||||
// ... Your code here as normal ...
|
||||
}
|
||||
// otherwise do nothing, do NOT throw an exception!
|
||||
}
|
||||
|
||||
//...
|
||||
}
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
As you have seen, it is quite easy to safely make use of
|
||||
``__wakeup`` and ``__clone`` in your entities without adding any
|
||||
really Doctrine-specific or Doctrine-dependant code.
|
||||
|
||||
These implementations are possible and safe because when Doctrine
|
||||
invokes these methods, the entities never have an identity (yet).
|
||||
Furthermore, it is possibly a good idea to check for the identity
|
||||
in your code anyway, since it's rarely the case that you want to
|
||||
unserialize or clone an entity with no identity.
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Mysql Enums
|
||||
===========
|
||||
|
||||
The type system of Doctrine 2 consists of flyweights, which means there is only
|
||||
The type system of Doctrine ORM consists of flyweights, which means there is only
|
||||
one instance of any given type. Additionally types do not contain state. Both
|
||||
assumptions make it rather complicated to work with the Enum Type of MySQL that
|
||||
is used quite a lot by developers.
|
||||
|
||||
When using Enums with a non-tweaked Doctrine 2 application you will get
|
||||
When using Enums with a non-tweaked Doctrine ORM application you will get
|
||||
errors from the Schema-Tool commands due to the unknown database type "enum".
|
||||
By default Doctrine does not map the MySQL enum type to a Doctrine type.
|
||||
This is because Enums contain state (their allowed values) and Doctrine
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
Keeping your Modules independent
|
||||
=================================
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
One of the goals of using modules is to create discrete units of functionality
|
||||
that do not have many (if any) dependencies, allowing you to use that
|
||||
functionality in other applications without including unnecessary items.
|
||||
|
||||
Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
|
||||
Doctrine ORM includes a new utility called the ``ResolveTargetEntityListener``,
|
||||
that functions by intercepting certain calls inside Doctrine and rewrite
|
||||
targetEntity parameters in your metadata mapping at runtime. It means that
|
||||
in your bundle you are able to use an interface or abstract class in your
|
||||
@@ -129,7 +127,8 @@ the targetEntity resolution will occur reliably:
|
||||
// Add the ResolveTargetEntityListener
|
||||
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
$connection = \Doctrine\DBAL\DriverManager::createConnection($connectionOptions, $config, $evm);
|
||||
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
|
||||
|
||||
Final Thoughts
|
||||
--------------
|
||||
@@ -138,5 +137,3 @@ With the ``ResolveTargetEntityListener``, we are able to decouple our
|
||||
bundles, keeping them usable by themselves, but still being able to
|
||||
define relationships between different objects. By using this method,
|
||||
I've found my bundles end up being easier to maintain independently.
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -81,6 +81,4 @@ before the prefix has been set.
|
||||
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
|
||||
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
|
||||
|
||||
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
|
||||
|
||||
@@ -30,7 +30,7 @@ highly uncomfortable because of the following:
|
||||
every panel-type? This wouldn't be flexible. You might be tempted
|
||||
to add an AbstractPanelEntity and an AbstractBlockEntity that use
|
||||
class inheritance. Your page could then only confer to the
|
||||
AbstractPanelType and Doctrine 2 would do the rest for you, i.e.
|
||||
AbstractPanelType and Doctrine ORM would do the rest for you, i.e.
|
||||
load the right entities. But - you'll for sure have lots of panels
|
||||
and blocks, and even worse, you'd have to edit the discriminator
|
||||
map *manually* every time you or another developer implements a new
|
||||
@@ -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
|
||||
@@ -152,16 +152,16 @@ 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 2)
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2.
|
||||
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine ORM.
|
||||
*
|
||||
* @var BlockStrategyInterface
|
||||
*/
|
||||
@@ -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!
|
||||
@@ -199,7 +199,7 @@ As you can see, we have a method "setBlockEntity" which ties a potential strateg
|
||||
$strategy->setBlockEntity($this);
|
||||
}
|
||||
|
||||
Now, the important point is that $strategyClassName is a Doctrine 2
|
||||
Now, the important point is that $strategyClassName is a Doctrine ORM
|
||||
field, i.e. Doctrine will persist this value. This is only the
|
||||
class name of your strategy and not an instance!
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Validation of Entities
|
||||
|
||||
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||
|
||||
Doctrine 2 does not ship with any internal validators, the reason
|
||||
Doctrine ORM does not ship with any internal validators, the reason
|
||||
being that we think all the frameworks out there already ship with
|
||||
quite decent ones that can be integrated into your Domain easily.
|
||||
What we offer are hooks to execute any kind of validation.
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ Working with DateTime Instances
|
||||
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
|
||||
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
|
||||
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
|
||||
interesting pieces of information on how to work with PHP DateTime instances in ORM.
|
||||
|
||||
DateTime changes are detected by Reference
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,16 +63,16 @@ Handling different Timezones with the DateTime Type
|
||||
|
||||
If you first come across the requirement to save different timezones you may be still optimistic about how
|
||||
to manage this mess,
|
||||
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
|
||||
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine ORM)
|
||||
that supports timezones correctly. Correctly here means that you can cover all the use-cases that
|
||||
can come up with timezones. If you don't believe me you should read up on `Storing DateTime
|
||||
in Databases <http://derickrethans.nl/storing-date-time-in-database.html>`_.
|
||||
in Databases <https://derickrethans.nl/storing-date-time-in-database.html>`_.
|
||||
|
||||
The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC.
|
||||
However with frequent daylight saving and political timezone changes you can have a UTC offset that moves
|
||||
in different offset directions depending on the real location.
|
||||
|
||||
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround
|
||||
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine ORM. However there is a workaround
|
||||
that even allows correct date-time handling with timezones:
|
||||
|
||||
1. Always convert any DateTime instance to UTC.
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ Doctrine ORM don't panic. You can get help from different sources:
|
||||
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
|
||||
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
|
||||
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
|
||||
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
|
||||
- On `StackOverflow <https://stackoverflow.com/questions/tagged/doctrine-orm>`_
|
||||
|
||||
If you need more structure over the different topics you can browse the :doc:`table
|
||||
@@ -41,6 +40,7 @@ Mapping Objects onto a Database
|
||||
|
||||
* **Drivers**:
|
||||
:doc:`Docblock Annotations <reference/annotations-reference>` |
|
||||
:doc:`Attributes <reference/attributes-reference>` |
|
||||
:doc:`XML <reference/xml-mapping>` |
|
||||
:doc:`YAML <reference/yaml-mapping>` |
|
||||
:doc:`PHP <reference/php-mapping>`
|
||||
@@ -72,6 +72,7 @@ Advanced Topics
|
||||
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
|
||||
* :doc:`Filters <reference/filters>`
|
||||
* :doc:`NamingStrategy <reference/namingstrategy>`
|
||||
* :doc:`TypedFieldMapper <reference/typedfieldmapper>`
|
||||
* :doc:`Improving Performance <reference/improving-performance>`
|
||||
* :doc:`Caching <reference/caching>`
|
||||
* :doc:`Partial Objects <reference/partial-objects>`
|
||||
@@ -112,7 +113,6 @@ Cookbook
|
||||
* **Implementation**:
|
||||
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
|
||||
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
|
||||
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
|
||||
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
|
||||
:doc:`Validation <cookbook/validation-of-entities>` |
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` |
|
||||
|
||||
226
docs/en/make.bat
226
docs/en/make.bat
@@ -1,113 +1,113 @@
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
set SPHINXBUILD=sphinx-build
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
set SPHINXBUILD=sphinx-build
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Doctrine2ORM.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
|
||||
@@ -9,51 +9,61 @@ steps of configuration.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\EntityManager,
|
||||
Doctrine\ORM\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;
|
||||
|
||||
// ...
|
||||
|
||||
|
||||
if ($applicationMode == "development") {
|
||||
$cache = new \Doctrine\Common\Cache\ArrayCache;
|
||||
$queryCache = new ArrayAdapter();
|
||||
$metadataCache = new ArrayAdapter();
|
||||
} else {
|
||||
$cache = new \Doctrine\Common\Cache\ApcCache;
|
||||
$queryCache = new PhpFilesAdapter('doctrine_queries');
|
||||
$metadataCache = new PhpFilesAdapter('doctrine_metadata');
|
||||
}
|
||||
|
||||
|
||||
$config = new Configuration;
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$config->setMetadataCache($metadataCache);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities']);
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->setQueryCache($queryCache);
|
||||
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
|
||||
$config->setProxyNamespace('MyProject\Proxies');
|
||||
|
||||
|
||||
if ($applicationMode == "development") {
|
||||
$config->setAutoGenerateProxyClasses(true);
|
||||
} else {
|
||||
$config->setAutoGenerateProxyClasses(false);
|
||||
}
|
||||
|
||||
$connectionOptions = array(
|
||||
|
||||
$connection = DriverManager::getConnection([
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => 'database.sqlite'
|
||||
);
|
||||
|
||||
$em = EntityManager::create($connectionOptions, $config);
|
||||
'path' => 'database.sqlite',
|
||||
], $config);
|
||||
|
||||
$em = new EntityManager($connection, $config);
|
||||
|
||||
Doctrine and Caching
|
||||
--------------------
|
||||
|
||||
Doctrine is optimized for working with caches. The main parts in Doctrine
|
||||
that are optimized for caching are the metadata mapping information with
|
||||
the metadata cache and the DQL to SQL conversions with the query cache.
|
||||
These 2 caches require only an absolute minimum of memory yet they heavily
|
||||
improve the runtime performance of Doctrine.
|
||||
|
||||
Doctrine does not bundle its own cache implementation anymore. Instead,
|
||||
the PSR-6 standard interfaces are used to access the cache. In the examples
|
||||
in this documentation, Symfony Cache is used as a reference implementation.
|
||||
|
||||
.. note::
|
||||
|
||||
Do not use Doctrine without a metadata and query cache!
|
||||
Doctrine is optimized for working with caches. The main
|
||||
parts in Doctrine that are optimized for caching are the metadata
|
||||
mapping information with the metadata cache and the DQL to SQL
|
||||
conversions with the query cache. These 2 caches require only an
|
||||
absolute minimum of memory yet they heavily improve the runtime
|
||||
performance of Doctrine. The recommended cache driver to use with
|
||||
Doctrine is `APC <http://www.php.net/apc>`_. APC provides you with
|
||||
an opcode-cache (which is highly recommended anyway) and a very
|
||||
fast in-memory cache storage that you can use for the metadata and
|
||||
query caches as seen in the previous code snippet.
|
||||
|
||||
Configuration Options
|
||||
---------------------
|
||||
@@ -101,29 +111,33 @@ Gets or sets the metadata driver implementation that is used by
|
||||
Doctrine to acquire the object-relational metadata for your
|
||||
classes.
|
||||
|
||||
There are currently 4 available implementations:
|
||||
There are currently 5 available implementations:
|
||||
|
||||
|
||||
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\YamlDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\DriverChain``
|
||||
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver`` (deprecated and will
|
||||
be removed in ``doctrine/orm`` 3.0)
|
||||
- ``Doctrine\ORM\Mapping\Driver\YamlDriver`` (deprecated and will be
|
||||
removed in ``doctrine/orm`` 3.0)
|
||||
|
||||
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
|
||||
@@ -136,30 +150,21 @@ Metadata Cache (***RECOMMENDED***)
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$config->getMetadataCacheImpl();
|
||||
$config->setMetadataCache($cache);
|
||||
$config->getMetadataCache();
|
||||
|
||||
Gets or sets the cache implementation to use for caching metadata
|
||||
information, that is, all the information you supply via
|
||||
Gets or sets the cache adapter to use for caching metadata
|
||||
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
|
||||
``Doctrine\Common\Cache\Cache`` interface.
|
||||
resources. The cache implementation must implement the PSR-6
|
||||
``Psr\Cache\CacheItemPoolInterface`` interface.
|
||||
|
||||
Usage of a metadata cache is highly recommended.
|
||||
|
||||
The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\ApcuCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
|
||||
For development you should use the
|
||||
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
|
||||
per-request basis.
|
||||
For development you should use an array cache like
|
||||
``Symfony\Component\Cache\Adapter\ArrayAdapter``
|
||||
which only caches data on a per-request basis.
|
||||
|
||||
Query Cache (***RECOMMENDED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -167,8 +172,8 @@ Query Cache (***RECOMMENDED***)
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->getQueryCacheImpl();
|
||||
$config->setQueryCache($cache);
|
||||
$config->getQueryCache();
|
||||
|
||||
Gets or sets the cache implementation to use for caching DQL
|
||||
queries, that is, the result of a DQL parsing process that includes
|
||||
@@ -180,18 +185,9 @@ minimal memory usage in your cache).
|
||||
|
||||
Usage of a query cache is highly recommended.
|
||||
|
||||
The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\ApcuCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
|
||||
For development you should use the
|
||||
``Doctrine\Common\Cache\ArrayCache`` which only caches data on a
|
||||
per-request basis.
|
||||
For development you should use an array cache like
|
||||
``Symfony\Component\Cache\Adapter\ArrayAdapter``
|
||||
which only caches data on a per-request basis.
|
||||
|
||||
SQL Logger (***Optional***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -204,10 +200,7 @@ SQL Logger (***Optional***)
|
||||
|
||||
Gets or sets the logger to use for logging all SQL statements
|
||||
executed by Doctrine. The logger class must implement the
|
||||
``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default
|
||||
implementation that logs to the standard output using ``echo`` and
|
||||
``var_dump`` can be found at
|
||||
``Doctrine\DBAL\Logging\EchoSQLLogger``.
|
||||
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
|
||||
|
||||
Auto-generating Proxy Classes (***OPTIONAL***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -223,7 +216,7 @@ option that controls this behavior is:
|
||||
|
||||
Possible values for ``$mode`` are:
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_NEVER``
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
|
||||
|
||||
Never autogenerate a proxy. You will need to generate the proxies
|
||||
manually, for this use the Doctrine Console like so:
|
||||
@@ -239,17 +232,17 @@ methods were added to the entity class that are not yet in the proxy class.
|
||||
In such a case, simply use the Doctrine Console to (re)generate the
|
||||
proxy classes.
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_ALWAYS``
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
|
||||
|
||||
Always generates a new proxy in every request and writes it to disk.
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
|
||||
|
||||
Generate the proxy class when the proxy file does not exist.
|
||||
This strategy causes a file exists call whenever any proxy is
|
||||
used the first time in a request.
|
||||
|
||||
- ``Doctrine\Common\Proxy\AbstractProxyFactory::AUTOGENERATE_EVAL``
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
|
||||
|
||||
Generate the proxy classes and evaluate them on the fly via eval(),
|
||||
avoiding writing the proxies to disk.
|
||||
@@ -259,7 +252,7 @@ In a production environment, it is highly recommended to use
|
||||
AUTOGENERATE_NEVER to allow for optimal performances. The other
|
||||
options are interesting in development environment.
|
||||
|
||||
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
|
||||
``setAutoGenerateProxyClasses`` can accept a boolean
|
||||
value. This is still possible, ``FALSE`` being equivalent to
|
||||
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
|
||||
|
||||
@@ -268,10 +261,10 @@ Development vs Production Configuration
|
||||
|
||||
You should code your Doctrine2 bootstrapping with two different
|
||||
runtime models in mind. There are some serious benefits of using
|
||||
APC or Memcache in production. In development however this will
|
||||
APCu or Memcache in production. In development however this will
|
||||
frequently give you fatal errors, when you change your entities and
|
||||
the cache still keeps the outdated metadata. That is why we
|
||||
recommend the ``ArrayCache`` for development.
|
||||
recommend an array cache for development.
|
||||
|
||||
Furthermore you should have the Auto-generating Proxy Classes
|
||||
option to true in development and to false in production. If this
|
||||
@@ -283,15 +276,13 @@ proxy sets an exclusive file lock which can cause serious
|
||||
performance bottlenecks in systems with regular concurrent
|
||||
requests.
|
||||
|
||||
Connection Options
|
||||
------------------
|
||||
Connection
|
||||
----------
|
||||
|
||||
The ``$connectionOptions`` passed as the first argument to
|
||||
``EntityManager::create()`` has to be either an array or an
|
||||
instance of ``Doctrine\DBAL\Connection``. If an array is passed it
|
||||
is directly passed along to the DBAL Factory
|
||||
``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL
|
||||
configuration is explained in the
|
||||
The ``$connection`` passed as the first argument to he constructor of
|
||||
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
|
||||
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
|
||||
to create such a connection. The DBAL configuration is explained in the
|
||||
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
|
||||
|
||||
Proxy Objects
|
||||
@@ -299,7 +290,7 @@ Proxy Objects
|
||||
|
||||
A proxy object is an object that is put in place or used instead of
|
||||
the "real" object. A proxy object can add behavior to the object
|
||||
being proxied without that object being aware of it. In Doctrine 2,
|
||||
being proxied without that object being aware of it. In ORM,
|
||||
proxy objects are used to realize several features but mainly for
|
||||
transparent lazy-loading.
|
||||
|
||||
@@ -309,7 +300,7 @@ of the objects. This is an essential property as without it there
|
||||
would always be fragile partial objects at the outer edges of your
|
||||
object graph.
|
||||
|
||||
Doctrine 2 implements a variant of the proxy pattern where it
|
||||
Doctrine ORM implements a variant of the proxy pattern where it
|
||||
generates classes that extend your entity classes and adds
|
||||
lazy-loading capabilities to them. Doctrine can then give you an
|
||||
instance of such a proxy class whenever you request an object of
|
||||
@@ -334,8 +325,9 @@ identifier. You could simply do this:
|
||||
$cart->addItem($item);
|
||||
|
||||
Here, we added an Item to a Cart without loading the Item from the
|
||||
database. If you invoke any method on the Item instance, it would
|
||||
fully initialize its state transparently from the database. Here
|
||||
database. If you access any state that isn't yet available in the
|
||||
Item instance, the proxying mechanism would fully initialize the
|
||||
object's state transparently from the database. Here
|
||||
$item is actually an instance of the proxy class that was generated
|
||||
for the Item class but your code does not need to care. In fact it
|
||||
**should not care**. Proxy objects should be transparent to your
|
||||
@@ -411,17 +403,17 @@ be found.
|
||||
Multiple Metadata Sources
|
||||
-------------------------
|
||||
|
||||
When using different components using Doctrine 2 you may end up
|
||||
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');
|
||||
|
||||
@@ -449,22 +441,22 @@ That will be available for all entities without a custom repository class.
|
||||
The default value is ``Doctrine\ORM\EntityRepository``.
|
||||
Any repository class must be a subclass of EntityRepository otherwise you got an ORMException
|
||||
|
||||
Setting up the Console
|
||||
----------------------
|
||||
Ignoring entities (***OPTIONAL***)
|
||||
-----------------------------------
|
||||
|
||||
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:
|
||||
Specifies the Entity FQCNs to ignore.
|
||||
SchemaTool will then skip these (e.g. when comparing schemas).
|
||||
|
||||
.. 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();
|
||||
$config->setSchemaIgnoreClasses([$fqcn]);
|
||||
$config->getSchemaIgnoreClasses();
|
||||
|
||||
|
||||
Setting up the Console
|
||||
----------------------
|
||||
|
||||
Doctrine uses the Symfony Console component for generating the command
|
||||
line interface. You can take a look at the
|
||||
:doc:`tools chapter <../reference/tools>` for inspiration how to setup the cli.
|
||||
|
||||
@@ -1,33 +1,46 @@
|
||||
Annotations Reference
|
||||
=====================
|
||||
|
||||
.. warning::
|
||||
The annotation driver is deprecated and will be removed in version
|
||||
3.0. It is strongly recommended to switch to one of the other
|
||||
mapping drivers.
|
||||
|
||||
.. note::
|
||||
|
||||
To be able to use annotations, you will have to install an extra
|
||||
package called ``doctrine/annotations``.
|
||||
|
||||
You've probably used docblock annotations in some form already,
|
||||
most likely to provide documentation metadata for a tool like
|
||||
``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a
|
||||
tool to embed metadata inside the documentation section which can
|
||||
then be processed by some tool. Doctrine 2 generalizes the concept
|
||||
then be processed by some tool. Doctrine ORM generalizes the concept
|
||||
of docblock annotations so that they can be used for any kind of
|
||||
metadata and so that it is easy to define new docblock annotations.
|
||||
In order to allow more involved annotation values and to reduce the
|
||||
chances of clashes with other docblock annotations, the Doctrine 2
|
||||
chances of clashes with other docblock annotations, the Doctrine ORM
|
||||
docblock annotations feature an alternative syntax that is heavily
|
||||
inspired by the Annotation syntax introduced in Java 5.
|
||||
|
||||
The implementation of these enhanced docblock annotations is
|
||||
located in the ``Doctrine\Common\Annotations`` namespace and
|
||||
therefore part of the Common package. Doctrine 2 docblock
|
||||
annotations support namespaces and nested annotations among other
|
||||
things. The Doctrine 2 ORM defines its own set of docblock
|
||||
annotations for supplying object-relational mapping metadata.
|
||||
The implementation of these enhanced docblock annotations is located in
|
||||
the ``doctrine/annotations`` package, but in the
|
||||
``Doctrine\Common\Annotations`` namespace for backwards compatibility
|
||||
reasons. Note that ``doctrine/annotations`` is not required by Doctrine
|
||||
ORM, and you will need to require that package if you want to use
|
||||
annotations. Doctrine ORM docblock annotations support namespaces and
|
||||
nested annotations among other things. The Doctrine ORM defines its
|
||||
own set of docblock annotations for supplying object-relational mapping
|
||||
metadata.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're not comfortable with the concept of docblock
|
||||
annotations, don't worry, as mentioned earlier Doctrine 2 provides
|
||||
annotations, don't worry, as mentioned earlier Doctrine ORM provides
|
||||
XML and YAML alternatives and you could easily implement your own
|
||||
favourite mechanism for defining ORM metadata.
|
||||
|
||||
In this chapter a reference of every Doctrine 2 Annotation is given
|
||||
In this chapter a reference of every Doctrine ORM Annotation is given
|
||||
with short explanations on their context and usage.
|
||||
|
||||
Index
|
||||
@@ -89,7 +102,7 @@ as part of the lifecycle of the instance variables entity-class.
|
||||
Required attributes:
|
||||
|
||||
- **type**: Name of the Doctrine Type which is converted between PHP
|
||||
and Database representation.
|
||||
and Database representation. Default to ``string`` or :ref:`Type from PHP property type <reference-php-mapping-types>`
|
||||
|
||||
Optional attributes:
|
||||
|
||||
@@ -115,6 +128,18 @@ Optional attributes:
|
||||
|
||||
- **nullable**: Determines if NULL values allowed for this column. 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.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **updatable**: Boolean value to determine if the column should be
|
||||
included when updating the row of the underlying entities table.
|
||||
If not specified, default value is true.
|
||||
|
||||
- **generated**: An enum with the possible values ALWAYS, INSERT, NEVER. Is
|
||||
used after an INSERT or UPDATE statement to determine if the database
|
||||
generated this value and it needs to be fetched using a SELECT statement.
|
||||
|
||||
- **options**: Array of additional options:
|
||||
|
||||
- ``default``: The default value to set for the column if no value
|
||||
@@ -185,6 +210,13 @@ Examples:
|
||||
*/
|
||||
protected $loginCount;
|
||||
|
||||
/**
|
||||
* Generated column
|
||||
* @Column(type="string", name="user_fullname", insertable=false, updatable=false)
|
||||
* MySQL example: full_name char(41) GENERATED ALWAYS AS (concat(firstname,' ',lastname)),
|
||||
*/
|
||||
protected $fullname;
|
||||
|
||||
.. _annref_column_result:
|
||||
|
||||
@ColumnResult
|
||||
@@ -213,7 +245,7 @@ Optional attributes:
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Change Tracking Policy annotation allows to specify how the
|
||||
Doctrine 2 UnitOfWork should detect changes in properties of
|
||||
Doctrine ORM UnitOfWork should detect changes in properties of
|
||||
entities during flush. By default each entity is checked according
|
||||
to a deferred implicit strategy, which means upon flush UnitOfWork
|
||||
compares all the properties of an entity to a previously stored
|
||||
@@ -254,7 +286,7 @@ Example:
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Id
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue(strategy="CUSTOM")
|
||||
* @CustomIdGenerator(class="My\Namespace\MyIdGenerator")
|
||||
@@ -350,7 +382,7 @@ in order to specify that it is an embedded class.
|
||||
|
||||
Required attributes:
|
||||
|
||||
- **class**: The embeddable class
|
||||
- **class**: The embeddable class. You can omit this value if you use a PHP property type instead.
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
@@ -388,7 +420,7 @@ Optional attributes:
|
||||
EntityRepository. Use of repositories for entities is encouraged to keep
|
||||
specialized DQL and SQL operations separated from the Model/Domain
|
||||
Layer.
|
||||
- **readOnly**: (>= 2.1) Specifies that this entity is marked as read only and not
|
||||
- **readOnly**: Specifies that this entity is marked as read only and not
|
||||
considered for change-tracking. Entities of this type can be persisted
|
||||
and removed though.
|
||||
|
||||
@@ -398,11 +430,11 @@ Example:
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity(repositoryClass="MyProject\UserRepository")
|
||||
* @Entity(repositoryClass="MyProject\UserRepository", readOnly=true)
|
||||
*/
|
||||
class User
|
||||
{
|
||||
//...
|
||||
// ...
|
||||
}
|
||||
|
||||
.. _annref_entity_result:
|
||||
@@ -455,7 +487,8 @@ Optional attributes:
|
||||
|
||||
|
||||
- **strategy**: Set the name of the identifier generation strategy.
|
||||
Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE.
|
||||
Valid values are ``AUTO``, ``SEQUENCE``, ``IDENTITY``, ``UUID`` (deprecated), ``CUSTOM`` and ``NONE``, explained
|
||||
in the :ref:`Identifier Generation Strategies <identifier-generation-strategies>` section.
|
||||
If not specified, default value is AUTO.
|
||||
|
||||
Example:
|
||||
@@ -513,7 +546,8 @@ Required attributes:
|
||||
|
||||
|
||||
- **name**: Name of the Index
|
||||
- **columns**: Array of columns.
|
||||
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
|
||||
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
@@ -535,6 +569,19 @@ Basic example:
|
||||
{
|
||||
}
|
||||
|
||||
Basic example using fields:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="ecommerce_products",indexes={@Index(name="search_idx", fields={"name", "email"})})
|
||||
*/
|
||||
class ECommerceProduct
|
||||
{
|
||||
}
|
||||
|
||||
Example with partial indexes:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -715,6 +762,7 @@ Required attributes:
|
||||
|
||||
- **targetEntity**: FQCN of the referenced target entity. Can be the
|
||||
unqualified class name if both classes are in the same namespace.
|
||||
You can omit this value if you use a PHP property type instead.
|
||||
*IMPORTANT:* No leading backslash!
|
||||
|
||||
Optional attributes:
|
||||
@@ -812,7 +860,7 @@ The @MappedSuperclass annotation cannot be used in conjunction with
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **repositoryClass**: (>= 2.2) Specifies the FQCN of a subclass of the EntityRepository.
|
||||
- **repositoryClass**: Specifies the FQCN of a subclass of the EntityRepository.
|
||||
That will be inherited for all subclasses of that Mapped Superclass.
|
||||
|
||||
Example:
|
||||
@@ -840,6 +888,11 @@ Example:
|
||||
|
||||
@NamedNativeQuery
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
Named Native Queries are deprecated as of version 2.9 and will be removed in ORM 3.0
|
||||
|
||||
Is used to specify a native SQL named query.
|
||||
The NamedNativeQuery annotation can be applied to an entity or mapped superclass.
|
||||
|
||||
@@ -923,6 +976,7 @@ Required attributes:
|
||||
|
||||
- **targetEntity**: FQCN of the referenced target entity. Can be the
|
||||
unqualified class name if both classes are in the same namespace.
|
||||
When typed properties are used it is inherited from PHP type.
|
||||
*IMPORTANT:* No leading backslash!
|
||||
|
||||
Optional attributes:
|
||||
@@ -1231,7 +1285,7 @@ Optional attributes:
|
||||
|
||||
- **indexes**: Array of @Index annotations
|
||||
- **uniqueConstraints**: Array of @UniqueConstraint annotations.
|
||||
- **schema**: (>= 2.5) Name of the schema the table lies in.
|
||||
- **schema**: Name of the schema the table lies in.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1263,7 +1317,8 @@ Required attributes:
|
||||
|
||||
|
||||
- **name**: Name of the Index
|
||||
- **columns**: Array of columns.
|
||||
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
|
||||
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
@@ -1285,6 +1340,19 @@ Basic example:
|
||||
{
|
||||
}
|
||||
|
||||
Basic example using fields:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="ecommerce_products",uniqueConstraints={@UniqueConstraint(name="search_idx", fields={"name", "email"})})
|
||||
*/
|
||||
class ECommerceProduct
|
||||
{
|
||||
}
|
||||
|
||||
Example with partial indexes:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -1319,4 +1387,3 @@ Example:
|
||||
* @Version
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
|
||||
@@ -2,29 +2,29 @@ Architecture
|
||||
============
|
||||
|
||||
This chapter gives an overview of the overall architecture,
|
||||
terminology and constraints of Doctrine 2. It is recommended to
|
||||
terminology and constraints of Doctrine ORM. It is recommended to
|
||||
read this chapter carefully.
|
||||
|
||||
Using an Object-Relational Mapper
|
||||
---------------------------------
|
||||
|
||||
As the term ORM already hints at, Doctrine 2 aims to simplify the
|
||||
As the term ORM already hints at, Doctrine ORM aims to simplify the
|
||||
translation between database rows and the PHP object model. The
|
||||
primary use case for Doctrine are therefore applications that
|
||||
utilize the Object-Oriented Programming Paradigm. For applications
|
||||
that do not primarily work with objects Doctrine 2 is not suited very
|
||||
that do not primarily work with objects Doctrine ORM is not suited very
|
||||
well.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
|
||||
Doctrine ORM requires a minimum of PHP 7.1. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine 2 Packages
|
||||
Doctrine ORM Packages
|
||||
-------------------
|
||||
|
||||
Doctrine 2 is divided into three main packages.
|
||||
Doctrine ORM is divided into three main packages.
|
||||
|
||||
- Common
|
||||
- DBAL (includes Common)
|
||||
@@ -66,33 +66,21 @@ The root namespace of the ORM package is ``Doctrine\ORM``.
|
||||
Terminology
|
||||
-----------
|
||||
|
||||
.. _terminology_entities:
|
||||
|
||||
Entities
|
||||
~~~~~~~~
|
||||
|
||||
An entity is a lightweight, persistent domain object. An entity can
|
||||
be any regular PHP class observing the following restrictions:
|
||||
|
||||
|
||||
- An entity class must not be final or contain final methods.
|
||||
- All persistent properties/field of any entity class should
|
||||
always be private or protected, otherwise lazy-loading might not
|
||||
work as expected. In case you serialize entities (for example Session)
|
||||
properties should be protected (See Serialize section below).
|
||||
- An entity class must not implement ``__clone`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
- An entity class must not implement ``__wakeup`` or
|
||||
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
Also consider implementing
|
||||
`Serializable <http://php.net/manual/en/class.serializable.php>`_
|
||||
instead.
|
||||
- An entity class must not be final nor read-only but
|
||||
it may contain final methods or read-only properties.
|
||||
- Any two entity classes in a class hierarchy that inherit
|
||||
directly or indirectly from one another must not have a mapped
|
||||
property with the same name. That is, if B inherits from A then B
|
||||
must not have a mapped field with the same name as an already
|
||||
mapped field that is inherited from A.
|
||||
- An entity cannot make use of func_get_args() to implement variable parameters.
|
||||
Generated proxies do not support this for performance reasons and your code might
|
||||
actually fail to work when violating this restriction.
|
||||
|
||||
Entities support inheritance, polymorphic associations, and
|
||||
polymorphic queries. Both abstract and concrete classes can be
|
||||
@@ -152,22 +140,19 @@ Serializing entities
|
||||
|
||||
Serializing entities can be problematic and is not really
|
||||
recommended, at least not as long as an entity instance still holds
|
||||
references to proxy objects or is still managed by an
|
||||
EntityManager. If you intend to serialize (and unserialize) entity
|
||||
instances that still hold references to proxy objects you may run
|
||||
into problems with private properties because of technical
|
||||
limitations. Proxy objects implement ``__sleep`` and it is not
|
||||
possible for ``__sleep`` to return names of private properties in
|
||||
parent classes. On the other hand it is not a solution for proxy
|
||||
objects to implement ``Serializable`` because Serializable does not
|
||||
work well with any potential cyclic object references (at least we
|
||||
did not find a way yet, if you did, please contact us).
|
||||
references to proxy objects or is still managed by an EntityManager.
|
||||
By default, serializing proxy objects does not initialize them. On
|
||||
unserialization, resulting objects are detached from the entity
|
||||
manager and cannot be initialiazed anymore. You can implement the
|
||||
``__serialize()`` method if you want to change that behavior, but
|
||||
then you need to ensure that you won't generate large serialized
|
||||
object graphs and take care of circular associations.
|
||||
|
||||
The EntityManager
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``EntityManager`` class is a central access point to the ORM
|
||||
functionality provided by Doctrine 2. The ``EntityManager`` API is
|
||||
The ``EntityManager`` class is a central access point to the
|
||||
functionality provided by Doctrine ORM. The ``EntityManager`` API is
|
||||
used to manage the persistence of your objects and to query for
|
||||
persistent objects.
|
||||
|
||||
@@ -184,12 +169,14 @@ in well defined units of work. Work with your objects and modify
|
||||
them as usual and when you're done call ``EntityManager#flush()``
|
||||
to make your changes persistent.
|
||||
|
||||
.. _unit-of-work:
|
||||
|
||||
The Unit of Work
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a
|
||||
typical implementation of the
|
||||
`Unit of Work pattern <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
|
||||
`Unit of Work pattern <https://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
|
||||
to keep track of all the things that need to be done the next time
|
||||
``flush`` is invoked. You usually do not directly interact with a
|
||||
``UnitOfWork`` but with the ``EntityManager`` instead.
|
||||
|
||||
@@ -22,9 +22,9 @@ One tip for working with relations is to read the relation from left to right, w
|
||||
- ManyToOne - Many instances of the current Entity refer to One instance of the referred Entity.
|
||||
- OneToOne - One instance of the current Entity refers to One instance of the referred Entity.
|
||||
|
||||
See below for all the possible relations.
|
||||
See below for all the possible relations.
|
||||
|
||||
An association is considered to be unidirectional if only one side of the association has
|
||||
An association is considered to be unidirectional if only one side of the association has
|
||||
a property referring to the other side.
|
||||
|
||||
To gain a full understanding of associations you should also read about :doc:`owning and
|
||||
@@ -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:
|
||||
@@ -182,13 +225,41 @@ Here is a one-to-one relationship between a ``Customer`` and a
|
||||
``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
|
||||
it is bidirectional.
|
||||
|
||||
Here we see the ``mappedBy`` and ``inversedBy`` annotations for the first time.
|
||||
Here we see the ``mappedBy`` and ``inversedBy`` attributes for the first time.
|
||||
They are used to tell Doctrine which property on the other side refers to the
|
||||
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;
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -259,6 +330,7 @@ Generated MySQL Schema:
|
||||
CREATE TABLE Cart (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
customer_id INT DEFAULT NULL,
|
||||
UNIQUE INDEX UNIQ_BA388B79395C3F3 (customer_id),
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
CREATE TABLE Customer (
|
||||
@@ -280,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;
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -325,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;
|
||||
@@ -336,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() {
|
||||
@@ -355,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;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -420,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 */
|
||||
@@ -429,14 +565,15 @@ The following example sets up such a unidirectional one-to-many association:
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many User have Many Phonenumbers.
|
||||
* Many Users have Many Phonenumbers.
|
||||
* @ManyToMany(targetEntity="Phonenumber")
|
||||
* @JoinTable(name="users_phonenumbers",
|
||||
* 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()
|
||||
{
|
||||
@@ -522,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 */
|
||||
@@ -532,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() {
|
||||
@@ -593,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 */
|
||||
@@ -608,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;
|
||||
|
||||
// ...
|
||||
|
||||
@@ -694,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 */
|
||||
@@ -706,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();
|
||||
@@ -723,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();
|
||||
@@ -805,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;
|
||||
@@ -816,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;
|
||||
}
|
||||
@@ -846,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();
|
||||
}
|
||||
|
||||
// ...
|
||||
@@ -909,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
|
||||
|
||||
@@ -936,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
|
||||
/**
|
||||
@@ -944,7 +1197,7 @@ mapping:
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private $shipment;
|
||||
private Shipment|null $shipment = null;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
@@ -972,15 +1225,30 @@ 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;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -1003,12 +1271,30 @@ 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
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
@@ -1016,9 +1302,10 @@ 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;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -1061,6 +1348,84 @@ join columns default to the simple, unqualified class name of the
|
||||
targeted class followed by "\_id". The referencedColumnName always
|
||||
defaults to "id", just as in one-to-one or many-to-one mappings.
|
||||
|
||||
Additionally, when using typed properties with Doctrine 2.9 or newer
|
||||
you can skip ``targetEntity`` in ``ManyToOne`` and ``OneToOne``
|
||||
associations as they will be set based on type. Also ``nullable``
|
||||
attribute on ``JoinColumn`` will be inherited from PHP type. So that:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[OneToOne]
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/** @OneToOne */
|
||||
private Shipment $shipment;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipment" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipment: ~
|
||||
|
||||
Is essentially the same as following:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
* One Product has One Shipment.
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id", nullable=false)
|
||||
*/
|
||||
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>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipment" target-entity="Shipment">
|
||||
<join-column name="shipment_id" referenced-column-name="id" nulable=false />
|
||||
</one-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipment:
|
||||
targetEntity: Shipment
|
||||
joinColumn:
|
||||
name: shipment_id
|
||||
referencedColumnName: id
|
||||
nullable: false
|
||||
|
||||
If you accept these defaults, you can reduce the mapping code to a
|
||||
minimum.
|
||||
|
||||
@@ -1098,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;
|
||||
}
|
||||
|
||||
1151
docs/en/reference/attributes-reference.rst
Normal file
1151
docs/en/reference/attributes-reference.rst
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,17 +14,11 @@ After working through this guide you should know:
|
||||
Mapping of associations will be covered in the next chapter on
|
||||
:doc:`Association Mapping <association-mapping>`.
|
||||
|
||||
Guide Assumptions
|
||||
-----------------
|
||||
|
||||
You should have already :doc:`installed and configure <configuration>`
|
||||
Doctrine.
|
||||
|
||||
Creating Classes for the Database
|
||||
---------------------------------
|
||||
|
||||
Every PHP object that you want to save in the database using Doctrine
|
||||
is called an "Entity". The term "Entity" describes objects
|
||||
is called an *Entity*. The term "Entity" describes objects
|
||||
that have an identity over many independent requests. This identity is
|
||||
usually achieved by assigning a unique identifier to an entity.
|
||||
In this tutorial the following ``Message`` PHP class will serve as the
|
||||
@@ -50,19 +44,22 @@ that describes your entity.
|
||||
Doctrine provides several different ways to specify object-relational
|
||||
mapping metadata:
|
||||
|
||||
- :doc:`Docblock Annotations <annotations-reference>`
|
||||
- :doc:`Attributes <attributes-reference>`
|
||||
- :doc:`XML <xml-mapping>`
|
||||
- :doc:`YAML <yaml-mapping>`
|
||||
- :doc:`PHP code <php-mapping>`
|
||||
- :doc:`Docblock Annotations <annotations-reference>` (deprecated and
|
||||
will be removed in ``doctrine/orm`` 3.0)
|
||||
- :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::
|
||||
|
||||
All metadata drivers perform equally. Once the metadata of a class has been
|
||||
read from the source (annotations, xml or yaml) it is stored in an instance
|
||||
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are
|
||||
read from the source (attributes, annotations, XML, etc.) it is stored in an instance
|
||||
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class which are
|
||||
stored in the metadata cache. If you're not using a metadata cache (not
|
||||
recommended!) then the XML driver is the fastest.
|
||||
|
||||
@@ -70,13 +67,26 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
#[Entity]
|
||||
class Message
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
/** @Entity */
|
||||
class Message
|
||||
{
|
||||
//...
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -99,16 +109,32 @@ You can change this by configuring information about the table:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
#[Entity]
|
||||
#[Table(name: 'message')]
|
||||
class Message
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="message")
|
||||
*/
|
||||
class Message
|
||||
{
|
||||
//...
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -131,19 +157,38 @@ Now the class ``Message`` will be saved and fetched from the table ``message``.
|
||||
Property Mapping
|
||||
----------------
|
||||
|
||||
The next step after marking a PHP class as an entity is mapping its properties
|
||||
to columns in a table.
|
||||
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::
|
||||
|
||||
.. code-block:: php
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
#[Entity]
|
||||
class Message
|
||||
{
|
||||
#[Column(type: Types::INTEGER)]
|
||||
private $id;
|
||||
#[Column(length: 140)]
|
||||
private $text;
|
||||
#[Column(name: 'posted_at', type: Types::DATETIME)]
|
||||
private $postedAt;
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
|
||||
/** @Entity */
|
||||
class Message
|
||||
{
|
||||
@@ -179,38 +224,78 @@ default.
|
||||
column: posted_at
|
||||
|
||||
When we don't explicitly specify a column name via the ``name`` option, Doctrine
|
||||
assumes the field name is also the column name. This means that:
|
||||
assumes the field name is also the column name. So in this example:
|
||||
|
||||
* the ``id`` property will map to the column ``id`` using the type ``integer``;
|
||||
* the ``text`` property will map to the column ``text`` with the default mapping type ``string``;
|
||||
* the ``postedAt`` property will map to the ``posted_at`` column with the ``datetime`` type.
|
||||
|
||||
The Column annotation has some more attributes. Here is a complete
|
||||
list:
|
||||
Here is a complete list of ``Column``s attributes (all optional):
|
||||
|
||||
- ``type``: (optional, defaults to 'string') The mapping type to
|
||||
use for the column.
|
||||
- ``name``: (optional, defaults to field name) The name of the
|
||||
column in the database.
|
||||
- ``length``: (optional, default 255) The length of the column in
|
||||
the database. (Applies only if a string-valued column is used).
|
||||
- ``unique``: (optional, default FALSE) Whether the column is a
|
||||
unique key.
|
||||
- ``nullable``: (optional, default FALSE) Whether the database
|
||||
column is nullable.
|
||||
- ``precision``: (optional, default 0) The precision for a decimal
|
||||
(exact numeric) column (applies only for decimal column),
|
||||
- ``type`` (default: 'string'): The mapping type to use for the column.
|
||||
- ``name`` (default: name of property): The name of the column in the database.
|
||||
- ``length`` (default: 255): The length of the column in the database.
|
||||
Applies only if a string-valued column is used.
|
||||
- ``unique`` (default: ``false``): Whether the column is a unique key.
|
||||
- ``nullable`` (default: ``false``): Whether the column is nullable.
|
||||
- ``insertable`` (default: ``true``): Whether the column should be inserted.
|
||||
- ``updatable`` (default: ``true``): Whether the column should be updated.
|
||||
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into.
|
||||
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
|
||||
(applies only for decimal column),
|
||||
which is the maximum number of digits that are stored for the values.
|
||||
- ``scale``: (optional, default 0) The scale for a decimal (exact
|
||||
- ``scale`` (default: 0): The scale for a decimal (exact
|
||||
numeric) column (applies only for decimal column), which represents
|
||||
the number of digits to the right of the decimal point and must
|
||||
not be greater than *precision*.
|
||||
- ``columnDefinition``: (optional) Allows to define a custom
|
||||
not be greater than ``precision``.
|
||||
- ``columnDefinition``: Allows to define a custom
|
||||
DDL snippet that is used to create the column. Warning: This normally
|
||||
confuses the SchemaTool to always detect the column as changed.
|
||||
- ``options``: (optional) Key-value pairs of options that get passed
|
||||
confuses the :doc:`SchemaTool <tools>` to always detect the column as changed.
|
||||
- ``options``: Key-value pairs of options that get passed
|
||||
to the underlying database platform when generating DDL statements.
|
||||
|
||||
.. _reference-php-mapping-types:
|
||||
|
||||
PHP Types Mapping
|
||||
_________________
|
||||
|
||||
.. versionadded:: 2.9
|
||||
|
||||
The column types can be inferred automatically from PHP's property types.
|
||||
However, when the property type is nullable this has no effect on the ``nullable`` Column attribute.
|
||||
|
||||
These are the "automatic" mapping rules:
|
||||
|
||||
+-----------------------+-------------------------------+
|
||||
| PHP property type | Doctrine column type |
|
||||
+=======================+===============================+
|
||||
| ``DateInterval`` | ``Types::DATEINTERVAL`` |
|
||||
+-----------------------+-------------------------------+
|
||||
| ``DateTime`` | ``Types::DATETIME_MUTABLE`` |
|
||||
+-----------------------+-------------------------------+
|
||||
| ``DateTimeImmutable`` | ``Types::DATETIME_IMMUTABLE`` |
|
||||
+-----------------------+-------------------------------+
|
||||
| ``array`` | ``Types::JSON`` |
|
||||
+-----------------------+-------------------------------+
|
||||
| ``bool`` | ``Types::BOOLEAN`` |
|
||||
+-----------------------+-------------------------------+
|
||||
| ``float`` | ``Types::FLOAT`` |
|
||||
+-----------------------+-------------------------------+
|
||||
| ``int`` | ``Types::INTEGER`` |
|
||||
+-----------------------+-------------------------------+
|
||||
| Any other type | ``Types::STRING`` |
|
||||
+-----------------------+-------------------------------+
|
||||
|
||||
As of version 2.11 Doctrine can also automatically map typed properties using a
|
||||
PHP 8.1 enum to set the right ``type`` and ``enumType``.
|
||||
|
||||
.. versionadded:: 2.14
|
||||
|
||||
Since version 2.14 you can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
|
||||
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
|
||||
|
||||
:doc:`Read more about TypedFieldMapper <typedfieldmapper>`.
|
||||
|
||||
.. _reference-mapping-types:
|
||||
|
||||
Doctrine Mapping Types
|
||||
@@ -270,7 +355,7 @@ A cookbook article shows how to define :doc:`your own custom mapping types
|
||||
.. warning::
|
||||
|
||||
All Date types assume that you are exclusively using the default timezone
|
||||
set by `date_default_timezone_set() <http://php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
set by `date_default_timezone_set() <https://php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||
or by the php.ini configuration ``date.timezone``. Working with
|
||||
different timezones will cause troubles and unexpected behavior.
|
||||
|
||||
@@ -284,12 +369,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
|
||||
@@ -299,8 +395,8 @@ annotation.
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
//...
|
||||
private int|null $id = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -326,11 +422,13 @@ 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
|
||||
database vendor prefers: AUTO_INCREMENT with MySQL, sequences with PostgreSQL
|
||||
and Oracle and so on.
|
||||
|
||||
.. _identifier-generation-strategies:
|
||||
|
||||
Identifier Generation Strategies
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -355,16 +453,13 @@ Here is the list of possible generation strategies:
|
||||
strategy does currently not provide full portability and is
|
||||
supported by the following platforms: MySQL/SQLite/SQL Anywhere
|
||||
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
|
||||
- ``UUID``: Tells Doctrine to use the built-in Universally Unique Identifier
|
||||
generator. This strategy provides full portability.
|
||||
- ``TABLE``: Tells Doctrine to use a separate table for ID
|
||||
generation. This strategy provides full portability.
|
||||
***This strategy is not yet implemented!***
|
||||
- ``UUID`` (deprecated): Tells Doctrine to use the built-in Universally
|
||||
Unique Identifier generator. This strategy provides full portability.
|
||||
- ``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
|
||||
@@ -376,7 +471,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
|
||||
@@ -386,8 +493,8 @@ 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;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
@@ -423,7 +530,7 @@ performance of Doctrine. The allocationSize specifies by how much
|
||||
values the sequence is incremented whenever the next value is
|
||||
retrieved. If this is larger than 1 (one) Doctrine can generate
|
||||
identifier values for the allocationSizes amount of entities. In
|
||||
the above example with ``allocationSize=100`` Doctrine 2 would only
|
||||
the above example with ``allocationSize=100`` Doctrine ORM would only
|
||||
need to access the sequence once to generate the identifiers for
|
||||
100 new entities.
|
||||
|
||||
@@ -451,11 +558,12 @@ need to access the sequence once to generate the identifiers for
|
||||
Composite Keys
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
With Doctrine 2 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>`.
|
||||
@@ -471,7 +579,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
|
||||
@@ -484,15 +593,11 @@ according to the used database platform.
|
||||
|
||||
.. _reference-basic-mapping-custom-mapping-types:
|
||||
|
||||
.. versionadded: 2.3
|
||||
|
||||
For more control over column quoting the ``Doctrine\ORM\Mapping\QuoteStrategy`` interface
|
||||
was introduced in 2.3. It is invoked for every column, table, alias and other
|
||||
was introduced in ORM. It is invoked for every column, table, alias and other
|
||||
SQL names. You can implement the QuoteStrategy and set it by calling
|
||||
``Doctrine\ORM\Configuration#setQuoteStrategy()``.
|
||||
|
||||
.. versionadded: 2.4
|
||||
|
||||
The ANSI Quote Strategy was added, which assumes quoting is not necessary for any SQL name.
|
||||
You can use it with the following code:
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ internally but also mean more work during ``flush``.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
}
|
||||
$em->flush(); //Persist objects that did not make up an entire batch
|
||||
$em->flush(); // Persist objects that did not make up an entire batch
|
||||
$em->clear();
|
||||
|
||||
Bulk Updates
|
||||
@@ -75,7 +75,7 @@ Iterating results
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
An alternative solution for bulk updates is to use the
|
||||
``Query#iterate()`` facility to iterate over the query results step
|
||||
``Query#toIterable()`` facility to iterate over the query results step
|
||||
by step instead of loading the whole result into memory at once.
|
||||
The following example shows how to do this, combining the iteration
|
||||
with the batching strategy that was already used for bulk inserts:
|
||||
@@ -86,16 +86,14 @@ with the batching strategy that was already used for bulk inserts:
|
||||
$batchSize = 20;
|
||||
$i = 1;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach ($iterableResult as $row) {
|
||||
$user = $row[0];
|
||||
foreach ($q->toIterable() as $user) {
|
||||
$user->increaseCredit();
|
||||
$user->calculateNewBonuses();
|
||||
++$i;
|
||||
if (($i % $batchSize) === 0) {
|
||||
$em->flush(); // Executes all updates.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
$em->flush();
|
||||
|
||||
@@ -137,7 +135,7 @@ Iterating results
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
An alternative solution for bulk deletes is to use the
|
||||
``Query#iterate()`` facility to iterate over the query results step
|
||||
``Query#toIterable()`` facility to iterate over the query results step
|
||||
by step instead of loading the whole result into memory at once.
|
||||
The following example shows how to do this:
|
||||
|
||||
@@ -147,14 +145,13 @@ The following example shows how to do this:
|
||||
$batchSize = 20;
|
||||
$i = 1;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
while (($row = $iterableResult->next()) !== false) {
|
||||
$em->remove($row[0]);
|
||||
foreach($q->toIterable() as $row) {
|
||||
$em->remove($row);
|
||||
++$i;
|
||||
if (($i % $batchSize) === 0) {
|
||||
$em->flush(); // Executes all deletions.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
$em->flush();
|
||||
|
||||
@@ -168,20 +165,18 @@ The following example shows how to do this:
|
||||
Iterating Large Results for Data-Processing
|
||||
-------------------------------------------
|
||||
|
||||
You can use the ``iterate()`` method just to iterate over a large
|
||||
result and no UPDATE or DELETE intention. The ``IterableResult``
|
||||
instance returned from ``$query->iterate()`` implements the
|
||||
Iterator interface so you can process a large result without memory
|
||||
You can use the ``toIterable()`` method just to iterate over a large
|
||||
result and no UPDATE or DELETE intention. ``$query->toIterable()`` returns ``iterable``
|
||||
so you can process a large result without memory
|
||||
problems using the following approach:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
|
||||
$iterableResult = $q->iterate();
|
||||
foreach ($iterableResult as $row) {
|
||||
// do stuff with the data in the row, $row[0] is always the object
|
||||
|
||||
foreach ($q->toIterable() as $row) {
|
||||
// do stuff with the data in the row
|
||||
|
||||
// detach from Doctrine, so that it can be Garbage-Collected immediately
|
||||
$this->_em->detach($row[0]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,266 +1,14 @@
|
||||
Caching
|
||||
=======
|
||||
|
||||
Doctrine provides cache drivers in the ``Common`` package for some
|
||||
of the most popular caching implementations such as APC, Memcache
|
||||
and Xcache. We also provide an ``ArrayCache`` driver which stores
|
||||
the data in a PHP array. Obviously, when using ``ArrayCache``, the
|
||||
cache does not persist between requests, but this is useful for
|
||||
testing in a development environment.
|
||||
The Doctrine ORM package can leverage cache adapters implementing the PSR-6
|
||||
standard to allow you to improve the performance of various aspects of
|
||||
Doctrine by simply making some additional configurations and method calls.
|
||||
|
||||
Cache Drivers
|
||||
-------------
|
||||
.. _types-of-caches:
|
||||
|
||||
The cache drivers follow a simple interface that is defined in
|
||||
``Doctrine\Common\Cache\Cache``. All the cache drivers extend a
|
||||
base class ``Doctrine\Common\Cache\CacheProvider`` which implements
|
||||
this interface.
|
||||
|
||||
The interface defines the following public methods for you to implement:
|
||||
|
||||
|
||||
- fetch($id) - Fetches an entry from the cache
|
||||
- contains($id) - Test if an entry exists in the cache
|
||||
- save($id, $data, $lifeTime = false) - Puts data into the cache for x seconds. 0 = infinite time
|
||||
- delete($id) - Deletes a cache entry
|
||||
|
||||
Each driver extends the ``CacheProvider`` class which defines a few
|
||||
abstract protected methods that each of the drivers must
|
||||
implement:
|
||||
|
||||
|
||||
- doFetch($id)
|
||||
- doContains($id)
|
||||
- doSave($id, $data, $lifeTime = false)
|
||||
- doDelete($id)
|
||||
|
||||
The public methods ``fetch()``, ``contains()`` etc. use the
|
||||
above protected methods which are implemented by the drivers. The
|
||||
code is organized this way so that the protected methods in the
|
||||
drivers do the raw interaction with the cache implementation and
|
||||
the ``CacheProvider`` can build custom functionality on top of
|
||||
these methods.
|
||||
|
||||
This documentation does not cover every single cache driver included
|
||||
with Doctrine. For an up-to-date-list, see the
|
||||
`cache directory on GitHub <https://github.com/doctrine/cache/tree/master/lib/Doctrine/Common/Cache>`_.
|
||||
|
||||
PhpFileCache
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The preferred cache driver for metadata and query caches is ``PhpFileCache``.
|
||||
This driver serializes cache items and writes them to a file. This allows for
|
||||
opcode caching to be used and provides high performance in most scenarios.
|
||||
|
||||
In order to use the ``PhpFileCache`` driver it must be able to write to
|
||||
a directory.
|
||||
|
||||
Below is an example of how to use the ``PhpFileCache`` driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
The PhpFileCache is not distributed across multiple machines if you are running
|
||||
your application in a distributed setup. This is ok for the metadata and query
|
||||
cache but is not a good approach for the result cache.
|
||||
|
||||
Memcache
|
||||
~~~~~~~~
|
||||
|
||||
In order to use the Memcache cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about Memcache
|
||||
`on the PHP website <http://php.net/memcache>`_. It will
|
||||
give you a little background information about what it is and how
|
||||
you can use it as well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the Memcache cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$memcache = new Memcache();
|
||||
$memcache->connect('memcache_host', 11211);
|
||||
|
||||
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache();
|
||||
$cacheDriver->setMemcache($memcache);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Memcached
|
||||
~~~~~~~~~
|
||||
|
||||
Memcached is a more recent and complete alternative extension to
|
||||
Memcache.
|
||||
|
||||
In order to use the Memcached cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about Memcached
|
||||
`on the PHP website <http://php.net/memcached>`_. It will
|
||||
give you a little background information about what it is and how
|
||||
you can use it as well as how to install it.
|
||||
|
||||
Below is a simple example of how you could use the Memcached cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$memcached = new Memcached();
|
||||
$memcached->addServer('memcache_host', 11211);
|
||||
|
||||
$cacheDriver = new \Doctrine\Common\Cache\MemcachedCache();
|
||||
$cacheDriver->setMemcached($memcached);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Redis
|
||||
~~~~~
|
||||
|
||||
In order to use the Redis cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about what Redis is
|
||||
`from here <http://redis.io/>`_. Also check
|
||||
`A PHP extension for Redis <https://github.com/nicolasff/phpredis/>`_ for how you can use
|
||||
and install the Redis PHP extension.
|
||||
|
||||
Below is a simple example of how you could use the Redis cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$redis = new Redis();
|
||||
$redis->connect('redis_host', 6379);
|
||||
|
||||
$cacheDriver = new \Doctrine\Common\Cache\RedisCache();
|
||||
$cacheDriver->setRedis($redis);
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Using Cache Drivers
|
||||
-------------------
|
||||
|
||||
In this section we'll describe how you can fully utilize the API of
|
||||
the cache drivers to save data to a cache, check if some cached data
|
||||
exists, fetch the cached data and delete the cached data. We'll use the
|
||||
``ArrayCache`` implementation as our example here.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ArrayCache();
|
||||
|
||||
Saving
|
||||
~~~~~~
|
||||
|
||||
Saving some data to the cache driver is as simple as using the
|
||||
``save()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
The ``save()`` method accepts three arguments which are described
|
||||
below:
|
||||
|
||||
|
||||
- ``$id`` - The cache id
|
||||
- ``$data`` - The cache entry/data.
|
||||
- ``$lifeTime`` - The lifetime. If != false, sets a specific
|
||||
lifetime for this cache entry (null => infinite lifeTime).
|
||||
|
||||
You can save any type of data whether it be a string, array,
|
||||
object, etc.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$array = array(
|
||||
'key1' => 'value1',
|
||||
'key2' => 'value2'
|
||||
);
|
||||
$cacheDriver->save('my_array', $array);
|
||||
|
||||
Checking
|
||||
~~~~~~~~
|
||||
|
||||
Checking whether cached data exists is very simple: just use the
|
||||
``contains()`` method. It accepts a single argument which is the ID
|
||||
of the cache entry.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
if ($cacheDriver->contains('cache_id')) {
|
||||
echo 'cache exists';
|
||||
} else {
|
||||
echo 'cache does not exist';
|
||||
}
|
||||
|
||||
Fetching
|
||||
~~~~~~~~
|
||||
|
||||
Now if you want to retrieve some cache entry you can use the
|
||||
``fetch()`` method. It also accepts a single argument just like
|
||||
``contains()`` which is again the ID of the cache entry.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$array = $cacheDriver->fetch('my_array');
|
||||
|
||||
Deleting
|
||||
~~~~~~~~
|
||||
|
||||
As you might guess, deleting is just as easy as saving, checking
|
||||
and fetching. You can delete by an individual ID, or you can
|
||||
delete all entries.
|
||||
|
||||
By Cache ID
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver->delete('my_array');
|
||||
|
||||
All
|
||||
^^^
|
||||
|
||||
If you simply want to delete all cache entries you can do so with
|
||||
the ``deleteAll()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$deleted = $cacheDriver->deleteAll();
|
||||
|
||||
Namespaces
|
||||
~~~~~~~~~~
|
||||
|
||||
If you heavily use caching in your application and use it in
|
||||
multiple parts of your application, or use it in different
|
||||
applications on the same server you may have issues with cache
|
||||
naming collisions. This can be worked around by using namespaces.
|
||||
You can set the namespace a cache driver should use by using the
|
||||
``setNamespace()`` method.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver->setNamespace('my_namespace_');
|
||||
|
||||
.. _integrating-with-the-orm:
|
||||
|
||||
Integrating with the ORM
|
||||
------------------------
|
||||
|
||||
The Doctrine ORM package is tightly integrated with the cache
|
||||
drivers to allow you to improve the performance of various aspects of
|
||||
Doctrine by simply making some additional configurations and
|
||||
method calls.
|
||||
Types of Caches
|
||||
---------------
|
||||
|
||||
Query Cache
|
||||
~~~~~~~~~~~
|
||||
@@ -276,28 +24,27 @@ use on your ORM configuration.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter('doctrine_queries');
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setQueryCacheImpl($cacheDriver);
|
||||
$config->setQueryCache($cache);
|
||||
|
||||
Result Cache
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The result cache can be used to cache the results of your queries
|
||||
so that we don't have to query the database or hydrate the data
|
||||
again after the first time. You just need to configure the result
|
||||
cache implementation.
|
||||
so that we don't have to query the database again after the first time.
|
||||
You just need to configure the result cache implementation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
|
||||
'doctrine_results',
|
||||
0,
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setResultCacheImpl($cacheDriver);
|
||||
$config->setResultCache($cache);
|
||||
|
||||
Now when you're executing DQL queries you can configure them to use
|
||||
the result cache.
|
||||
@@ -306,7 +53,7 @@ the result cache.
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('select u from \Entities\User u');
|
||||
$query->useResultCache(true);
|
||||
$query->enableResultCache();
|
||||
|
||||
You can also configure an individual query to use a different
|
||||
result cache driver.
|
||||
@@ -314,22 +61,23 @@ result cache driver.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
|
||||
'doctrine_results',
|
||||
0,
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$query->setResultCacheDriver($cacheDriver);
|
||||
$query->setResultCache($cache);
|
||||
|
||||
.. note::
|
||||
|
||||
Setting the result cache driver on the query will
|
||||
automatically enable the result cache for the query. If you want to
|
||||
disable it pass false to ``useResultCache()``.
|
||||
disable it use ``disableResultCache()``.
|
||||
|
||||
::
|
||||
|
||||
<?php
|
||||
$query->useResultCache(false);
|
||||
$query->disableResultCache();
|
||||
|
||||
|
||||
If you want to set the time the cache has to live you can use the
|
||||
@@ -350,19 +98,20 @@ yourself with the ``setResultCacheId()`` method.
|
||||
$query->setResultCacheId('my_custom_id');
|
||||
|
||||
You can also set the lifetime and cache ID by passing the values as
|
||||
the second and third argument to ``useResultCache()``.
|
||||
the first and second argument to ``enableResultCache()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->useResultCache(true, 3600, 'my_custom_id');
|
||||
$query->enableResultCache(3600, 'my_custom_id');
|
||||
|
||||
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.
|
||||
@@ -370,11 +119,13 @@ first.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\PhpFileCache(
|
||||
$cache = \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
|
||||
'doctrine_metadata',
|
||||
0,
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setMetadataCacheImpl($cacheDriver);
|
||||
$config->setMetadataCache($cache);
|
||||
|
||||
Now the metadata information will only be parsed once and stored in
|
||||
the cache driver.
|
||||
@@ -424,30 +175,15 @@ requested many times in a single PHP request. Even though this data
|
||||
may be stored in a fast memory cache, often that cache is over a
|
||||
network link leading to sizable network traffic.
|
||||
|
||||
The ChainCache class allows multiple caches to be registered at once.
|
||||
For example, a per-request ArrayCache can be used first, followed by
|
||||
a (relatively) slower MemcacheCache if the ArrayCache misses.
|
||||
ChainCache automatically handles pushing data up to faster caches in
|
||||
A chain cache class allows multiple caches to be registered at once.
|
||||
For example, a per-request array cache can be used first, followed by
|
||||
a (relatively) slower Memcached cache if the array cache misses.
|
||||
The chain cache automatically handles pushing data up to faster caches in
|
||||
the chain and clearing data in the entire stack when it is deleted.
|
||||
|
||||
A ChainCache takes a simple array of CacheProviders in the order that
|
||||
they should be used.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$arrayCache = new \Doctrine\Common\Cache\ArrayCache();
|
||||
$memcache = new Memcache();
|
||||
$memcache->connect('memcache_host', 11211);
|
||||
$chainCache = new \Doctrine\Common\Cache\ChainCache([
|
||||
$arrayCache,
|
||||
$memcache,
|
||||
]);
|
||||
|
||||
ChainCache itself extends the CacheProvider interface, so it is
|
||||
possible to create chains of chains. While this may seem like an easy
|
||||
way to build a simple high-availability cache, ChainCache does not
|
||||
implement any exception handling so using it as a high-availability
|
||||
mechanism is not recommended.
|
||||
Symfony Cache provides such a chain cache. To find out how to use it,
|
||||
please have a look at the
|
||||
`Symfony Documentation <https://symfony.com/doc/current/components/cache/adapters/chain_adapter.html>`_.
|
||||
|
||||
Cache Slams
|
||||
-----------
|
||||
@@ -464,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>`_.
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ Deferred Explicit
|
||||
|
||||
The deferred explicit policy is similar to the deferred implicit
|
||||
policy in that it detects changes through a property-by-property
|
||||
comparison at commit time. The difference is that Doctrine 2 only
|
||||
comparison at commit time. The difference is that Doctrine ORM only
|
||||
considers entities that have been explicitly marked for change detection
|
||||
through a call to EntityManager#persist(entity) or through a save
|
||||
cascade. All other entities are skipped. This policy therefore
|
||||
@@ -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
|
||||
{
|
||||
// ...
|
||||
@@ -61,6 +60,11 @@ This policy can be configured as follows:
|
||||
Notify
|
||||
~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
The notify change tracking policy is deprecated and will be removed in ORM 3.0.
|
||||
(`Details <https://github.com/doctrine/orm/issues/8383>`_)
|
||||
|
||||
This policy is based on the assumption that the entities notify
|
||||
interested listeners of changes to their properties. For that
|
||||
purpose, a class that wants to use this policy needs to implement
|
||||
@@ -73,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;
|
||||
}
|
||||
@@ -99,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) {
|
||||
@@ -112,8 +114,8 @@ behaviour:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setData($data)
|
||||
|
||||
public function setData($data): void
|
||||
{
|
||||
if ($data != $this->data) {
|
||||
$this->_onPropertyChanged('data', $this->data, $data);
|
||||
@@ -129,6 +131,32 @@ 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
|
||||
for example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// ...
|
||||
|
||||
class MyEntity implements NotifyPropertyChanged
|
||||
{
|
||||
public function setEmbeddable(MyValueObject $embeddable): void
|
||||
{
|
||||
if (!$embeddable->equals($this->embeddable)) {
|
||||
// notice the entityField.embeddableField notation for referencing the property
|
||||
$this->_onPropertyChanged('embeddable.prop1', $this->embeddable->getProp1(), $embeddable->getProp1());
|
||||
$this->_onPropertyChanged('embeddable.prop2', $this->embeddable->getProp2(), $embeddable->getProp2());
|
||||
$this->embeddable = $embeddable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
This would update all the fields of the embeddable, you may wish to
|
||||
implement a diff method on your embedded object which returns only
|
||||
the changed fields.
|
||||
|
||||
The negative point of this policy is obvious: You need implement an
|
||||
interface and write some plumbing code. But also note that we tried
|
||||
hard to keep this notification functionality abstract. Strictly
|
||||
@@ -147,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,104 +41,80 @@ access point to ORM functionality provided by Doctrine.
|
||||
// bootstrap.php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
$paths = array("/path/to/entity-files");
|
||||
$paths = ['/path/to/entity-files'];
|
||||
$isDevMode = false;
|
||||
|
||||
// the connection configuration
|
||||
$dbParams = array(
|
||||
$dbParams = [
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => 'root',
|
||||
'password' => '',
|
||||
'dbname' => 'foo',
|
||||
);
|
||||
];
|
||||
|
||||
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
$config = ORMSetup::createAttributeMetadataConfiguration($paths, $isDevMode);
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $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);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
$paths = ['/path/to/xml-mappings'];
|
||||
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
Or if you prefer YAML:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = array("/path/to/yml-mappings");
|
||||
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
$paths = ['/path/to/yml-mappings'];
|
||||
$config = ORMSetup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $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 false, set then proxy classes have to be explicitly created through the command line.
|
||||
- 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 <http://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:
|
||||
|
||||
On Doctrine 2.4 and above:
|
||||
|
||||
.. 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);
|
||||
|
||||
On Doctrine 2.3 and below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once 'my_bootstrap.php';
|
||||
|
||||
// Any way to access the EntityManager from your application
|
||||
$em = GetMyEntityManager();
|
||||
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
|
||||
));
|
||||
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>`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Doctrine Query Language
|
||||
===========================
|
||||
=======================
|
||||
|
||||
DQL stands for Doctrine Query Language and is an Object
|
||||
Query Language derivative that is very similar to the Hibernate
|
||||
@@ -180,10 +180,10 @@ not need to lazy load the association with another query.
|
||||
|
||||
Doctrine allows you to walk all the associations between
|
||||
all the objects in your domain model. Objects that were not already
|
||||
loaded from the database are replaced with lazy load proxy
|
||||
instances. Non-loaded Collections are also replaced by lazy-load
|
||||
loaded from the database are replaced with lazy-loading proxy
|
||||
instances. Non-loaded Collections are also replaced by lazy-loading
|
||||
instances that fetch all the contained objects upon first access.
|
||||
However relying on the lazy-load mechanism leads to many small
|
||||
However relying on the lazy-loading mechanism leads to many small
|
||||
queries executed against the database, which can significantly
|
||||
affect the performance of your application. **Fetch Joins** are the
|
||||
solution to hydrate most or all of the entities that you need in a
|
||||
@@ -319,11 +319,11 @@ With Nested Conditions in WHERE Clause:
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
|
||||
$query->setParameters(array(
|
||||
$query->setParameters([
|
||||
'name' => 'Bob',
|
||||
'name2' => 'Alice',
|
||||
'id' => 321,
|
||||
));
|
||||
]);
|
||||
$users = $query->getResult(); // array of ForumUser objects
|
||||
|
||||
With COUNT DISTINCT:
|
||||
@@ -458,8 +458,6 @@ Get all users that have no phonenumber
|
||||
Get all instances of a specific type, for use with inheritance
|
||||
hierarchies:
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
@@ -469,30 +467,46 @@ hierarchies:
|
||||
|
||||
Get all users visible on a given website that have chosen certain gender:
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM User u WHERE u.gender IN (SELECT IDENTITY(agl.gender) FROM Site s JOIN s.activeGenderList agl WHERE s.id = ?1)');
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Starting with 2.4, the IDENTITY() DQL function also works for composite primary keys:
|
||||
The IDENTITY() DQL function also works for composite primary keys
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery("SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1");
|
||||
|
||||
Joins between entities without associations were not possible until version
|
||||
2.4, where you can generate an arbitrary join with the following syntax:
|
||||
Joins between entities without associations are available,
|
||||
where you can generate an arbitrary join with the following syntax:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM User u JOIN Banlist b WITH u.email = b.email');
|
||||
|
||||
With an arbitrary join the result differs from the joins using a mapped property.
|
||||
The result of an arbitrary join is an one dimensional array with a mix of the entity from the ``SELECT``
|
||||
and the joined entity fitting to the filtering of the query. In case of the example with ``User``
|
||||
and ``Banlist``, it can look like this:
|
||||
|
||||
- User
|
||||
- Banlist
|
||||
- Banlist
|
||||
- User
|
||||
- Banlist
|
||||
- User
|
||||
- Banlist
|
||||
- Banlist
|
||||
- Banlist
|
||||
|
||||
In this form of join, the ``Banlist`` entities found by the filtering in the ``WITH`` part are not fetched by an accessor
|
||||
method on ``User``, but are already part of the result. In case the accessor method for Banlists is invoked on a User instance,
|
||||
it loads all the related ``Banlist`` objects corresponding to this ``User``. This change of behaviour needs to be considered
|
||||
when the DQL is switched to an arbitrary join.
|
||||
|
||||
.. note::
|
||||
The differences between WHERE, WITH and HAVING clauses may be
|
||||
confusing.
|
||||
@@ -534,8 +548,6 @@ You use the partial syntax when joining as well:
|
||||
"NEW" Operator Syntax
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Using the ``NEW`` operator you can construct Data Transfer Objects (DTOs) directly from DQL queries.
|
||||
|
||||
- When using ``SELECT NEW`` you don't need to specify a mapped entity.
|
||||
@@ -611,6 +623,13 @@ then phonenumber-id:
|
||||
...
|
||||
'nameUpper' => string 'JWAGE' (length=5)
|
||||
|
||||
You can also index by a to-one association, which will use the id of
|
||||
the associated entity (the join column) as the key in the result set:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT p, u FROM Participant INDEX BY p.user JOIN p.user u WHERE p.event = 3
|
||||
|
||||
UPDATE queries
|
||||
--------------
|
||||
|
||||
@@ -651,12 +670,34 @@ The same restrictions apply for the reference of related entities.
|
||||
|
||||
.. warning::
|
||||
|
||||
DQL DELETE statements are ported directly into a
|
||||
Database DELETE statement and therefore bypass any events and checks for the
|
||||
version column if they are not explicitly added to the WHERE clause
|
||||
of the query. Additionally Deletes of specified entities are *NOT*
|
||||
cascaded to related entities even if specified in the metadata.
|
||||
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
|
||||
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
|
||||
(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.
|
||||
|
||||
Comments in queries
|
||||
-------------------
|
||||
|
||||
We can use comments with the SQL syntax of comments.
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u FROM MyProject\Model\User u
|
||||
-- my comment
|
||||
WHERE u.age > 20 -- comment at the end of a line
|
||||
|
||||
Functions, Operators, Aggregates
|
||||
--------------------------------
|
||||
@@ -671,29 +712,35 @@ The following functions are supported in SELECT, WHERE and HAVING
|
||||
clauses:
|
||||
|
||||
|
||||
- IDENTITY(single\_association\_path\_expression [, fieldMapping]) - Retrieve the foreign key column of association of the owning side
|
||||
- ABS(arithmetic\_expression)
|
||||
- CONCAT(str1, str2)
|
||||
- CURRENT\_DATE() - Return the current date
|
||||
- CURRENT\_TIME() - Returns the current time
|
||||
- CURRENT\_TIMESTAMP() - Returns a timestamp of the current date
|
||||
- ``IDENTITY(single_association_path_expression [, fieldMapping])`` -
|
||||
Retrieve the foreign key column of association of the owning side
|
||||
- ``ABS(arithmetic_expression)``
|
||||
- ``CONCAT(str1, str2)``
|
||||
- ``CURRENT_DATE()`` - Return the current date
|
||||
- ``CURRENT_TIME()`` - Returns the current time
|
||||
- ``CURRENT_TIMESTAMP()`` - Returns a timestamp of the current date
|
||||
and time.
|
||||
- LENGTH(str) - Returns the length of the given string
|
||||
- LOCATE(needle, haystack [, offset]) - Locate the first
|
||||
- ``LENGTH(str)`` - Returns the length of the given string
|
||||
- ``LOCATE(needle, haystack [, offset])`` - Locate the first
|
||||
occurrence of the substring in the string.
|
||||
- LOWER(str) - returns the string lowercased.
|
||||
- MOD(a, b) - Return a MOD b.
|
||||
- SIZE(collection) - Return the number of elements in the
|
||||
- ``LOWER(str)`` - returns the string lowercased.
|
||||
- ``MOD(a, b)`` - Return a MOD b.
|
||||
- ``SIZE(collection)`` - Return the number of elements in the
|
||||
specified collection
|
||||
- SQRT(q) - Return the square-root of q.
|
||||
- SUBSTRING(str, start [, length]) - Return substring of given
|
||||
- ``SQRT(q)`` - Return the square-root of q.
|
||||
- ``SUBSTRING(str, start [, length])`` - Return substring of given
|
||||
string.
|
||||
- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim
|
||||
- ``TRIM([LEADING | TRAILING | BOTH] ['trchar' FROM] str)`` - Trim
|
||||
the string by the given trim char, defaults to whitespaces.
|
||||
- UPPER(str) - Return the upper-case of the given string.
|
||||
- DATE_ADD(date, value, unit) - Add the given time to a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
|
||||
- DATE_SUB(date, value, unit) - Subtract the given time from a given date. (Supported units are SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR)
|
||||
- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2.
|
||||
- ``UPPER(str)`` - Return the upper-case of the given string.
|
||||
- ``DATE_ADD(date, value, unit)`` - Add the given time to a given date.
|
||||
(Supported units are ``SECOND``, ``MINUTE``, ``HOUR``, ``DAY``,
|
||||
``WEEK``, ``MONTH``, ``YEAR``)
|
||||
- ``DATE_SUB(date, value, unit)`` - Subtract the given time from a
|
||||
given date. (Supported units are ``SECOND``, ``MINUTE``, ``HOUR``,
|
||||
``DAY``, ``WEEK``, ``MONTH``, ``YEAR``)
|
||||
- ``DATE_DIFF(date1, date2)`` - Calculate the difference in days
|
||||
between date1-date2.
|
||||
|
||||
Arithmetic operators
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -747,7 +794,7 @@ You can register custom DQL functions in your ORM Configuration:
|
||||
$config->addCustomNumericFunction($name, $class);
|
||||
$config->addCustomDatetimeFunction($name, $class);
|
||||
|
||||
$em = EntityManager::create($dbParams, $config);
|
||||
$em = new EntityManager($connection, $config);
|
||||
|
||||
The functions have to return either a string, numeric or datetime
|
||||
value depending on the registered function type. As an example we
|
||||
@@ -759,8 +806,8 @@ classes have to implement the base class :
|
||||
<?php
|
||||
namespace MyProject\Query\AST;
|
||||
|
||||
use \Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use \Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
|
||||
class MysqlFloor extends FunctionNode
|
||||
{
|
||||
@@ -803,7 +850,7 @@ what type of results to expect.
|
||||
Single Table
|
||||
~~~~~~~~~~~~
|
||||
|
||||
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where all classes of a hierarchy
|
||||
are mapped to a single database table. In order to distinguish
|
||||
which row represents which type in the hierarchy a so-called
|
||||
@@ -817,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;
|
||||
|
||||
// ...
|
||||
@@ -896,11 +933,11 @@ entities:
|
||||
Class Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
|
||||
`Class Table Inheritance <https://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where each class in a hierarchy
|
||||
is mapped to several tables: its own table and the tables of all
|
||||
parent classes. The table of a child class is linked to the table
|
||||
of a parent class through a foreign key constraint. Doctrine 2
|
||||
of a parent class through a foreign key constraint. Doctrine ORM
|
||||
implements this strategy through the use of a discriminator column
|
||||
in the topmost table of the hierarchy because this is the easiest
|
||||
way to achieve polymorphic queries with Class Table Inheritance.
|
||||
@@ -912,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
|
||||
{
|
||||
// ...
|
||||
@@ -1021,7 +1057,7 @@ the Query class. Here they are:
|
||||
|
||||
Instead of using these methods, you can alternatively use the
|
||||
general-purpose method
|
||||
``Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)``.
|
||||
``Query#execute(array $params = [], $hydrationMode = Query::HYDRATE_OBJECT)``.
|
||||
Using this method you can directly supply the hydration mode as the
|
||||
second parameter via one of the Query constants. In fact, the
|
||||
methods mentioned earlier are just convenient shortcuts for the
|
||||
@@ -1145,10 +1181,11 @@ make best use of the different result formats:
|
||||
The constants for the different hydration modes are:
|
||||
|
||||
|
||||
- Query::HYDRATE\_OBJECT
|
||||
- Query::HYDRATE\_ARRAY
|
||||
- Query::HYDRATE\_SCALAR
|
||||
- Query::HYDRATE\_SINGLE\_SCALAR
|
||||
- ``Query::HYDRATE_OBJECT``
|
||||
- ``Query::HYDRATE_ARRAY``
|
||||
- ``Query::HYDRATE_SCALAR``
|
||||
- ``Query::HYDRATE_SINGLE_SCALAR``
|
||||
- ``Query::HYDRATE_SCALAR_COLUMN``
|
||||
|
||||
Object Hydration
|
||||
^^^^^^^^^^^^^^^^
|
||||
@@ -1214,7 +1251,7 @@ Scalar Hydration:
|
||||
|
||||
|
||||
1. Fields from classes are prefixed by the DQL alias in the result.
|
||||
A query of the kind 'SELECT u.name ..' returns a key 'u\_name' in
|
||||
A query of the kind 'SELECT u.name ..' returns a key 'u_name' in
|
||||
the result rows.
|
||||
|
||||
Single Scalar Hydration
|
||||
@@ -1237,6 +1274,25 @@ You can use the ``getSingleScalarResult()`` shortcut as well:
|
||||
<?php
|
||||
$numArticles = $query->getSingleScalarResult();
|
||||
|
||||
Scalar Column Hydration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you have a query which returns a one-dimensional array of scalar values
|
||||
you can use scalar column hydration:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT a.id FROM CmsUser u');
|
||||
$ids = $query->getResult(Query::HYDRATE_SCALAR_COLUMN);
|
||||
|
||||
You can use the ``getSingleColumnResult()`` shortcut as well:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$ids = $query->getSingleColumnResult();
|
||||
|
||||
Custom Hydration Modes
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1248,13 +1304,14 @@ creating a class which extends ``AbstractHydrator``:
|
||||
<?php
|
||||
namespace MyProject\Hydrators;
|
||||
|
||||
use Doctrine\DBAL\FetchMode;
|
||||
use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
|
||||
|
||||
class CustomHydrator extends AbstractHydrator
|
||||
{
|
||||
protected function _hydrateAll()
|
||||
{
|
||||
return $this->_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return $this->_stmt->fetchAll(FetchMode::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1362,21 +1419,22 @@ userland. However the following few hints are to be used in
|
||||
userland:
|
||||
|
||||
|
||||
- Query::HINT\_FORCE\_PARTIAL\_LOAD - Allows to hydrate objects
|
||||
- ``Query::HINT_FORCE_PARTIAL_LOAD`` - Allows to hydrate objects
|
||||
although not all their columns are fetched. This query hint can be
|
||||
used to handle memory consumption problems with large result-sets
|
||||
that contain char or binary data. Doctrine has no way of implicitly
|
||||
reloading this data. Partially loaded objects have to be passed to
|
||||
``EntityManager::refresh()`` if they are to be reloaded fully from
|
||||
the database.
|
||||
- Query::HINT\_REFRESH - This query is used internally by
|
||||
the database. This query hint is deprecated and will be removed
|
||||
in the future (`Details <https://github.com/doctrine/orm/issues/8471>`_)
|
||||
- ``Query::HINT_REFRESH`` - This query is used internally by
|
||||
``EntityManager::refresh()`` and can be used in userland as well.
|
||||
If you specify this hint and a query returns the data for an entity
|
||||
that is already managed by the UnitOfWork, the fields of the
|
||||
existing entity will be refreshed. In normal operation a result-set
|
||||
that loads data of an already existing entity is discarded in favor
|
||||
of the already existing entity.
|
||||
- Query::HINT\_CUSTOM\_TREE\_WALKERS - An array of additional
|
||||
- ``Query::HINT_CUSTOM_TREE_WALKERS`` - An array of additional
|
||||
``Doctrine\ORM\Query\TreeWalker`` instances that are attached to
|
||||
the DQL query parsing process.
|
||||
|
||||
@@ -1462,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
|
||||
----
|
||||
|
||||
@@ -1490,7 +1550,6 @@ Terminals
|
||||
|
||||
- identifier (name, email, ...) must match ``[a-z_][a-z0-9_]*``
|
||||
- fully_qualified_name (Doctrine\Tests\Models\CMS\CmsUser) matches PHP's fully qualified class names
|
||||
- aliased_name (CMS:CmsUser) uses two identifiers, one for the namespace alias and one for the class inside it
|
||||
- string ('foo', 'bar''s house', '%ninja%', ...)
|
||||
- char ('/', '\\', ' ', ...)
|
||||
- integer (-1, 0, 1, 34, ...)
|
||||
@@ -1613,7 +1672,7 @@ From, Join and Index by
|
||||
RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
|
||||
JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
|
||||
Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" (JoinAssociationDeclaration | RangeVariableDeclaration) ["WITH" ConditionalExpression]
|
||||
IndexBy ::= "INDEX" "BY" StateFieldPathExpression
|
||||
IndexBy ::= "INDEX" "BY" SingleValuedPathExpression
|
||||
|
||||
Select Expressions
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
@@ -1656,7 +1715,7 @@ Literal Values
|
||||
.. code-block:: php
|
||||
|
||||
Literal ::= string | char | integer | float | boolean
|
||||
InParameter ::= Literal | InputParameter
|
||||
InParameter ::= ArithmeticExpression | InputParameter
|
||||
|
||||
Input Parameter
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -1731,7 +1790,7 @@ QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS
|
||||
QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
|
||||
BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
|
||||
ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
|
||||
InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
|
||||
InExpression ::= ArithmeticExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
|
||||
InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
|
||||
InstanceOfParameter ::= AbstractSchemaName | InputParameter
|
||||
LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
|
||||
@@ -1771,5 +1830,3 @@ Functions
|
||||
"LOWER" "(" StringPrimary ")" |
|
||||
"UPPER" "(" StringPrimary ")" |
|
||||
"IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
.
|
||||
@@ -52,7 +54,7 @@ or adding entities to a collection twice. You have to check for both conditions
|
||||
in the code before calling ``$em->flush()`` if you know that unique constraint failures
|
||||
can occur.
|
||||
|
||||
In `Symfony2 <http://www.symfony.com>`_ for example there is a Unique Entity Validator
|
||||
In `Symfony2 <https://www.symfony.com>`_ for example there is a Unique Entity Validator
|
||||
to achieve this task.
|
||||
|
||||
For collections you can check with ``$collection->contains($entity)`` if an entity is already
|
||||
@@ -80,7 +82,7 @@ You can solve this exception by:
|
||||
How can I filter an association?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Natively you can't filter associations in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities.
|
||||
You should use DQL queries to query for the filtered set of entities.
|
||||
|
||||
I call clear() on a One-To-Many collection but the entities are not deleted
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -98,7 +100,7 @@ How can I add columns to a many-to-many table?
|
||||
|
||||
The many-to-many association is only supporting foreign keys in the table definition
|
||||
To work with many-to-many tables containing extra columns you have to use the
|
||||
foreign keys as primary keys feature of Doctrine introduced in version 2.1.
|
||||
foreign keys as primary keys feature of Doctrine ORM.
|
||||
|
||||
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
|
||||
|
||||
@@ -112,8 +114,8 @@ over this collection using a LIMIT statement (or vendor equivalent).
|
||||
Doctrine does not offer a solution for this out of the box but there are several extensions
|
||||
that do:
|
||||
|
||||
* `DoctrineExtensions <http://github.com/beberlei/DoctrineExtensions>`_
|
||||
* `Pagerfanta <http://github.com/whiteoctober/pagerfanta>`_
|
||||
* `DoctrineExtensions <https://github.com/beberlei/DoctrineExtensions>`_
|
||||
* `Pagerfanta <https://github.com/whiteoctober/pagerfanta>`_
|
||||
|
||||
Why does pagination not work correctly with fetch joins?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -128,10 +130,10 @@ See the previous question for a solution to this task.
|
||||
Inheritance
|
||||
-----------
|
||||
|
||||
Can I use Inheritance with Doctrine 2?
|
||||
Can I use Inheritance with Doctrine ORM?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
|
||||
|
||||
Yes, you can use Single- or Joined-Table Inheritance in ORM.
|
||||
|
||||
See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>` for
|
||||
the details.
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Filters
|
||||
=======
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
Doctrine 2.2 features a filter system that allows the developer to add SQL to
|
||||
Doctrine ORM features a filter system that allows the developer to add SQL to
|
||||
the conditional clauses of queries, regardless the place where the SQL is
|
||||
generated (e.g. from a DQL query, or by loading associated entities).
|
||||
|
||||
@@ -44,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')) {
|
||||
@@ -55,6 +53,9 @@ proper quoting of parameters.
|
||||
}
|
||||
}
|
||||
|
||||
If the parameter is an array and should be quoted as a list of values for an IN query
|
||||
this is possible with the alternative ``SQLFilter#setParameterList()`` and
|
||||
``SQLFilter#getParameterList()`` functions.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
@@ -11,7 +11,7 @@ request and can greatly improve performance.
|
||||
"If you care about performance and don't use a bytecode
|
||||
cache then you don't really care about performance. Please get one
|
||||
and start using it."
|
||||
|
||||
|
||||
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
|
||||
|
||||
|
||||
@@ -27,11 +27,13 @@ Doctrine will need to load your mapping information on every single
|
||||
request and has to parse each DQL query on every single request.
|
||||
This is a waste of resources.
|
||||
|
||||
The preferred cache driver for metadata and query caches is ``PhpFileCache``.
|
||||
This driver serializes cache items and writes them to a file.
|
||||
The preferred cache adapter for metadata and query caches is a PHP file
|
||||
cache like Symfony's
|
||||
`PHP files adapter <https://symfony.com/doc/current/components/cache/adapters/php_files_adapter.html>`_.
|
||||
This kind of cache serializes cache items and writes them to a file.
|
||||
This allows for opcode caching to be used and provides high performance in most scenarios.
|
||||
|
||||
See :ref:`integrating-with-the-orm`
|
||||
See :ref:`types-of-caches`
|
||||
|
||||
Alternative Query Result Formats
|
||||
--------------------------------
|
||||
@@ -43,14 +45,36 @@ in scenarios where data is loaded for read-only purposes.
|
||||
Read-Only Entities
|
||||
------------------
|
||||
|
||||
Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping
|
||||
references for details). This means that the entity marked as read only is never considered
|
||||
for updates, which means when you call flush on the EntityManager these entities are skipped
|
||||
even if properties changed. Read-Only allows to persist new entities of a kind and remove existing
|
||||
ones, they are just not considered for updates.
|
||||
You can mark entities as read only (See metadata mapping
|
||||
references for details).
|
||||
|
||||
This means that the entity marked as read only is never considered for updates.
|
||||
During flush on the EntityManager these entities are skipped even if properties
|
||||
changed.
|
||||
|
||||
Read-Only allows to persist new entities of a kind and remove existing ones,
|
||||
they are just not considered for updates.
|
||||
|
||||
See :ref:`annref_entity`
|
||||
|
||||
You can also explicitly mark individual entities read only directly on the
|
||||
UnitOfWork via a call to ``markReadOnly()``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$user = $entityManager->find(User::class, $id);
|
||||
$entityManager->getUnitOfWork()->markReadOnly($user);
|
||||
|
||||
Or you can set all objects that are the result of a query hydration to be
|
||||
marked as read only with the following query hint:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$query = $entityManager->createQuery('SELECT u FROM App\\Entity\\User u');
|
||||
$query->setHint(Query::HINT_READ_ONLY, true);
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
Extra-Lazy Collections
|
||||
----------------------
|
||||
|
||||
@@ -75,4 +99,4 @@ See :doc:`Best Practices <reference/best-practices>`
|
||||
Change Tracking policies
|
||||
------------------------
|
||||
|
||||
See: :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||
See: :doc:`Change Tracking Policies <change-tracking-policies>`
|
||||
|
||||
@@ -31,30 +31,44 @@ Example:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @MappedSuperclass */
|
||||
class MappedSuperclassBase
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\JoinColumn;
|
||||
use Doctrine\ORM\Mapping\OneToOne;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping\MappedSuperclass;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
#[MappedSuperclass]
|
||||
class Person
|
||||
{
|
||||
/** @Column(type="integer") */
|
||||
protected $mapped1;
|
||||
/** @Column(type="string") */
|
||||
protected $mapped2;
|
||||
/**
|
||||
* @OneToOne(targetEntity="MappedSuperclassRelated1")
|
||||
* @JoinColumn(name="related1_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $mappedRelated1;
|
||||
|
||||
#[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 */
|
||||
class EntitySubClass extends MappedSuperclassBase
|
||||
|
||||
#[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]
|
||||
class Toothbrush
|
||||
{
|
||||
#[Id, Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
|
||||
@@ -73,7 +87,7 @@ defined on that class directly.
|
||||
Single Table Inheritance
|
||||
------------------------
|
||||
|
||||
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||
`Single Table Inheritance <https://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where all classes of a hierarchy
|
||||
are mapped to a single database table. In order to distinguish
|
||||
which row represents which type in the hierarchy a so-called
|
||||
@@ -83,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")
|
||||
@@ -98,7 +132,7 @@ Example:
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
@@ -108,7 +142,7 @@ Example:
|
||||
}
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
|
||||
MyProject\Model\Person:
|
||||
type: entity
|
||||
inheritanceType: SINGLE_TABLE
|
||||
@@ -118,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
|
||||
@@ -162,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.
|
||||
|
||||
@@ -181,11 +216,11 @@ the root entity of the single-table inheritance hierarchy.
|
||||
Class Table Inheritance
|
||||
-----------------------
|
||||
|
||||
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
|
||||
`Class Table Inheritance <https://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
|
||||
is an inheritance mapping strategy where each class in a hierarchy
|
||||
is mapped to several tables: its own table and the tables of all
|
||||
parent classes. The table of a child class is linked to the table
|
||||
of a parent class through a foreign key constraint. Doctrine 2
|
||||
of a parent class through a foreign key constraint. Doctrine ORM
|
||||
implements this strategy through the use of a discriminator column
|
||||
in the topmost table of the hierarchy because this is the easiest
|
||||
way to achieve polymorphic queries with Class Table Inheritance.
|
||||
@@ -196,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
|
||||
{
|
||||
// ...
|
||||
@@ -217,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
|
||||
@@ -230,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::
|
||||
@@ -268,12 +301,15 @@ 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*
|
||||
to query for the base entity without any LEFT JOINs to the sub-types.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -289,9 +325,15 @@ column and cascading on delete.
|
||||
|
||||
Overrides
|
||||
---------
|
||||
Used to override a mapping for an entity field or relationship.
|
||||
May be applied to an entity that extends a mapped superclass
|
||||
to override a relationship or field mapping defined by the mapped superclass.
|
||||
|
||||
Used to override a mapping for an entity field or relationship. Can only be
|
||||
applied to an entity that extends a mapped superclass or uses a trait to
|
||||
override a relationship or field mapping defined by the mapped superclass or
|
||||
trait.
|
||||
|
||||
It is not possible to override attributes or associations in entity to entity
|
||||
inheritance scenarios, because this can cause unforseen edge case behavior and
|
||||
increases complexity in ORM internal classes.
|
||||
|
||||
|
||||
Association Override
|
||||
@@ -305,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
|
||||
@@ -315,7 +402,7 @@ Example:
|
||||
*/
|
||||
class User
|
||||
{
|
||||
//other fields mapping
|
||||
// other fields mapping
|
||||
|
||||
/**
|
||||
* @ManyToMany(targetEntity="Group", inversedBy="users")
|
||||
@@ -323,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
|
||||
@@ -454,7 +542,7 @@ Things to note:
|
||||
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
|
||||
- The association type *CANNOT* be changed.
|
||||
- The override could redefine the joinTables or joinColumns depending on the association type.
|
||||
- The override could redefine inversedBy to reference more than one extended entity.
|
||||
- The override could redefine ``inversedBy`` to reference more than one extended entity.
|
||||
- The override could redefine fetch to modify the fetch strategy of the extended entity.
|
||||
|
||||
Attribute Override
|
||||
@@ -465,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
|
||||
@@ -476,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
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ Therefore we think it is very important to be honest about the
|
||||
current limitations to our users. Much like every other piece of
|
||||
software Doctrine2 is not perfect and far from feature complete.
|
||||
This section should give you an overview of current limitations of
|
||||
Doctrine 2 as well as critical known issues that you should know
|
||||
Doctrine ORM as well as critical known issues that you should know
|
||||
about.
|
||||
|
||||
Current Limitations
|
||||
@@ -107,17 +107,17 @@ to the same entity.
|
||||
Behaviors
|
||||
~~~~~~~~~
|
||||
|
||||
Doctrine 2 will **never** include a behavior system like Doctrine 1
|
||||
Doctrine ORM will **never** include a behavior system like Doctrine 1
|
||||
in the core library. We don't think behaviors add more value than
|
||||
they cost pain and debugging hell. Please see the many different
|
||||
blog posts we have written on this topics:
|
||||
|
||||
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
|
||||
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
|
||||
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
|
||||
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
|
||||
- `Doctrine2 "Behaviors" in a Nutshell <https://www.doctrine-project.org/2010/02/17/doctrine2-behaviours-nutshell.html>`_
|
||||
- `A re-usable Versionable behavior for Doctrine2 <https://www.doctrine-project.org/2010/02/24/doctrine2-versionable.html>`_
|
||||
- `Write your own ORM on top of Doctrine2 <https://www.doctrine-project.org/2010/07/19/your-own-orm-doctrine2.html>`_
|
||||
- `Doctrine ORM Behavioral Extensions <https://www.doctrine-project.org/2010/11/18/doctrine2-behavioral-extensions.html>`_
|
||||
|
||||
Doctrine 2 has enough hooks and extension points so that **you** can
|
||||
Doctrine ORM has enough hooks and extension points so that **you** can
|
||||
add whatever you want on top of it. None of this will ever become
|
||||
core functionality of Doctrine2 however, you will have to rely on
|
||||
third party extensions for magical behaviors.
|
||||
@@ -126,13 +126,13 @@ Nested Set
|
||||
~~~~~~~~~~
|
||||
|
||||
NestedSet was offered as a behavior in Doctrine 1 and will not be
|
||||
included in the core of Doctrine 2. However there are already two
|
||||
included in the core of Doctrine ORM. However there are already two
|
||||
extensions out there that offer support for Nested Set with
|
||||
Doctrine 2:
|
||||
ORM:
|
||||
|
||||
|
||||
- `Doctrine2 Hierarchical-Structural Behavior <http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
|
||||
- `Doctrine2 NestedSet <http://github.com/blt04/doctrine2-nestedset>`_
|
||||
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
|
||||
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
@@ -149,9 +149,9 @@ Identifier Quoting and Legacy Databases
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For compatibility reasons between all the supported vendors and
|
||||
edge case problems Doctrine 2 does **NOT** do automatic identifier
|
||||
edge case problems Doctrine ORM does **NOT** do automatic identifier
|
||||
quoting. This can lead to problems when trying to get
|
||||
legacy-databases to work with Doctrine 2.
|
||||
legacy-databases to work with Doctrine ORM.
|
||||
|
||||
|
||||
- You can quote column-names as described in the
|
||||
@@ -177,27 +177,3 @@ MySQL with MyISAM tables
|
||||
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
|
||||
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
|
||||
other storage engines that support transactions if you need integrity.
|
||||
|
||||
Entities, Proxies and Reflection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using methods for Reflection on entities can be prone to error, when the entity
|
||||
is actually a proxy the following methods will not work correctly:
|
||||
|
||||
- ``new ReflectionClass``
|
||||
- ``new ReflectionObject``
|
||||
- ``get_class()``
|
||||
- ``get_parent_class()``
|
||||
|
||||
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
|
||||
methods, which resolve the proxy problem beforehand.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
$bookProxy = $entityManager->getReference('Acme\Book');
|
||||
|
||||
$reflection = ClassUtils::newReflectionClass($bookProxy);
|
||||
$class = ClassUtils::getClass($bookProxy)¸
|
||||
|
||||
@@ -13,9 +13,15 @@ metadata:
|
||||
|
||||
|
||||
- **XML files** (XmlDriver)
|
||||
- **Attributes** (AttributeDriver)
|
||||
- **PHP Code in files or static functions** (PhpDriver)
|
||||
|
||||
There are also two deprecated ways to do this:
|
||||
|
||||
- **Class DocBlock Annotations** (AnnotationDriver)
|
||||
- **YAML files** (YamlDriver)
|
||||
- **PHP Code in files or static functions** (PhpDriver)
|
||||
|
||||
They will be removed in 3.0, make sure to avoid them.
|
||||
|
||||
Something important to note about the above drivers is they are all
|
||||
an intermediate step to the same end result. The mapping
|
||||
@@ -38,8 +44,10 @@ an entity.
|
||||
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
|
||||
|
||||
|
||||
If you want to use one of the included core metadata drivers you
|
||||
just need to configure it. All the drivers are in the
|
||||
If you want to use one of the included core metadata drivers you need to
|
||||
configure it. If you pick the annotation driver despite it being
|
||||
deprecated, you will additionally need to install
|
||||
``doctrine/annotations``. All the drivers are in the
|
||||
``Doctrine\ORM\Mapping\Driver`` namespace:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -53,67 +61,82 @@ 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}
|
||||
*/
|
||||
protected $_fileExtension = '.dcm.ext';
|
||||
|
||||
|
||||
/**
|
||||
* {@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
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -125,13 +148,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
|
||||
@@ -147,21 +169,13 @@ ClassMetadata
|
||||
-------------
|
||||
|
||||
The last piece you need to know and understand about metadata in
|
||||
Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to
|
||||
Doctrine ORM is the API of the ``ClassMetadata`` classes. You need to
|
||||
be familiar with them in order to implement your own drivers but
|
||||
more importantly to retrieve mapping information for a certain
|
||||
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.
|
||||
@@ -190,5 +204,3 @@ iterate over them:
|
||||
foreach ($class->fieldMappings as $fieldMapping) {
|
||||
echo $fieldMapping['fieldName'] . "\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
Implementing a NamingStrategy
|
||||
==============================
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
Using a naming strategy you can provide rules for generating database identifiers,
|
||||
column or table names. This feature helps
|
||||
reduce the verbosity of the mapping document, eliminating repetitive noise (eg: ``TABLE_``).
|
||||
|
||||
.. 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
|
||||
-----------------------------
|
||||
|
||||
@@ -71,7 +71,7 @@ with inheritance hierarchies.
|
||||
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
|
||||
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
|
||||
$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .
|
||||
"FROM users u INNER JOIN address a ON u.address_id = a.id";
|
||||
|
||||
$rsm = new ResultSetMappingBuilder($entityManager);
|
||||
@@ -80,9 +80,7 @@ with inheritance hierarchies.
|
||||
|
||||
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
|
||||
The ``SELECT`` clause can be generated
|
||||
from a ``ResultSetMappingBuilder``. You can either cast the builder
|
||||
object to ``(string)`` and the DQL aliases are used as SQL table aliases
|
||||
or use the ``generateSelectClause($tableAliases)`` method and pass
|
||||
@@ -267,7 +265,7 @@ detail:
|
||||
<?php
|
||||
/**
|
||||
* Adds a meta column (foreign key or discriminator column) to the result set.
|
||||
*
|
||||
*
|
||||
* @param string $alias
|
||||
* @param string $columnAlias
|
||||
* @param string $columnName
|
||||
@@ -322,10 +320,10 @@ entity.
|
||||
$rsm->addEntityResult('User', 'u');
|
||||
$rsm->addFieldResult('u', 'id', 'id');
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
|
||||
|
||||
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
The result would look like this:
|
||||
@@ -358,10 +356,10 @@ thus owns the foreign key.
|
||||
$rsm->addFieldResult('u', 'id', 'id');
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
$rsm->addMetaResult('u', 'address_id', 'address_id');
|
||||
|
||||
|
||||
$query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
Foreign keys are used by Doctrine for lazy-loading purposes when
|
||||
@@ -387,12 +385,12 @@ associations that are lazy.
|
||||
$rsm->addFieldResult('a', 'address_id', 'id');
|
||||
$rsm->addFieldResult('a', 'street', 'street');
|
||||
$rsm->addFieldResult('a', 'city', 'city');
|
||||
|
||||
|
||||
$sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' .
|
||||
'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?';
|
||||
$query = $this->_em->createNativeQuery($sql, $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
In this case the nested entity ``Address`` is registered with the
|
||||
@@ -422,10 +420,10 @@ to map the hierarchy (both use a discriminator column).
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
$rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column
|
||||
$rsm->setDiscriminatorColumn('u', 'discr');
|
||||
|
||||
|
||||
$query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
|
||||
$users = $query->getResult();
|
||||
|
||||
Note that in the case of Class Table Inheritance, an example as
|
||||
@@ -437,6 +435,10 @@ strategy but with native SQL it is your responsibility.
|
||||
Named Native Query
|
||||
------------------
|
||||
|
||||
.. note::
|
||||
|
||||
Named Native Queries are deprecated as of version 2.9 and will be removed in ORM 3.0
|
||||
|
||||
You can also map a native query using a named native query mapping.
|
||||
|
||||
To achieve that, you must describe the SQL resultset structure
|
||||
@@ -791,7 +793,7 @@ followed by a dot ("."), followed by the name or the field or property of the pr
|
||||
6:
|
||||
name: address.country
|
||||
column: a_country
|
||||
|
||||
|
||||
|
||||
|
||||
If you retrieve a single entity and if you use the default mapping,
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
Partial Objects
|
||||
===============
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Creating Partial Objects through DQL is deprecated and
|
||||
will be removed in the future, use data transfer object
|
||||
support in DQL instead. (`Details
|
||||
<https://github.com/doctrine/orm/issues/8471>`_)
|
||||
|
||||
A partial object is an object whose state is not fully initialized
|
||||
after being reconstituted from the database and that is
|
||||
disconnected from the rest of its data. The following section will
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PHP Mapping
|
||||
===========
|
||||
|
||||
Doctrine 2 also allows you to provide the ORM metadata in the form
|
||||
Doctrine ORM also allows you to provide the ORM metadata in the form
|
||||
of plain PHP code using the ``ClassMetadata`` API. You can write
|
||||
the code in PHP files or inside of a static function named
|
||||
``loadMetadata($class)`` on the entity class itself.
|
||||
@@ -9,6 +9,11 @@ the code in PHP files or inside of a static function named
|
||||
PHP Files
|
||||
---------
|
||||
|
||||
.. note::
|
||||
|
||||
PHPDriver is deprecated and will be removed in 3.0, use StaticPHPDriver
|
||||
instead.
|
||||
|
||||
If you wish to write your mapping information inside PHP files that
|
||||
are named after the entity and included to populate the metadata
|
||||
for an entity you can do so by using the ``PHPDriver``:
|
||||
@@ -85,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);
|
||||
|
||||
@@ -160,7 +167,7 @@ The API of the ClassMetadataBuilder has the following methods with a fluent inte
|
||||
- ``addNamedQuery($name, $dqlQuery)``
|
||||
- ``setJoinedTableInheritance()``
|
||||
- ``setSingleTableInheritance()``
|
||||
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
|
||||
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255, $columnDefinition = null, $enumType = null)``
|
||||
- ``addDiscriminatorMapClass($name, $class)``
|
||||
- ``setChangeTrackingPolicyDeferredExplicit()``
|
||||
- ``setChangeTrackingPolicyNotify()``
|
||||
@@ -180,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
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -304,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()``
|
||||
@@ -321,5 +325,3 @@ themselves.
|
||||
- ``setIdentifierValues($entity, $id)``
|
||||
- ``setFieldValue($entity, $field, $value)``
|
||||
- ``getFieldValue($entity, $field)``
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ as you want, or just pick a preferred one.
|
||||
|
||||
The ``QueryBuilder`` is not an abstraction of DQL, but merely a tool to dynamically build it.
|
||||
You should still use plain DQL when you can, as it is simpler and more readable.
|
||||
More about this in the :doc:`FAQ <faq>`_.
|
||||
More about this in the :doc:`FAQ <faq>`.
|
||||
|
||||
Constructing a new QueryBuilder object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -252,8 +252,8 @@ while the named placeholders start with a : followed by a string.
|
||||
Calling ``setParameter()`` automatically infers which type you are setting as
|
||||
value. This works for integers, arrays of strings/integers, DateTime instances
|
||||
and for managed entities. If you want to set a type explicitly you can call
|
||||
the third argument to ``setParameter()`` explicitly. It accepts either a PDO
|
||||
type or a DBAL Type name for conversion.
|
||||
the third argument to ``setParameter()`` explicitly. It accepts either a DBAL
|
||||
Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -268,7 +268,7 @@ type 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
|
||||
@@ -277,10 +277,17 @@ following syntax:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// Query here...
|
||||
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
|
||||
$qb->setParameters(new ArrayCollection([
|
||||
new Parameter('1', 'value for ?1'),
|
||||
new Parameter('2', 'value for ?2')
|
||||
]));
|
||||
|
||||
Getting already bound parameters is easy - simply use the above
|
||||
mentioned syntax with "getParameter()" or "getParameters()":
|
||||
@@ -354,6 +361,7 @@ a querybuilder instance into a Query object:
|
||||
|
||||
// Execute Query
|
||||
$result = $query->getResult();
|
||||
$iterableResult = $query->toIterable();
|
||||
$single = $query->getSingleResult();
|
||||
$array = $query->getArrayResult();
|
||||
$scalar = $query->getScalarResult();
|
||||
@@ -426,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 **/
|
||||
|
||||
@@ -512,6 +526,9 @@ complete list of supported helper methods available:
|
||||
// Example - $qb->expr()->sqrt('u.currentBalance')
|
||||
public function sqrt($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->mod('u.currentBalance', '10')
|
||||
public function mod($x); // Returns Expr\Func
|
||||
|
||||
// Example - $qb->expr()->count('u.firstname')
|
||||
public function count($x); // Returns Expr\Func
|
||||
|
||||
@@ -532,7 +549,7 @@ using ``addCriteria``:
|
||||
// ...
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['firstName', 'ASC']);
|
||||
->orderBy(['firstName' => Criteria::ASC]);
|
||||
|
||||
// $qb instanceof QueryBuilder
|
||||
$qb->addCriteria($criteria);
|
||||
|
||||
@@ -31,31 +31,31 @@ Each cache region resides in a specific cache namespace and has its own lifetime
|
||||
Notice that when caching collection and queries only identifiers are stored.
|
||||
The entity values will be stored in its own region
|
||||
|
||||
Something like below for an entity region :
|
||||
Something like below for an entity region:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
[
|
||||
'region_name:entity_1_hash' => ['id'=> 1, 'name' => 'FooBar', 'associationName'=>null],
|
||||
'region_name:entity_2_hash' => ['id'=> 2, 'name' => 'Foo', 'associationName'=>['id'=>11]],
|
||||
'region_name:entity_3_hash' => ['id'=> 3, 'name' => 'Bar', 'associationName'=>['id'=>22]]
|
||||
'region_name:entity_1_hash' => ['id' => 1, 'name' => 'FooBar', 'associationName' => null],
|
||||
'region_name:entity_2_hash' => ['id' => 2, 'name' => 'Foo', 'associationName' => ['id' => 11]],
|
||||
'region_name:entity_3_hash' => ['id' => 3, 'name' => 'Bar', 'associationName' => ['id' => 22]]
|
||||
];
|
||||
|
||||
|
||||
If the entity holds a collection that also needs to be cached.
|
||||
An collection region could look something like :
|
||||
An collection region could look something like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
[
|
||||
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId'=> 1, 'list' => [1, 2, 3]],
|
||||
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId'=> 2, 'list' => [2, 3]],
|
||||
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId'=> 3, 'list' => [2, 4]]
|
||||
'region_name:entity_1_coll_assoc_name_hash' => ['ownerId' => 1, 'list' => [1, 2, 3]],
|
||||
'region_name:entity_2_coll_assoc_name_hash' => ['ownerId' => 2, 'list' => [2, 3]],
|
||||
'region_name:entity_3_coll_assoc_name_hash' => ['ownerId' => 3, 'list' => [2, 4]]
|
||||
];
|
||||
|
||||
A query region might be something like :
|
||||
A query region might be something like:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -77,11 +77,10 @@ A query region might be something like :
|
||||
Cache Regions
|
||||
-------------
|
||||
|
||||
``Doctrine\ORM\Cache\Region\DefaultRegion`` It's the default implementation.
|
||||
``Doctrine\ORM\Cache\Region\DefaultRegion`` is the default implementation.
|
||||
A simplest cache region compatible with all doctrine-cache drivers but does not support locking.
|
||||
|
||||
``Doctrine\ORM\Cache\Region`` and ``Doctrine\ORM\Cache\ConcurrentRegion``
|
||||
Defines contracts that should be implemented by a cache provider.
|
||||
define contracts that should be implemented by a cache provider.
|
||||
|
||||
It allows you to provide your own cache implementation that might take advantage of specific cache driver.
|
||||
|
||||
@@ -91,13 +90,8 @@ If you want to support locking for ``READ_WRITE`` strategies you should implemen
|
||||
Cache region
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Defines a contract for accessing a particular region.
|
||||
|
||||
``Doctrine\ORM\Cache\Region``
|
||||
|
||||
Defines a contract for accessing a particular cache region.
|
||||
|
||||
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Region.html>`_.
|
||||
``Doctrine\ORM\Cache\Region`` defines a contract for accessing a particular
|
||||
cache region.
|
||||
|
||||
Concurrent cache region
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -107,11 +101,7 @@ By default, Doctrine provides a very simple implementation based on file locks `
|
||||
|
||||
If you want to use an ``READ_WRITE`` cache, you should consider providing your own cache region.
|
||||
|
||||
``Doctrine\ORM\Cache\ConcurrentRegion``
|
||||
|
||||
Defines contract for concurrently managed data region.
|
||||
|
||||
`See API Doc <https://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/ConcurrentRegion.html>`_.
|
||||
``Doctrine\ORM\Cache\ConcurrentRegion`` defines a contract for concurrently managed data region.
|
||||
|
||||
Timestamp region
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -120,8 +110,6 @@ Timestamp region
|
||||
|
||||
Tracks the timestamps of the most recent updates to particular entity.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/TimestampRegion.html>`_.
|
||||
|
||||
.. _reference-second-level-cache-mode:
|
||||
|
||||
Caching mode
|
||||
@@ -138,7 +126,7 @@ Caching mode
|
||||
|
||||
* Read Write Cache doesn’t employ any locks but can do reads, inserts, updates and deletes.
|
||||
* Good if the application needs to update data rarely.
|
||||
|
||||
|
||||
|
||||
* ``READ_WRITE``
|
||||
|
||||
@@ -153,21 +141,21 @@ Built-in cached persisters
|
||||
|
||||
Cached persisters are responsible to access cache regions.
|
||||
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| Cache Usage | Persister |
|
||||
+=======================+===========================================================================================+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadOnlyCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\ReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Entity\\NonStrictReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadOnlyCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\ReadWriteCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\Collection\\NonStrictReadWriteCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
+-----------------------+------------------------------------------------------------------------------------------+
|
||||
| Cache Usage | Persister |
|
||||
+=======================+==========================================================================================+
|
||||
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister`` |
|
||||
+-----------------------+------------------------------------------------------------------------------------------+
|
||||
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister`` |
|
||||
+-----------------------+------------------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister`` |
|
||||
+-----------------------+------------------------------------------------------------------------------------------+
|
||||
| READ_ONLY | ``Doctrine\ORM\Cache\Persister\Collection\ReadOnlyCachedCollectionPersister`` |
|
||||
+-----------------------+------------------------------------------------------------------------------------------+
|
||||
| READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister`` |
|
||||
+-----------------------+------------------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | ``Doctrine\ORM\Cache\Persister\Collection\NonStrictReadWriteCachedCollectionPersister`` |
|
||||
+-----------------------+------------------------------------------------------------------------------------------+
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
@@ -177,16 +165,17 @@ Doctrine allows you to specify configurations and some points of extension for t
|
||||
Enable Second Level Cache
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To enable the second-level-cache, you should provide a cache factory
|
||||
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
|
||||
To enable the second-level-cache, you should provide a cache factory.
|
||||
``Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Cache\RegionsConfiguration */
|
||||
/* @var $cache \Doctrine\Common\Cache\Cache */
|
||||
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig */
|
||||
/** @var \Psr\Cache\CacheItemPoolInterface $cache */
|
||||
/** @var \Doctrine\ORM\Configuration $config */
|
||||
|
||||
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($config, $cache);
|
||||
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($cacheConfig, $cache);
|
||||
|
||||
// Enable second-level-cache
|
||||
$config->setSecondLevelCacheEnabled();
|
||||
@@ -201,15 +190,18 @@ Cache Factory
|
||||
|
||||
Cache Factory is the main point of extension.
|
||||
|
||||
It allows you to provide a specific implementation of the following components :
|
||||
It allows you to provide a specific implementation of the following components:
|
||||
|
||||
* ``QueryCache`` Store and retrieve query cache results.
|
||||
* ``CachedEntityPersister`` Store and retrieve entity results.
|
||||
* ``CachedCollectionPersister`` Store and retrieve query results.
|
||||
* ``EntityHydrator`` Transform an entity into a cache entry and cache entry into entities
|
||||
* ``CollectionHydrator`` Transform a collection into a cache entry and cache entry into collection
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/DefaultCacheFactory.html>`_.
|
||||
``QueryCache``
|
||||
stores and retrieves query cache results.
|
||||
``CachedEntityPersister``
|
||||
stores and retrieves entity results.
|
||||
``CachedCollectionPersister``
|
||||
stores and retrieves query results.
|
||||
``EntityHydrator``
|
||||
transforms entities into a cache entries and cache entries into entities
|
||||
``CollectionHydrator``
|
||||
transforms collections into cache entries and cache entries into collections
|
||||
|
||||
Region Lifetime
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -219,26 +211,27 @@ To specify a default lifetime for all regions or specify a different lifetime fo
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
/* @var $cacheConfig \Doctrine\ORM\Cache\CacheConfiguration */
|
||||
/** @var \Doctrine\ORM\Configuration $config */
|
||||
/** @var \Doctrine\ORM\Cache\CacheConfiguration $cacheConfig */
|
||||
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $regionConfig */
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
|
||||
// Cache Region lifetime
|
||||
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region; In seconds
|
||||
$regionConfig->setDefaultLifetime(7200); // Default time to live; In seconds
|
||||
$regionConfig->setLifetime('my_entity_region', 3600); // Time to live for a specific region (in seconds)
|
||||
$regionConfig->setDefaultLifetime(7200); // Default time to live (in seconds)
|
||||
|
||||
|
||||
Cache Log
|
||||
~~~~~~~~~
|
||||
By providing a cache logger you should be able to get information about all cache operations such as hits, misses and puts.
|
||||
|
||||
``\Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
|
||||
``Doctrine\ORM\Cache\Logging\StatisticsCacheLogger`` is a built-in implementation that provides basic statistics.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
/** @var \Doctrine\ORM\Configuration $config */
|
||||
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
|
||||
|
||||
// Cache logger
|
||||
@@ -267,42 +260,61 @@ By providing a cache logger you should be able to get information about all cach
|
||||
// Get the total number of cached entries *not* found in all regions.
|
||||
$logger->getMissCount();
|
||||
|
||||
If you want to get more information you should implement ``\Doctrine\ORM\Cache\Logging\CacheLogger``.
|
||||
and collect all information you want.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/current/Doctrine/ORM/Cache/Logging/CacheLogger.html>`_.
|
||||
|
||||
If you want to get more information you should implement
|
||||
``Doctrine\ORM\Cache\Logging\CacheLogger`` and collect
|
||||
all the information you want.
|
||||
|
||||
Entity cache definition
|
||||
-----------------------
|
||||
* Entity cache configuration allows you to define the caching strategy and region for an entity.
|
||||
|
||||
* ``usage`` Specifies the caching strategy: ``READ_ONLY``, ``NONSTRICT_READ_WRITE``, ``READ_WRITE``. see :ref:`reference-second-level-cache-mode`
|
||||
* ``region`` Optional value that specifies the name of the second level cache region.
|
||||
* ``usage`` specifies the caching strategy: ``READ_ONLY``,
|
||||
``NONSTRICT_READ_WRITE``, ``READ_WRITE``.
|
||||
See :ref:`reference-second-level-cache-mode`.
|
||||
* ``region`` is an optional value that specifies the name of the second
|
||||
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
|
||||
}
|
||||
@@ -310,7 +322,7 @@ Entity cache definition
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="Country">
|
||||
<cache usage="READ_ONLY" region="my_entity_region" />
|
||||
<id name="id" type="integer" column="id">
|
||||
@@ -325,8 +337,8 @@ Entity cache definition
|
||||
Country:
|
||||
type: entity
|
||||
cache:
|
||||
usage : READ_ONLY
|
||||
region : my_entity_region
|
||||
usage: READ_ONLY
|
||||
region: my_entity_region
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
@@ -346,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
|
||||
/**
|
||||
@@ -360,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
|
||||
}
|
||||
@@ -386,7 +427,7 @@ It caches the primary keys of association and cache each element will be cached
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="State">
|
||||
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
@@ -396,7 +437,7 @@ It caches the primary keys of association and cache each element will be cached
|
||||
</id>
|
||||
|
||||
<field name="name" type="string" column="name"/>
|
||||
|
||||
|
||||
<many-to-one field="country" target-entity="Country">
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
|
||||
@@ -416,7 +457,7 @@ It caches the primary keys of association and cache each element will be cached
|
||||
State:
|
||||
type: entity
|
||||
cache:
|
||||
usage : NONSTRICT_READ_WRITE
|
||||
usage: NONSTRICT_READ_WRITE
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
@@ -434,17 +475,18 @@ It caches the primary keys of association and cache each element will be cached
|
||||
country_id:
|
||||
referencedColumnName: id
|
||||
cache:
|
||||
usage : NONSTRICT_READ_WRITE
|
||||
usage: NONSTRICT_READ_WRITE
|
||||
|
||||
oneToMany:
|
||||
cities:
|
||||
targetEntity:City
|
||||
mappedBy: state
|
||||
cache:
|
||||
usage : NONSTRICT_READ_WRITE
|
||||
usage: NONSTRICT_READ_WRITE
|
||||
|
||||
.. note::
|
||||
|
||||
> Note: for this to work, the target entity must also be marked as cacheable.
|
||||
for this to work, the target entity must also be marked as cacheable.
|
||||
|
||||
Cache usage
|
||||
~~~~~~~~~~~
|
||||
@@ -461,8 +503,8 @@ Basic entity cache
|
||||
|
||||
$country1 = $em->find('Country', 1); // Retrieve item from cache
|
||||
|
||||
$country->setName("New Name");
|
||||
$em->persist($country);
|
||||
$country1->setName('New Name');
|
||||
|
||||
$em->flush(); // Hit database to update the row and update cache
|
||||
|
||||
$em->clear(); // Clear entity manager
|
||||
@@ -487,7 +529,7 @@ Association cache
|
||||
$state = $em->find('State', 1);
|
||||
|
||||
// Hit database to update the row and update cache entry
|
||||
$state->setName("New Name");
|
||||
$state->setName('New Name');
|
||||
$em->persist($state);
|
||||
$em->flush();
|
||||
|
||||
@@ -538,14 +580,14 @@ The query cache stores the results of the query but as identifiers, entity value
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $em \Doctrine\ORM\EntityManager */
|
||||
/** @var \Doctrine\ORM\EntityManager $em */
|
||||
|
||||
// Execute database query, store query cache and entity cache
|
||||
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
$em->clear()
|
||||
$em->clear();
|
||||
|
||||
// Check if query result is valid and load entities from cache
|
||||
$result2 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
|
||||
@@ -565,21 +607,22 @@ The Cache Mode controls how a particular query interacts with the second-level c
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $em \Doctrine\ORM\EntityManager */
|
||||
/** @var \Doctrine\ORM\EntityManager $em */
|
||||
// Will refresh the query cache and all entities the cache as it reads from the database.
|
||||
$result1 = $em->createQuery('SELECT c FROM Country c ORDER BY c.name')
|
||||
->setCacheMode(Cache::MODE_GET)
|
||||
->setCacheMode(\Doctrine\ORM\Cache::MODE_GET)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
.. note::
|
||||
|
||||
The the default query cache mode is ```Cache::MODE_NORMAL```
|
||||
The default query cache mode is ```Cache::MODE_NORMAL```
|
||||
|
||||
DELETE / UPDATE queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
DQL UPDATE / DELETE statements are ported directly into a database and bypass the second-level cache,
|
||||
DQL UPDATE / DELETE statements are ported directly into a database and bypass
|
||||
the second-level cache.
|
||||
Entities that are already cached will NOT be invalidated.
|
||||
However the cached data could be evicted using the cache API or an special query hint.
|
||||
|
||||
@@ -591,7 +634,7 @@ Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_
|
||||
<?php
|
||||
// Execute and invalidate
|
||||
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
->setHint(Query::HINT_CACHE_EVICT, true)
|
||||
->setHint(\Doctrine\ORM\Query::HINT_CACHE_EVICT, true)
|
||||
->execute();
|
||||
|
||||
|
||||
@@ -622,7 +665,7 @@ Using the repository query cache
|
||||
--------------------------------
|
||||
|
||||
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
|
||||
All persister use a single timestamps cache region keeps track of the last update for each persister,
|
||||
All persisters use a single timestamp cache region to keep track of the last update for each persister,
|
||||
When a query is loaded from cache, the timestamp region is checked for the last update for that persister.
|
||||
Using the last update timestamps as part of the query key invalidate the cache key when an update occurs.
|
||||
|
||||
@@ -641,7 +684,7 @@ Using the last update timestamps as part of the query key invalidate the cache k
|
||||
$em->clear();
|
||||
|
||||
// Reload from database.
|
||||
// At this point the query cache key if not logger valid, the select goes straight
|
||||
// At this point the query cache key is no longer valid, the select goes straight to the database
|
||||
$entities = $em->getRepository('Entity\Country')->findAll();
|
||||
|
||||
Cache API
|
||||
@@ -653,7 +696,7 @@ However, you can use the cache API to check / invalidate cache entries.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/* @var $cache \Doctrine\ORM\Cache */
|
||||
/** @var \Doctrine\ORM\Cache $cache */
|
||||
$cache = $em->getCache();
|
||||
|
||||
$cache->containsEntity('Entity\State', 1) // Check if the cache exists
|
||||
@@ -677,40 +720,35 @@ 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;
|
||||
}
|
||||
|
||||
// Supported
|
||||
/* @var $article Article */
|
||||
/** @var Article $article */
|
||||
$article = $em->find('Article', 1);
|
||||
|
||||
// Supported
|
||||
/* @var $article Article */
|
||||
/** @var Article $article */
|
||||
$article = $em->find('Article', $article);
|
||||
|
||||
// Supported
|
||||
$id = array('source' => 1, 'target' => 2);
|
||||
$id = ['source' => 1, 'target' => 2];
|
||||
$reference = $em->find('Reference', $id);
|
||||
|
||||
// NOT Supported
|
||||
$id = array('source' => new Article(1), 'target' => new Article(2));
|
||||
$id = ['source' => new Article(1), 'target' => new Article(2)];
|
||||
$reference = $em->find('Reference', $id);
|
||||
|
||||
Distributed environments
|
||||
@@ -728,4 +766,5 @@ Paginator
|
||||
~~~~~~~~~
|
||||
|
||||
Count queries generated by ``Doctrine\ORM\Tools\Pagination\Paginator`` are not cached by second-level cache.
|
||||
Although entities and query result are cached count queries will hit the database every time.
|
||||
Although entities and query result are cached, count queries will hit the
|
||||
database every time.
|
||||
|
||||
@@ -10,7 +10,7 @@ we cannot protect you from SQL injection.
|
||||
Please also read the documentation chapter on Security in Doctrine DBAL. This
|
||||
page only handles Security issues in the ORM.
|
||||
|
||||
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
|
||||
- `DBAL Security Page <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/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
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -5,91 +5,67 @@ Doctrine Console
|
||||
----------------
|
||||
|
||||
The Doctrine Console is a Command Line Interface tool for simplifying common
|
||||
administration tasks during the development of a project that uses Doctrine 2.
|
||||
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 developer. There is no
|
||||
access all Commands that were registered by a developer. There is no
|
||||
auto-detection mechanism at work. The Doctrine binary
|
||||
already registers all the commands that currently ship with
|
||||
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``
|
||||
or ``DBAL`` Connection. You have to inject them into the console application
|
||||
using so called Helper-Sets. This requires either the ``db``
|
||||
or the ``em`` helpers to be defined in order to work correctly.
|
||||
All the commands of the Doctrine Console require access to the
|
||||
``EntityManager``. You have to inject it into the console application.
|
||||
|
||||
Whenever you invoke the Doctrine binary the current folder is searched for a
|
||||
``cli-config.php`` file. This file contains the project specific configuration:
|
||||
Here is an example of a the project-specific ``bin/doctrine`` binary.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
|
||||
));
|
||||
$cli->setHelperSet($helperSet);
|
||||
|
||||
When dealing with the ORM package, the EntityManagerHelper is
|
||||
required:
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
|
||||
.. code-block:: php
|
||||
// replace with path to your own project bootstrap file
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
<?php
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
|
||||
));
|
||||
$cli->setHelperSet($helperSet);
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
|
||||
The HelperSet instance has to be generated in a separate file (i.e.
|
||||
``cli-config.php``) that contains typical Doctrine bootstrap code
|
||||
and predefines the needed HelperSet attributes mentioned above. A
|
||||
sample ``cli-config.php`` file looks as follows:
|
||||
$commands = [
|
||||
// If you want to add your own custom console commands,
|
||||
// you can do so here.
|
||||
];
|
||||
|
||||
.. code-block:: php
|
||||
ConsoleRunner::run(
|
||||
new SingleManagerProvider($entityManager),
|
||||
$commands
|
||||
);
|
||||
|
||||
<?php
|
||||
// cli-config.php
|
||||
require_once 'my_bootstrap.php';
|
||||
|
||||
// Any way to access the EntityManager from your application
|
||||
$em = GetMyEntityManager();
|
||||
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()),
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
|
||||
));
|
||||
|
||||
It is important to define a correct HelperSet that Doctrine binary
|
||||
script will ultimately use. The Doctrine Binary will automatically
|
||||
find the first instance of HelperSet in the global variable
|
||||
namespace and use this.
|
||||
|
||||
.. note::
|
||||
.. note::
|
||||
|
||||
You have to adjust this snippet for your specific application or framework
|
||||
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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -173,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
|
||||
|
||||
@@ -204,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
|
||||
|
||||
@@ -219,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
|
||||
-----------------
|
||||
@@ -259,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
|
||||
@@ -346,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::
|
||||
|
||||
@@ -375,7 +348,7 @@ First you need to retrieve the metadata instances with the
|
||||
$em->getConnection()->getSchemaManager()
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
|
||||
$cmf->setEntityManager($em);
|
||||
$metadata = $cmf->getAllMetadata();
|
||||
@@ -396,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::
|
||||
|
||||
@@ -412,7 +385,7 @@ You can also reverse engineer a database using the
|
||||
Runtime vs Development Mapping Validation
|
||||
-----------------------------------------
|
||||
|
||||
For performance reasons Doctrine 2 has to skip some of the
|
||||
For performance reasons Doctrine ORM has to skip some of the
|
||||
necessary validation of metadata mappings. You have to execute
|
||||
this validation in your development workflow to verify the
|
||||
associations are correctly defined.
|
||||
@@ -423,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
|
||||
@@ -517,4 +495,3 @@ HelperSet, like it is described in the configuration section.
|
||||
|
||||
// Runs console application
|
||||
$cli->run();
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ transaction. Without any explicit transaction demarcation from your
|
||||
side, this quickly results in poor performance because transactions
|
||||
are not cheap.
|
||||
|
||||
For the most part, Doctrine 2 already takes care of proper
|
||||
For the most part, Doctrine ORM already takes care of proper
|
||||
transaction demarcation for you: All the write operations
|
||||
(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()``
|
||||
is invoked which wraps all of these changes in a single
|
||||
transaction.
|
||||
|
||||
However, Doctrine 2 also allows (and encourages) you to take over
|
||||
However, Doctrine ORM also allows (and encourages) you to take over
|
||||
and control transaction demarcation yourself.
|
||||
|
||||
These are two ways to deal with transactions when using the
|
||||
@@ -68,7 +68,7 @@ looks like this:
|
||||
// $em instanceof EntityManager
|
||||
$em->getConnection()->beginTransaction(); // suspend auto-commit
|
||||
try {
|
||||
//... do some work
|
||||
// ... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
$em->persist($user);
|
||||
@@ -98,7 +98,7 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
$em->transactional(function($em) {
|
||||
//... do some work
|
||||
// ... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
$em->persist($user);
|
||||
@@ -154,7 +154,7 @@ occurred you should do that with a new ``EntityManager``.
|
||||
Locking Support
|
||||
---------------
|
||||
|
||||
Doctrine 2 offers support for Pessimistic- and Optimistic-locking
|
||||
Doctrine ORM offers support for Pessimistic- and Optimistic-locking
|
||||
strategies natively. This allows to take very fine-grained control
|
||||
over what kind of locking is required for your Entities in your
|
||||
application.
|
||||
@@ -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:
|
||||
@@ -367,7 +389,7 @@ And the change headline action (POST Request):
|
||||
Pessimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine 2 supports Pessimistic Locking at the database level. No
|
||||
Doctrine ORM supports Pessimistic Locking at the database level. No
|
||||
attempt is being made to implement pessimistic locking inside
|
||||
Doctrine, rather vendor-specific and ANSI-SQL commands are used to
|
||||
acquire row-level locks. Every Entity can be part of a pessimistic
|
||||
@@ -376,11 +398,11 @@ lock, there is no special metadata required to use this feature.
|
||||
However for Pessimistic Locking to work you have to disable the
|
||||
Auto-Commit Mode of your Database and start a transaction around
|
||||
your pessimistic lock use-case using the "Approach 2: Explicit
|
||||
Transaction Demarcation" described above. Doctrine 2 will throw an
|
||||
Transaction Demarcation" described above. Doctrine ORM will throw an
|
||||
Exception if you attempt to acquire an pessimistic lock and no
|
||||
transaction is running.
|
||||
|
||||
Doctrine 2 currently supports two pessimistic lock modes:
|
||||
Doctrine ORM currently supports two pessimistic lock modes:
|
||||
|
||||
|
||||
- Pessimistic Write
|
||||
@@ -390,7 +412,7 @@ Doctrine 2 currently supports two pessimistic lock modes:
|
||||
locks other concurrent requests that attempt to update or lock rows
|
||||
in write mode.
|
||||
|
||||
You can use pessimistic locks in three different scenarios:
|
||||
You can use pessimistic locks in four different scenarios:
|
||||
|
||||
|
||||
1. Using
|
||||
@@ -402,8 +424,10 @@ You can use pessimistic locks in three different scenarios:
|
||||
or
|
||||
``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
3. Using
|
||||
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``EntityManager#refresh($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
4. Using
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
or
|
||||
``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)``
|
||||
|
||||
|
||||
|
||||
176
docs/en/reference/typedfieldmapper.rst
Normal file
176
docs/en/reference/typedfieldmapper.rst
Normal file
@@ -0,0 +1,176 @@
|
||||
Implementing a TypedFieldMapper
|
||||
===============================
|
||||
|
||||
.. versionadded:: 2.14
|
||||
|
||||
You can specify custom typed field mapping between PHP type and DBAL type using ``Configuration``
|
||||
and a custom ``Doctrine\ORM\Mapping\TypedFieldMapper`` implementation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$configuration->setTypedFieldMapper(new CustomTypedFieldMapper());
|
||||
|
||||
|
||||
DefaultTypedFieldMapper
|
||||
-----------------------
|
||||
|
||||
By default the ``Doctrine\ORM\Mapping\DefaultTypedFieldMapper`` is used, and you can pass an array of
|
||||
PHP type => DBAL type mappings into its constructor to override the default behavior or add new mappings.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use App\CustomIds\CustomIdObject;
|
||||
use App\DBAL\Type\CustomIdObjectType;
|
||||
|
||||
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
|
||||
CustomIdObject::class => CustomIdObjectType::class,
|
||||
]));
|
||||
|
||||
Then, an entity using the ``CustomIdObject`` typed field will be correctly assigned its DBAL type
|
||||
(``CustomIdObjectType``) without the need of explicit declaration.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'cms_users_typed_with_custom_typed_field')]
|
||||
class UserTypedWithCustomTypedField
|
||||
{
|
||||
#[ORM\Column]
|
||||
public CustomIdObject $customId;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Table(name="cms_users_typed_with_custom_typed_field")
|
||||
*/
|
||||
class UserTypedWithCustomTypedField
|
||||
{
|
||||
/** @Column */
|
||||
public CustomIdObject $customId;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="UserTypedWithCustomTypedField">
|
||||
<field name="customId"/>
|
||||
<!-- -->
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
UserTypedWithCustomTypedField:
|
||||
type: entity
|
||||
fields:
|
||||
customId: ~
|
||||
|
||||
It is perfectly valid to override even the "automatic" mapping rules mentioned above:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use App\DBAL\Type\CustomIntType;
|
||||
|
||||
$configuration->setTypedFieldMapper(new DefaultTypedFieldMapper([
|
||||
'int' => CustomIntType::class,
|
||||
]));
|
||||
|
||||
.. note::
|
||||
|
||||
If chained, once the first ``TypedFieldMapper`` assigns a type to a field, the ``DefaultTypedFieldMapper`` will
|
||||
ignore its mapping and not override it anymore (if it is later in the chain). See below for chaining type mappers.
|
||||
|
||||
|
||||
TypedFieldMapper interface
|
||||
-------------------------
|
||||
The interface ``Doctrine\ORM\Mapping\TypedFieldMapper`` allows you to implement your own
|
||||
typed field mapping logic. It consists of just one function
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Validates & completes the given field mapping based on typed property.
|
||||
*
|
||||
* @param array{fieldName: string, enumType?: string, type?: mixed} $mapping The field mapping to validate & complete.
|
||||
* @param \ReflectionProperty $field
|
||||
*
|
||||
* @return array{fieldName: string, enumType?: string, type?: mixed} The updated mapping.
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array;
|
||||
|
||||
|
||||
ChainTypedFieldMapper
|
||||
---------------------
|
||||
|
||||
The class ``Doctrine\ORM\Mapping\ChainTypedFieldMapper`` allows you to chain multiple ``TypedFieldMapper`` instances.
|
||||
When being evaluated, the ``TypedFieldMapper::validateAndComplete`` is called in the order in which
|
||||
the instances were supplied to the ``ChainTypedFieldMapper`` constructor.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use App\DBAL\Type\CustomIntType;
|
||||
|
||||
$configuration->setTypedFieldMapper(
|
||||
new ChainTypedFieldMapper(
|
||||
DefaultTypedFieldMapper(['int' => CustomIntType::class,]),
|
||||
new CustomTypedFieldMapper()
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
Implementing a TypedFieldMapper
|
||||
-------------------------------
|
||||
|
||||
If you want to assign all ``BackedEnum`` fields to your custom ``BackedEnumDBALType`` or you want to use different
|
||||
DBAL types based on whether the entity field is nullable or not, you can achieve this by implementing your own
|
||||
typed field mapper.
|
||||
|
||||
You need to create a class which implements ``Doctrine\ORM\Mapping\TypedFieldMapper``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
final class CustomEnumTypedFieldMapper implements TypedFieldMapper
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateAndComplete(array $mapping, ReflectionProperty $field): array
|
||||
{
|
||||
$type = $field->getType();
|
||||
|
||||
if (
|
||||
! isset($mapping['type'])
|
||||
&& ($type instanceof ReflectionNamedType)
|
||||
) {
|
||||
if (! $type->isBuiltin() && enum_exists($type->getName())) {
|
||||
$mapping['type'] = BackedEnumDBALType::class;
|
||||
}
|
||||
}
|
||||
|
||||
return $mapping;
|
||||
}
|
||||
}
|
||||
|
||||
.. note::
|
||||
|
||||
Note that this case checks whether the mapping is already assigned, and if yes, it skips it. This is up to your
|
||||
implementation. You can make a "greedy" mapper which will always override the mapping with its own type, or one
|
||||
that behaves like ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
|
||||
@@ -16,11 +16,11 @@ Bidirectional Associations
|
||||
The following rules apply to **bidirectional** associations:
|
||||
|
||||
- The inverse side has to have the ``mappedBy`` attribute of the OneToOne,
|
||||
OneToMany, or ManyToMany mapping declaration. The mappedBy
|
||||
OneToMany, or ManyToMany mapping declaration. The ``mappedBy``
|
||||
attribute contains the name of the association-field on the owning side.
|
||||
- The owning side has to have the ``inversedBy`` attribute of the
|
||||
OneToOne, ManyToOne, or ManyToMany mapping declaration.
|
||||
The inversedBy attribute contains the name of the association-field
|
||||
OneToOne, ManyToOne, or ManyToMany mapping declaration.
|
||||
The ``inversedBy`` attribute contains the name of the association-field
|
||||
on the inverse-side.
|
||||
- ManyToOne is always the owning side of a bidirectional association.
|
||||
- OneToMany is always the inverse side of a bidirectional association.
|
||||
|
||||
@@ -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,11 +34,11 @@ 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
|
||||
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
|
||||
$this->assertInstanceOf('Doctrine\Persistence\Proxy', $objectA);
|
||||
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
|
||||
@@ -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
|
||||
@@ -134,6 +134,10 @@ optimize the performance of the Flush Operation:
|
||||
explicit strategies of notifying the UnitOfWork what objects/properties
|
||||
changed.
|
||||
|
||||
.. note::
|
||||
|
||||
Flush only a single entity with ``$entityManager->flush($entity)`` is deprecated and will be removed in ORM 3.0.
|
||||
(`Details <https://github.com/doctrine/orm/issues/8459>`_)
|
||||
|
||||
Query Internals
|
||||
---------------
|
||||
@@ -198,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
|
||||
@@ -291,44 +295,44 @@ example that encapsulate much of the association management code:
|
||||
<?php
|
||||
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
|
||||
|
||||
@@ -407,7 +412,7 @@ There are two approaches to handle this problem in your code:
|
||||
Transitive persistence / Cascade Operations
|
||||
-------------------------------------------
|
||||
|
||||
Doctrine 2 provides a mechanism for transitive persistence through cascading of certain operations.
|
||||
Doctrine ORM provides a mechanism for transitive persistence through cascading of certain operations.
|
||||
Each association to another entity or a collection of
|
||||
entities can be configured to automatically cascade the following operations to the associated entities:
|
||||
``persist``, ``remove``, ``merge``, ``detach``, ``refresh`` or ``all``.
|
||||
@@ -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()
|
||||
{
|
||||
@@ -463,14 +470,11 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||
<?php
|
||||
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;
|
||||
//...
|
||||
// ...
|
||||
}
|
||||
|
||||
...you can now create a user and an associated comment like this:
|
||||
@@ -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();
|
||||
|
||||
@@ -521,6 +525,8 @@ For each cascade operation that gets activated, Doctrine also
|
||||
applies that operation to the association, be it single or
|
||||
collection valued.
|
||||
|
||||
.. _persistence-by-reachability:
|
||||
|
||||
Persistence by Reachability: Cascade Persist
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -557,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:
|
||||
|
||||
@@ -568,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]);
|
||||
}
|
||||
@@ -610,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:
|
||||
|
||||
@@ -27,7 +27,7 @@ Work that have not yet been persisted are lost.
|
||||
|
||||
.. note::
|
||||
|
||||
Doctrine does NEVER touch the public API of methods in your entity
|
||||
Doctrine NEVER touches the public API of methods in your entity
|
||||
classes (like getters and setters) nor the constructor method.
|
||||
Instead, it uses reflection to get/set data from/to your entity objects.
|
||||
When Doctrine fetches data from DB and saves it back,
|
||||
@@ -48,12 +48,12 @@ headline "Hello World" with the ID 1234:
|
||||
<?php
|
||||
$article = $entityManager->find('CMS\Article', 1234);
|
||||
$article->setHeadline('Hello World dude!');
|
||||
|
||||
|
||||
$article2 = $entityManager->find('CMS\Article', 1234);
|
||||
echo $article2->getHeadline();
|
||||
|
||||
In this case the Article is accessed from the entity manager twice,
|
||||
but modified in between. Doctrine 2 realizes this and will only
|
||||
but modified in between. Doctrine ORM realizes this and will only
|
||||
ever give you access to one instance of the Article with ID 1234,
|
||||
no matter how often do you retrieve it from the EntityManager and
|
||||
even no matter what kind of Query method you are using (find,
|
||||
@@ -95,30 +95,31 @@ from newly opened EntityManager.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
#[Entity]
|
||||
class Article
|
||||
{
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
/** @Column(type="string") */
|
||||
private $headline;
|
||||
|
||||
/** @ManyToOne(targetEntity="User") */
|
||||
private $author;
|
||||
|
||||
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
|
||||
private $comments;
|
||||
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
|
||||
#[Column(type: 'string')]
|
||||
private string $headline;
|
||||
|
||||
#[ManyToOne(targetEntity: User::class)]
|
||||
private User|null $author = null;
|
||||
|
||||
/** @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);
|
||||
|
||||
This code only retrieves the ``Article`` instance with id 1 executing
|
||||
@@ -139,50 +140,28 @@ your code. See the following code:
|
||||
|
||||
<?php
|
||||
$article = $em->find('Article', 1);
|
||||
|
||||
|
||||
// accessing a method of the user instance triggers the lazy-load
|
||||
echo "Author: " . $article->getAuthor()->getName() . "\n";
|
||||
|
||||
|
||||
// Lazy Loading Proxies pass instanceof tests:
|
||||
if ($article->getAuthor() instanceof User) {
|
||||
// a User Proxy is a generated "UserProxy" class
|
||||
}
|
||||
|
||||
|
||||
// accessing the comments as an iterator triggers the lazy-load
|
||||
// retrieving ALL the comments of this article from the database
|
||||
// using a single SELECT statement
|
||||
foreach ($article->getComments() as $comment) {
|
||||
echo $comment->getText() . "\n\n";
|
||||
}
|
||||
|
||||
|
||||
// Article::$comments passes instanceof tests for the Collection interface
|
||||
// But it will NOT pass for the ArrayCollection interface
|
||||
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
|
||||
echo "This will always be true!";
|
||||
}
|
||||
|
||||
A slice of the generated proxy classes code looks like the
|
||||
following piece of code. A real proxy class override ALL public
|
||||
methods along the lines of the ``getName()`` method shown below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class UserProxy extends User implements Proxy
|
||||
{
|
||||
private function _load()
|
||||
{
|
||||
// lazy loading code
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$this->_load();
|
||||
return parent::getName();
|
||||
}
|
||||
// .. other public methods of User
|
||||
}
|
||||
|
||||
.. warning::
|
||||
|
||||
Traversing the object graph for parts that are lazy-loaded will
|
||||
@@ -275,7 +254,7 @@ which means that its persistent state will be deleted once
|
||||
for and appear in query and collection results. See
|
||||
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
|
||||
for more information.
|
||||
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
@@ -304,7 +283,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
|
||||
@@ -318,7 +297,7 @@ Deleting an object with all its associated objects can be achieved
|
||||
in multiple ways with very different performance impacts.
|
||||
|
||||
|
||||
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2
|
||||
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine ORM
|
||||
will fetch this association. If its a Single association it will
|
||||
pass this entity to
|
||||
``EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()``.
|
||||
@@ -338,9 +317,9 @@ in multiple ways with very different performance impacts.
|
||||
|
||||
.. note::
|
||||
|
||||
Calling ``remove`` on an entity will remove the object from the identiy
|
||||
map and therefore detach it. Querying the same entity again, for example
|
||||
via a lazy loaded relation, will return a new object.
|
||||
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.
|
||||
|
||||
|
||||
Detaching entities
|
||||
@@ -388,8 +367,7 @@ automatically without invoking the ``detach`` method:
|
||||
currently managed by the EntityManager instance become detached.
|
||||
- When serializing an entity. The entity retrieved upon subsequent
|
||||
unserialization will be detached (This is the case for all entities
|
||||
that are serialized and stored in some cache, i.e. when using the
|
||||
Query Result Cache).
|
||||
that are serialized and stored in some cache).
|
||||
|
||||
The ``detach`` operation is usually not as frequently needed and
|
||||
used as ``persist`` and ``remove``.
|
||||
@@ -414,14 +392,6 @@ Example:
|
||||
// $entity now refers to the fully managed copy returned by the merge operation.
|
||||
// The EntityManager $em now manages the persistence of $entity as usual.
|
||||
|
||||
.. note::
|
||||
|
||||
When you want to serialize/unserialize entities you
|
||||
have to make all entity properties protected, never private. The
|
||||
reason for this is, if you serialize a class that was a proxy
|
||||
instance before, the private variables won't be serialized and a
|
||||
PHP Notice is thrown.
|
||||
|
||||
|
||||
The semantics of the merge operation, applied to an entity X, are
|
||||
as follows:
|
||||
@@ -653,7 +623,7 @@ just created via the "new" operator).
|
||||
Querying
|
||||
--------
|
||||
|
||||
Doctrine 2 provides the following ways, in increasing level of
|
||||
Doctrine ORM provides the following ways, in increasing level of
|
||||
power and flexibility, to query for persistent objects. You should
|
||||
always start with the simplest one that suits your needs.
|
||||
|
||||
@@ -700,13 +670,13 @@ methods on a repository as follows:
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
|
||||
|
||||
// All users that are 20 years old
|
||||
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20));
|
||||
|
||||
|
||||
// All users that are 20 years old and have a surname of 'Miller'
|
||||
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller'));
|
||||
|
||||
|
||||
// A single user by its nickname
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
|
||||
|
||||
@@ -742,7 +712,7 @@ examples are equivalent:
|
||||
<?php
|
||||
// A single user by its nickname
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
|
||||
|
||||
|
||||
// A single user by its nickname (__call magic)
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
|
||||
|
||||
@@ -757,8 +727,6 @@ Additionally, you can just count the result of the provided conditions when you
|
||||
By Criteria
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
The Repository implement the ``Doctrine\Common\Collections\Selectable``
|
||||
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
|
||||
and pass them to the ``matching($criteria)`` method.
|
||||
@@ -800,7 +768,7 @@ A DQL query is represented by an instance of the
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
|
||||
|
||||
// All users with an age between 20 and 30 (inclusive).
|
||||
$q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30");
|
||||
$users = $q->getResult();
|
||||
@@ -836,7 +804,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.
|
||||
@@ -845,21 +813,21 @@ 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
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
@@ -872,7 +840,5 @@ You can access your repository now by calling:
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager
|
||||
|
||||
|
||||
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
|
||||
|
||||
|
||||
|
||||
@@ -8,9 +8,7 @@ The XML driver is backed by an XML Schema document that describes
|
||||
the structure of a mapping document. The most recent version of the
|
||||
XML Schema document is available online at
|
||||
`https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
In order to point to the latest version of the document of a
|
||||
particular stable release branch, just append the release number,
|
||||
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
|
||||
The most convenient way to work with
|
||||
XML mapping files is to use an IDE/editor that can provide
|
||||
code-completion based on such an XML Schema document. The following
|
||||
is an outline of a XML mapping document with the proper xmlns/xsi
|
||||
@@ -18,9 +16,9 @@ setup for the latest code in trunk.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
...
|
||||
@@ -104,9 +102,9 @@ of several common elements:
|
||||
|
||||
// Doctrine.Tests.ORM.Mapping.User.dcm.xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
@@ -208,10 +206,10 @@ Optional attributes:
|
||||
- **inheritance-type** - The type of inheritance, defaults to none. A
|
||||
more detailed description follows in the
|
||||
*Defining Inheritance Mappings* section.
|
||||
- **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not
|
||||
- **read-only** - Specifies that this entity is marked as read only and not
|
||||
considered for change-tracking. Entities of this type can be persisted
|
||||
and removed though.
|
||||
- **schema** - (>= 2.5) The schema the table lies in, for platforms that support schemas
|
||||
- **schema** - The schema the table lies in, for platforms that support schemas
|
||||
|
||||
Defining Fields
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -258,6 +256,11 @@ Optional attributes:
|
||||
table? Defaults to false.
|
||||
- nullable - Should this field allow NULL as a value? Defaults to
|
||||
false.
|
||||
- insertable - Should this field be inserted? Defaults to true.
|
||||
- updatable - Should this field be updated? Defaults to true.
|
||||
- generated - Enum of the values ALWAYS, INSERT, NEVER that determines if
|
||||
generated value must be fetched from database after INSERT or UPDATE.
|
||||
Defaults to "NEVER".
|
||||
- version - Should this field be used for optimistic locking? Only
|
||||
works on fields with type integer or datetime.
|
||||
- scale - Scale of a decimal type.
|
||||
@@ -293,7 +296,7 @@ Defining Identity and Generator Strategies
|
||||
|
||||
An entity has to have at least one ``<id />`` element. For
|
||||
composite keys you can specify more than one id-element, however
|
||||
surrogate keys are recommended for use with Doctrine 2. The Id
|
||||
surrogate keys are recommended for use with Doctrine ORM. The Id
|
||||
field allows to define properties of the identifier and allows a
|
||||
subset of the ``<field />`` element attributes:
|
||||
|
||||
@@ -691,6 +694,7 @@ specified by their respective tags:
|
||||
- ``<cascade-merge />``
|
||||
- ``<cascade-remove />``
|
||||
- ``<cascade-refresh />``
|
||||
- ``<cascade-detach />``
|
||||
|
||||
Join Column Element
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -765,9 +769,9 @@ entity relationship. You can define this in XML with the "association-key" attri
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
YAML Mapping
|
||||
============
|
||||
|
||||
.. note::
|
||||
.. warning::
|
||||
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.
|
||||
|
||||
|
||||
@@ -41,9 +41,10 @@
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/partial-objects
|
||||
reference/annotations-reference
|
||||
reference/attributes-reference
|
||||
reference/xml-mapping
|
||||
reference/yaml-mapping
|
||||
reference/annotations-reference
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
@@ -72,7 +73,6 @@
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
|
||||
@@ -43,9 +43,10 @@ Reference Guide
|
||||
reference/native-sql
|
||||
reference/change-tracking-policies
|
||||
reference/partial-objects
|
||||
reference/annotations-reference
|
||||
reference/attributes-reference
|
||||
reference/xml-mapping
|
||||
reference/yaml-mapping
|
||||
reference/annotations-reference
|
||||
reference/php-mapping
|
||||
reference/caching
|
||||
reference/improving-performance
|
||||
@@ -74,7 +75,6 @@ Cookbook
|
||||
cookbook/dql-user-defined-functions
|
||||
cookbook/implementing-arrayaccess-for-domain-objects
|
||||
cookbook/implementing-the-notify-changetracking-policy
|
||||
cookbook/implementing-wakeup-or-clone
|
||||
cookbook/resolve-target-entity-listener
|
||||
cookbook/sql-table-prefixes
|
||||
cookbook/strategy-cookbook-introduction
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
Composite and Foreign Keys as Primary Key
|
||||
=========================================
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept
|
||||
and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases.
|
||||
For Doctrine 2.0 composite keys of primitive data-types are supported, for Doctrine 2.1 even foreign keys as
|
||||
Doctrine ORM supports composite primary keys natively. Composite keys are a very powerful relational database concept
|
||||
and we took good care to make sure Doctrine ORM supports as many of the composite primary key use-cases.
|
||||
For Doctrine ORM composite keys of primitive data-types are supported, even foreign keys as
|
||||
primary keys are supported.
|
||||
|
||||
This tutorial shows how the semantics of composite primary keys work and how they map to the database.
|
||||
@@ -19,13 +17,40 @@ the ID fields have to have their values set before you call ``EntityManager#pers
|
||||
Primitive Types only
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Even in version 2.0 you can have composite keys as long as they only consist of the primitive types
|
||||
You can have composite keys as long as they only consist of the primitive types
|
||||
``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name
|
||||
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;
|
||||
@@ -36,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)
|
||||
{
|
||||
@@ -46,23 +71,44 @@ 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"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="VehicleCatalogue\Model\Car">
|
||||
@@ -120,10 +166,6 @@ and to ``year`` to the related entities.
|
||||
Identity through foreign Entities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note::
|
||||
|
||||
Identity through foreign entities is only supported with Doctrine 2.1
|
||||
|
||||
There are tons of use-cases where the identity of an Entity should be determined by the entity
|
||||
of one or many parent entities.
|
||||
|
||||
@@ -137,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.
|
||||
|
||||
@@ -161,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);
|
||||
}
|
||||
@@ -182,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)
|
||||
{
|
||||
@@ -198,17 +241,62 @@ 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="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<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" />
|
||||
@@ -243,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
|
||||
@@ -294,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 $payed = 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
|
||||
|
||||
|
||||
@@ -3,21 +3,21 @@ Extra Lazy Associations
|
||||
|
||||
.. versionadded:: 2.1
|
||||
|
||||
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog
|
||||
In many cases associations between entities can get pretty large. Even in a simple scenario like a blog.
|
||||
where posts can be commented, you always have to assume that a post draws hundreds of comments.
|
||||
In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This
|
||||
In Doctrine ORM if you accessed an association it would always get loaded completely into memory. This
|
||||
can lead to pretty serious performance problems, if your associations contain several hundreds or thousands
|
||||
of entities.
|
||||
|
||||
With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations
|
||||
Doctrine ORM includes a feature called **Extra Lazy** for associations. Associations
|
||||
are marked as **Lazy** by default, which means the whole collection object for an association is populated
|
||||
the first time its accessed. If you mark an association as extra lazy the following methods on collections
|
||||
can be called without triggering a full load of the collection:
|
||||
|
||||
- ``Collection#contains($entity)``
|
||||
- ``Collection#containsKey($key)`` (available with Doctrine 2.5)
|
||||
- ``Collection#containsKey($key)``
|
||||
- ``Collection#count()``
|
||||
- ``Collection#get($key)`` (available with Doctrine 2.4)
|
||||
- ``Collection#get($key)``
|
||||
- ``Collection#slice($offset, $length = null)``
|
||||
|
||||
For each of the above methods the following semantics apply:
|
||||
@@ -25,7 +25,7 @@ For each of the above methods the following semantics apply:
|
||||
- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database.
|
||||
- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed.
|
||||
|
||||
Additionally even with Doctrine 2.0 the following methods do not trigger the collection load:
|
||||
Additionally even with Doctrine ORM the following methods do not trigger the collection load:
|
||||
|
||||
- ``Collection#add($entity)``
|
||||
- ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does
|
||||
@@ -40,7 +40,7 @@ easily using a combination of ``count`` and ``slice``.
|
||||
``removeElement`` directly issued DELETE queries to the database from
|
||||
version 2.4.0 to 2.7.0. This circumvents the flush operation and might run
|
||||
outside a transactional boundary if you don't create one yourself. We
|
||||
consider this a critical bug in the assumptio of how the ORM works and
|
||||
consider this a critical bug in the assumption of how the ORM works and
|
||||
reverted ``removeElement`` EXTRA_LAZY behavior in 2.7.1.
|
||||
|
||||
|
||||
@@ -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,16 +77,17 @@ 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
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
|
||||
@@ -92,4 +106,3 @@ switch to extra lazy as shown in these examples:
|
||||
targetEntity: CmsUser
|
||||
mappedBy: groups
|
||||
fetch: EXTRA_LAZY
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@ development is said to use the *Database First* approach to Doctrine.
|
||||
|
||||
In this workflow you would modify the database schema first and then
|
||||
regenerate the PHP code to use with this schema. You need a flexible
|
||||
code-generator for this task and up to Doctrine 2.2, the code generator hasn't
|
||||
been flexible enough to achieve this.
|
||||
code-generator for this task.
|
||||
|
||||
We spinned off a subproject, Doctrine CodeGenerator, that will fill this gap and
|
||||
allow you to do *Database First* development.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Pagination
|
||||
==========
|
||||
|
||||
.. versionadded:: 2.2
|
||||
|
||||
Starting with version 2.2 Doctrine ships with a Paginator for DQL queries. It
|
||||
Doctrine ORM ships with a Paginator for DQL queries. It
|
||||
has a very simple API and implements the SPL interfaces ``Countable`` and
|
||||
``IteratorAggregate``.
|
||||
|
||||
@@ -41,3 +39,7 @@ collection. You can disable this behavior by setting the
|
||||
``$fetchJoinCollection`` flag to ``false``; in that case only 2 instead of the 3 queries
|
||||
described are executed. We hope to automate the detection for this in
|
||||
the future.
|
||||
|
||||
.. note::
|
||||
|
||||
``$fetchJoinCollection`` flag set to ``true`` might affect results if you use aggregations in your query.
|
||||
@@ -1,13 +1,9 @@
|
||||
Working with Indexed Associations
|
||||
=================================
|
||||
|
||||
.. note::
|
||||
|
||||
This feature is available from version 2.1 of Doctrine.
|
||||
|
||||
Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in
|
||||
Doctrine ORM collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in
|
||||
the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY``
|
||||
was used. Starting with Doctrine 2.1 you can index your collections by a value in the related entity.
|
||||
was used. You can index your collections by a value in the related entity.
|
||||
This is a first step towards full ordered hashmap support through the Doctrine ORM.
|
||||
The feature works like an implicit ``INDEX BY`` for the selected association but has several
|
||||
downsides also:
|
||||
@@ -27,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.
|
||||
@@ -34,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;
|
||||
@@ -51,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)
|
||||
{
|
||||
@@ -71,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.");
|
||||
@@ -95,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();
|
||||
}
|
||||
@@ -104,9 +161,9 @@ The code and mappings for the Market entity looks like this:
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Market">
|
||||
@@ -146,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;
|
||||
@@ -161,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)
|
||||
{
|
||||
@@ -181,7 +269,7 @@ here are the code and mappings for it:
|
||||
$market->addStock($this);
|
||||
}
|
||||
|
||||
public function getSymbol()
|
||||
public function getSymbol(): string
|
||||
{
|
||||
return $this->symbol;
|
||||
}
|
||||
@@ -190,9 +278,9 @@ here are the code and mappings for it:
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
<doctrine-mapping xmlns="https://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
|
||||
@@ -253,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:
|
||||
@@ -291,6 +379,5 @@ Outlook into the Future
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For the inverse side of a many-to-many associations there will be a way to persist the keys and the order
|
||||
as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
as a third and fourth parameter into the join table. This feature is discussed in `#2817 <https://github.com/doctrine/orm/issues/2817>`_
|
||||
This feature cannot be implemented for one-to-many associations, because they are never the owning side.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user