mirror of
https://github.com/doctrine/orm.git
synced 2026-04-02 20:32:19 +02:00
Compare commits
985 Commits
2.20.10
...
old-protot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f7a7a58ae | ||
|
|
51063f502f | ||
|
|
e8c4d7b177 | ||
|
|
2a98a98cd3 | ||
|
|
01de90c141 | ||
|
|
dddd899322 | ||
|
|
72bc09926d | ||
|
|
49dd4c81d0 | ||
|
|
3460805efe | ||
|
|
722f4a86b3 | ||
|
|
1af6270095 | ||
|
|
9a8e1fb477 | ||
|
|
426ddb87a5 | ||
|
|
e7d2ac5277 | ||
|
|
d1302eabad | ||
|
|
5592535a56 | ||
|
|
a3e53b6a23 | ||
|
|
8c259ea5cb | ||
|
|
7de263c740 | ||
|
|
c7faafa192 | ||
|
|
f9a01ae9c4 | ||
|
|
c673a3daf2 | ||
|
|
e77091399a | ||
|
|
5b3f9ee3c7 | ||
|
|
eae20efb24 | ||
|
|
8d0c6a8746 | ||
|
|
ff65cae170 | ||
|
|
662a71b08a | ||
|
|
b06efe3408 | ||
|
|
f12d24cce7 | ||
|
|
6c7bb5a273 | ||
|
|
027344969e | ||
|
|
9190b73d72 | ||
|
|
df7e000ab0 | ||
|
|
533d09467f | ||
|
|
c9fc364c2c | ||
|
|
f2624e953e | ||
|
|
d528f70568 | ||
|
|
ccbe5ded06 | ||
|
|
21bc805dc9 | ||
|
|
b07393ece8 | ||
|
|
1488955ee3 | ||
|
|
5afceb9f10 | ||
|
|
4bfa22353a | ||
|
|
8c125b1065 | ||
|
|
0187cf53a2 | ||
|
|
05d54788fd | ||
|
|
a896421be2 | ||
|
|
4993c4ae9b | ||
|
|
376c5e1628 | ||
|
|
8601d9e525 | ||
|
|
4e988bb103 | ||
|
|
d606941284 | ||
|
|
fa7802b1c7 | ||
|
|
4f8027aa62 | ||
|
|
8220bb3ced | ||
|
|
57b72b1390 | ||
|
|
dabbfc70a9 | ||
|
|
9906068426 | ||
|
|
b115284932 | ||
|
|
6971e74b4a | ||
|
|
99b7d1e9d4 | ||
|
|
906c1465c2 | ||
|
|
04762015c6 | ||
|
|
f185a6f30f | ||
|
|
c35142f0ae | ||
|
|
3cda3f1ae3 | ||
|
|
9b8b5d9bf6 | ||
|
|
e1bb9e005d | ||
|
|
b9880bb30f | ||
|
|
becf73050e | ||
|
|
27f431c628 | ||
|
|
5b15a614a7 | ||
|
|
e747f7b9c4 | ||
|
|
2b0e49d173 | ||
|
|
b2f27f6310 | ||
|
|
3af277ca55 | ||
|
|
fb50004d58 | ||
|
|
6bf8d61ffc | ||
|
|
057a97d93c | ||
|
|
b5c4c7aaec | ||
|
|
9bd6c59285 | ||
|
|
a988d5808e | ||
|
|
8c451a6a66 | ||
|
|
96d22a6ca8 | ||
|
|
cc57d33e65 | ||
|
|
55b06ea6e1 | ||
|
|
35a9f20cc4 | ||
|
|
93a18c4b2e | ||
|
|
cdd64e7bcb | ||
|
|
6f07bbf482 | ||
|
|
31d9626b61 | ||
|
|
84170a735d | ||
|
|
b58a73aed3 | ||
|
|
d7a385105e | ||
|
|
de18565fdc | ||
|
|
cecd03d920 | ||
|
|
ce9d4c739d | ||
|
|
4ce24a4d66 | ||
|
|
d964eb6d50 | ||
|
|
4885e1c7fe | ||
|
|
42f08f6e3e | ||
|
|
23a0608c47 | ||
|
|
2b734405d2 | ||
|
|
0a34e4cef6 | ||
|
|
b8ebde9d07 | ||
|
|
a6a9455bdd | ||
|
|
6a6370b843 | ||
|
|
706de46ef1 | ||
|
|
b4338ad0d8 | ||
|
|
b762189c97 | ||
|
|
398ddb426f | ||
|
|
8bb91280ed | ||
|
|
87a6eef400 | ||
|
|
bf14cca3f3 | ||
|
|
5ef41cc923 | ||
|
|
01a4292b0d | ||
|
|
ebb53acda9 | ||
|
|
d1bcb1f0a0 | ||
|
|
04036f2244 | ||
|
|
6c0537325c | ||
|
|
1ab9d0b194 | ||
|
|
01f26bba53 | ||
|
|
d8f67108c9 | ||
|
|
2167536792 | ||
|
|
34933416d6 | ||
|
|
93d55d7fbc | ||
|
|
e8eaf8386d | ||
|
|
97e275ff31 | ||
|
|
5e3db26738 | ||
|
|
71393f7d5a | ||
|
|
ed61f0e647 | ||
|
|
11cfaf1233 | ||
|
|
08d34341c5 | ||
|
|
5ac805fc4d | ||
|
|
6615520bf7 | ||
|
|
541fb5968d | ||
|
|
22bf778711 | ||
|
|
322516337e | ||
|
|
d00453dc7e | ||
|
|
0cfc82ddae | ||
|
|
e2189307f4 | ||
|
|
b5ba7cddd5 | ||
|
|
2269510e8a | ||
|
|
4bc261aa46 | ||
|
|
d20dfcfa8d | ||
|
|
8744d1936f | ||
|
|
a5ed7ed89c | ||
|
|
2e9f533746 | ||
|
|
3e0b70b4dc | ||
|
|
1feac20c72 | ||
|
|
3cef55ec39 | ||
|
|
cd57cafe47 | ||
|
|
fa4d3bf799 | ||
|
|
7c9ab76896 | ||
|
|
c4d335ed10 | ||
|
|
5d6b5a706f | ||
|
|
62de42cfa1 | ||
|
|
f4cd3df403 | ||
|
|
929bcaf704 | ||
|
|
4594380bf4 | ||
|
|
d250ee79c5 | ||
|
|
4d7ffa0e4f | ||
|
|
65c13cf15b | ||
|
|
fc62b8f997 | ||
|
|
4d49b7778c | ||
|
|
c780f014b9 | ||
|
|
97248866ce | ||
|
|
dd8fcfd851 | ||
|
|
54beb95f68 | ||
|
|
d0b47f3aa9 | ||
|
|
8a79638faa | ||
|
|
6dc46e78cc | ||
|
|
0c2ccec915 | ||
|
|
72971fd438 | ||
|
|
cf94ec8136 | ||
|
|
d81284b506 | ||
|
|
b0825e490b | ||
|
|
91346c41e7 | ||
|
|
1760b4f655 | ||
|
|
e6a79ef9b2 | ||
|
|
15e49aff35 | ||
|
|
5839553356 | ||
|
|
5a9fe624b6 | ||
|
|
b40c2c6b78 | ||
|
|
36aeed46f8 | ||
|
|
ce76688865 | ||
|
|
2f520244df | ||
|
|
a59ae2e5e3 | ||
|
|
2431bc4076 | ||
|
|
6219f72dc6 | ||
|
|
6ff677d2ab | ||
|
|
730543f17e | ||
|
|
1157a845ec | ||
|
|
f62cccbfd1 | ||
|
|
a20373449c | ||
|
|
362ccb4d24 | ||
|
|
2df74442a0 | ||
|
|
b9feae09c0 | ||
|
|
5c773554dd | ||
|
|
bfa65207b0 | ||
|
|
53b7f9d6f8 | ||
|
|
afd79c189f | ||
|
|
8d83dfa05c | ||
|
|
d311888ef9 | ||
|
|
46a880ac0f | ||
|
|
f4c84897ba | ||
|
|
890d544161 | ||
|
|
87ac04d10e | ||
|
|
7b261676d2 | ||
|
|
da69537408 | ||
|
|
d6ec5a3861 | ||
|
|
82972f697a | ||
|
|
0538f93913 | ||
|
|
0977c8926a | ||
|
|
2500025ee0 | ||
|
|
5e57fcf9ee | ||
|
|
7d34b64885 | ||
|
|
2245ad2751 | ||
|
|
68635faf83 | ||
|
|
6f3ca36b08 | ||
|
|
cb3d4caec2 | ||
|
|
863dabe73f | ||
|
|
5ac6a83560 | ||
|
|
7ad0dcbd88 | ||
|
|
1d649ad952 | ||
|
|
c4c489e7d9 | ||
|
|
f319f10d2e | ||
|
|
1b5bd54290 | ||
|
|
847d5bc666 | ||
|
|
e540c4a8c4 | ||
|
|
b361aa438e | ||
|
|
396b7f6718 | ||
|
|
39c5dcb79d | ||
|
|
c17339e64e | ||
|
|
4179a9676f | ||
|
|
e16500ae72 | ||
|
|
f7309d898f | ||
|
|
28f78a0e60 | ||
|
|
8bcd13f5c7 | ||
|
|
079f398bd2 | ||
|
|
035d9edf95 | ||
|
|
984641d20f | ||
|
|
f584bae121 | ||
|
|
9ce1f0d13f | ||
|
|
0cbc87a888 | ||
|
|
f4ed9534cc | ||
|
|
497bbfcec8 | ||
|
|
2d194dee53 | ||
|
|
76bc652d88 | ||
|
|
d98d8bb0c2 | ||
|
|
55f238f790 | ||
|
|
2726636e55 | ||
|
|
305308278a | ||
|
|
c5191fa3f9 | ||
|
|
e5b88b3fe5 | ||
|
|
fae518aaef | ||
|
|
5756a7fc73 | ||
|
|
3d9a4b8ed8 | ||
|
|
12b28571f5 | ||
|
|
ec573d1b75 | ||
|
|
347e33e345 | ||
|
|
6ad04f7d51 | ||
|
|
f6cc1290d2 | ||
|
|
bbd2460e06 | ||
|
|
9019297c55 | ||
|
|
1ed8bc705f | ||
|
|
063c402dd5 | ||
|
|
46b695d67c | ||
|
|
b881f14b4d | ||
|
|
e2ea883786 | ||
|
|
77e3e5c96c | ||
|
|
c7718efff8 | ||
|
|
42dc7830d7 | ||
|
|
10aac31e8d | ||
|
|
16a1877d58 | ||
|
|
6d282cdc43 | ||
|
|
491a382f91 | ||
|
|
563833340a | ||
|
|
b128753960 | ||
|
|
79f17d02f7 | ||
|
|
28e7e5392c | ||
|
|
46d342d0cf | ||
|
|
3fd15f0cc7 | ||
|
|
8c16a20fd2 | ||
|
|
6ec3ff02b7 | ||
|
|
39634ad91e | ||
|
|
1e60554556 | ||
|
|
ed62eb8672 | ||
|
|
0998a46480 | ||
|
|
50b35fc6aa | ||
|
|
9f4a6922a4 | ||
|
|
9d30fdf9c7 | ||
|
|
d6da264404 | ||
|
|
fbca71af39 | ||
|
|
25b5aa6570 | ||
|
|
c030a213f5 | ||
|
|
2f0b5a503a | ||
|
|
cf13e0d10f | ||
|
|
c2c6c623f3 | ||
|
|
5db00b15d2 | ||
|
|
d2a679a780 | ||
|
|
398e1128f2 | ||
|
|
4132f408df | ||
|
|
c0453603e1 | ||
|
|
d9b0ab7b41 | ||
|
|
119610b81e | ||
|
|
59b5e5ae6c | ||
|
|
b7a2434ef7 | ||
|
|
82796e8711 | ||
|
|
145d5139cb | ||
|
|
ea12b6f82f | ||
|
|
ae166d773b | ||
|
|
de31b2abc9 | ||
|
|
b1bec0ee6c | ||
|
|
9b8f9786d9 | ||
|
|
fdfd2de658 | ||
|
|
fcf012075a | ||
|
|
2ac4142e5f | ||
|
|
967d34473e | ||
|
|
dd3c5c8dc3 | ||
|
|
c4c59df1b4 | ||
|
|
ac92e9f8d3 | ||
|
|
54833ec31b | ||
|
|
1b6a1ee315 | ||
|
|
6fb6b9d32d | ||
|
|
1f0660dcfd | ||
|
|
1afbf141fc | ||
|
|
d372416350 | ||
|
|
f6581657de | ||
|
|
935f3606a2 | ||
|
|
cf8f71e56e | ||
|
|
b01bb234f1 | ||
|
|
0bb3624404 | ||
|
|
5f6d758ddc | ||
|
|
88f6485dea | ||
|
|
15a149c9f6 | ||
|
|
d199c92c95 | ||
|
|
c98f40251a | ||
|
|
41292e85df | ||
|
|
dfd9d1b9e7 | ||
|
|
3570057358 | ||
|
|
7eb6e00940 | ||
|
|
f5a0b568f3 | ||
|
|
64dffe0ede | ||
|
|
8836954787 | ||
|
|
3ce3aa978f | ||
|
|
31e19239d1 | ||
|
|
86dc7f46a1 | ||
|
|
042cff9f22 | ||
|
|
5f7210b441 | ||
|
|
3ac8930613 | ||
|
|
29c86ff5e9 | ||
|
|
43c252458b | ||
|
|
a754acb44a | ||
|
|
1a14c85c54 | ||
|
|
9f572d4a4a | ||
|
|
1bbdc5b597 | ||
|
|
1b845c7c84 | ||
|
|
f3c72dc2d1 | ||
|
|
e5ef92555b | ||
|
|
2be99346b1 | ||
|
|
ec508aff81 | ||
|
|
9f8af8ee4c | ||
|
|
a0cd82b9bc | ||
|
|
89e39b8192 | ||
|
|
554489c7e8 | ||
|
|
bb1fe1d6c9 | ||
|
|
cf548e9b02 | ||
|
|
70d6150412 | ||
|
|
09d3226db9 | ||
|
|
449a6ead69 | ||
|
|
99c6a510e6 | ||
|
|
d00cabafbf | ||
|
|
61778f95d4 | ||
|
|
f100bcec5f | ||
|
|
f4ef0a1d5f | ||
|
|
bbc1ed93b1 | ||
|
|
2343a58ffe | ||
|
|
bd6ae78940 | ||
|
|
a0e414ba57 | ||
|
|
d952c1247e | ||
|
|
07a5861503 | ||
|
|
45c3805edf | ||
|
|
aadf295b9a | ||
|
|
91acb40433 | ||
|
|
5fde371852 | ||
|
|
67fe52f36c | ||
|
|
849d0ac3cc | ||
|
|
3e1d124f23 | ||
|
|
408704b845 | ||
|
|
f6fde34297 | ||
|
|
b0cade71ba | ||
|
|
46ea9d1b52 | ||
|
|
9e57633204 | ||
|
|
faab3225bd | ||
|
|
86f3b6afe7 | ||
|
|
3d3f8b7a8b | ||
|
|
98b9a7dd34 | ||
|
|
6e4daa3997 | ||
|
|
9179aeb343 | ||
|
|
0b04aa9000 | ||
|
|
e6fa8622d7 | ||
|
|
df794b648f | ||
|
|
695edb9fdc | ||
|
|
d376afb53c | ||
|
|
0822f8a456 | ||
|
|
bbf0c0355d | ||
|
|
cbfd52e2fe | ||
|
|
6cc43d884b | ||
|
|
e7e4247395 | ||
|
|
3035470d06 | ||
|
|
92445d095e | ||
|
|
4e2a2975c2 | ||
|
|
e479e0daf4 | ||
|
|
f41c6083c3 | ||
|
|
ab03f16e03 | ||
|
|
f351f5ab3b | ||
|
|
6aabdec97b | ||
|
|
df0cc3452b | ||
|
|
65ebadec49 | ||
|
|
38032c166e | ||
|
|
35d8c87fad | ||
|
|
ec265bf3a3 | ||
|
|
9f7df926cf | ||
|
|
09266d5361 | ||
|
|
46e27d0cde | ||
|
|
a31ce59097 | ||
|
|
0a00a3f044 | ||
|
|
e64820f21e | ||
|
|
0b7d878cd3 | ||
|
|
0151657ea1 | ||
|
|
06ffd851c7 | ||
|
|
97534219db | ||
|
|
df94e79ece | ||
|
|
4509c770da | ||
|
|
bc0c0986c5 | ||
|
|
e0ee4b1ad7 | ||
|
|
bcc9da2f4a | ||
|
|
e351954ec6 | ||
|
|
1a435b2de4 | ||
|
|
8b66f88661 | ||
|
|
1c7770794b | ||
|
|
692a6d941f | ||
|
|
af0b0dbdc3 | ||
|
|
193563e3ed | ||
|
|
eef6308b19 | ||
|
|
1751560970 | ||
|
|
bc44d8bceb | ||
|
|
9d27f22ee6 | ||
|
|
e2d077c62d | ||
|
|
7d37fe0060 | ||
|
|
8cc949808a | ||
|
|
7d93958cba | ||
|
|
43eebb270f | ||
|
|
52769aa118 | ||
|
|
762fdd166e | ||
|
|
2de6c807c8 | ||
|
|
c181836233 | ||
|
|
a346c3f84e | ||
|
|
93558d4786 | ||
|
|
579f084202 | ||
|
|
79282317d1 | ||
|
|
da7288e541 | ||
|
|
c14dbc2409 | ||
|
|
e559f823ea | ||
|
|
63d5a07f06 | ||
|
|
d72936caab | ||
|
|
e98654342f | ||
|
|
217d28f767 | ||
|
|
e6557cbee3 | ||
|
|
b44ba04d1f | ||
|
|
ee4e267595 | ||
|
|
13f173a126 | ||
|
|
7fc285dd32 | ||
|
|
346d2380bd | ||
|
|
d791f7f6ca | ||
|
|
d455a023f8 | ||
|
|
2ccf239628 | ||
|
|
d559c933e1 | ||
|
|
45ea294233 | ||
|
|
d4e02aadc0 | ||
|
|
4476ec467d | ||
|
|
ddb3cd17bd | ||
|
|
3e8aa71554 | ||
|
|
82c91a2606 | ||
|
|
2cd3f1f909 | ||
|
|
7968dfae76 | ||
|
|
577a590987 | ||
|
|
9e122e7a89 | ||
|
|
9855f56bd5 | ||
|
|
ea95507da4 | ||
|
|
98f51794d8 | ||
|
|
e3360573ab | ||
|
|
8a75d3d6f6 | ||
|
|
142fe6a01d | ||
|
|
872571f4a5 | ||
|
|
63cb8018dd | ||
|
|
a0071b17de | ||
|
|
f2fab1ac36 | ||
|
|
e3936d0411 | ||
|
|
37d4927118 | ||
|
|
dc7fca409c | ||
|
|
cb590fc17e | ||
|
|
ccdf44d9f1 | ||
|
|
1fcb7b5005 | ||
|
|
f567970a85 | ||
|
|
386fc0b010 | ||
|
|
51300d63f4 | ||
|
|
0d1684873b | ||
|
|
066d135b21 | ||
|
|
0cef113a5b | ||
|
|
45a8a0c412 | ||
|
|
afa2768f28 | ||
|
|
34ca9fc86e | ||
|
|
75e25c1e7c | ||
|
|
4952f82e0b | ||
|
|
2bc1778f80 | ||
|
|
bacb18bd42 | ||
|
|
0c7745a0a6 | ||
|
|
f94e5eff85 | ||
|
|
aef6f17d9c | ||
|
|
654f3e30ce | ||
|
|
5221e999c2 | ||
|
|
66cf6d9f58 | ||
|
|
4108b05401 | ||
|
|
b227daa982 | ||
|
|
f138011843 | ||
|
|
0ec1e1ceb2 | ||
|
|
bd8dd42336 | ||
|
|
0c0b3e9682 | ||
|
|
557d7600b2 | ||
|
|
37db536a66 | ||
|
|
05e7049c44 | ||
|
|
b9c3d3dfcf | ||
|
|
b497a079d3 | ||
|
|
0b4276f738 | ||
|
|
c236f3e846 | ||
|
|
3dab1396c5 | ||
|
|
17c06f5d04 | ||
|
|
3f3996cc47 | ||
|
|
a1037fee9b | ||
|
|
b7776e1ca3 | ||
|
|
34b8b4f086 | ||
|
|
3c9172bad8 | ||
|
|
d6332695fd | ||
|
|
8796f460a7 | ||
|
|
36c4e7df43 | ||
|
|
67d275f451 | ||
|
|
088d3b0217 | ||
|
|
96ed87b2a5 | ||
|
|
a8235bcfc3 | ||
|
|
d2a70d030e | ||
|
|
0bd7ffc266 | ||
|
|
5411108c83 | ||
|
|
7681b89137 | ||
|
|
e42a888dad | ||
|
|
8be1e3f1d3 | ||
|
|
9452f024c7 | ||
|
|
f5b434af25 | ||
|
|
81b1f277a0 | ||
|
|
dcdc8a5a45 | ||
|
|
53ace82169 | ||
|
|
83a312187b | ||
|
|
e48c033839 | ||
|
|
f53b0399f3 | ||
|
|
a064d9fd17 | ||
|
|
21d521e187 | ||
|
|
a67b533d00 | ||
|
|
ff6541503e | ||
|
|
5f6cdb150f | ||
|
|
2537c7f52b | ||
|
|
a0d8a05a7b | ||
|
|
d1fedbf05d | ||
|
|
3102d97173 | ||
|
|
77edd5310c | ||
|
|
24114afb14 | ||
|
|
d1cf9a7617 | ||
|
|
9800ca7657 | ||
|
|
cfba9efd4e | ||
|
|
85ab80a039 | ||
|
|
d1e561eb13 | ||
|
|
cd590f2f33 | ||
|
|
d4ed772b07 | ||
|
|
324dfa4e23 | ||
|
|
391e101cba | ||
|
|
63b8c4db3b | ||
|
|
fee52ddcfa | ||
|
|
9ad525a34f | ||
|
|
d0f998ce45 | ||
|
|
1ffde77a87 | ||
|
|
f4c84883ec | ||
|
|
46836d38ac | ||
|
|
57ee603fec | ||
|
|
cebe3005a6 | ||
|
|
111898a269 | ||
|
|
a0342c87dd | ||
|
|
7da3c2d678 | ||
|
|
81ace85f29 | ||
|
|
b01a9a3ea1 | ||
|
|
945e98dcaf | ||
|
|
52a5ac4bb8 | ||
|
|
75c1cfc0f4 | ||
|
|
489eb411a3 | ||
|
|
2bc55103fd | ||
|
|
368e10a4df | ||
|
|
5d496f145e | ||
|
|
b7f2147015 | ||
|
|
2cb9df201f | ||
|
|
fc8942786d | ||
|
|
a580ef9911 | ||
|
|
d9e419f4a2 | ||
|
|
7b574c25d2 | ||
|
|
8d3736ddae | ||
|
|
d05e8e8285 | ||
|
|
e376962ab5 | ||
|
|
5798192e61 | ||
|
|
e4ce75a8dc | ||
|
|
c4eb2b016d | ||
|
|
ede29990e3 | ||
|
|
01bb774273 | ||
|
|
29beae2daa | ||
|
|
28941f37cd | ||
|
|
d1ceb55c68 | ||
|
|
575639cb49 | ||
|
|
55863d6101 | ||
|
|
aa9f11e11d | ||
|
|
cd1d527b57 | ||
|
|
a25fdb56fc | ||
|
|
36e6526cd9 | ||
|
|
059d7ad08d | ||
|
|
9b91c2dc6c | ||
|
|
c0be5ab403 | ||
|
|
83e7e07464 | ||
|
|
0ce63c0160 | ||
|
|
45b7cd0d01 | ||
|
|
67d6da5226 | ||
|
|
4e06a15dc8 | ||
|
|
3411950c6c | ||
|
|
8315460083 | ||
|
|
e7ace6f8a0 | ||
|
|
52cc90b205 | ||
|
|
f00c433746 | ||
|
|
62d664b6b1 | ||
|
|
7f292d3ee0 | ||
|
|
4c0f6fc513 | ||
|
|
5eea2a6944 | ||
|
|
a30a0b9cb6 | ||
|
|
566b777834 | ||
|
|
cae465b850 | ||
|
|
e3592b5ca7 | ||
|
|
08661094fc | ||
|
|
4e7d5c84e3 | ||
|
|
aeff25f7df | ||
|
|
4810a308eb | ||
|
|
092457fb2e | ||
|
|
8af59cd3de | ||
|
|
ded2e05be7 | ||
|
|
07844788ca | ||
|
|
fe0df7f47d | ||
|
|
076c184d7e | ||
|
|
449111cecb | ||
|
|
567879d932 | ||
|
|
58f11eaec7 | ||
|
|
d79c6672c5 | ||
|
|
f2645f0157 | ||
|
|
a28197a321 | ||
|
|
1e40657862 | ||
|
|
d1b2f62b3c | ||
|
|
4e42bfc45f | ||
|
|
245e1dc2ea | ||
|
|
82062b6df3 | ||
|
|
3e3b7cf1f2 | ||
|
|
1e7a0574fe | ||
|
|
df583fa2cb | ||
|
|
c3ffd0cd22 | ||
|
|
a81754342a | ||
|
|
5ae48b3799 | ||
|
|
05463f43ca | ||
|
|
d1cb92e497 | ||
|
|
820ffc5f2a | ||
|
|
e510015692 | ||
|
|
68cf7661ad | ||
|
|
1ccc4ffcfc | ||
|
|
e645a67ab6 | ||
|
|
f26a43c58a | ||
|
|
ee47d37f7c | ||
|
|
d18144bef1 | ||
|
|
f0d7fa5344 | ||
|
|
7e6e89a8f7 | ||
|
|
dd4684d72e | ||
|
|
b33abd3c14 | ||
|
|
3d204ac1f4 | ||
|
|
247e7ec711 | ||
|
|
1cc5b8ee91 | ||
|
|
012a4e2668 | ||
|
|
1308662ce5 | ||
|
|
fa5292bf7c | ||
|
|
310bed63b1 | ||
|
|
d36438b21e | ||
|
|
fea7f52362 | ||
|
|
5ef465820c | ||
|
|
51da2adf74 | ||
|
|
79f51bf51c | ||
|
|
2045869985 | ||
|
|
7141cb7f9e | ||
|
|
662a8e43f0 | ||
|
|
c6bb99113e | ||
|
|
1f0d55686f | ||
|
|
7fab08bc70 | ||
|
|
642574e17d | ||
|
|
bd53f1980c | ||
|
|
291457f1aa | ||
|
|
a68c3b4239 | ||
|
|
b5b3e30d6a | ||
|
|
b45c4fb441 | ||
|
|
3c9737b3d6 | ||
|
|
5f90854ea6 | ||
|
|
3127a2a787 | ||
|
|
8e46fcda86 | ||
|
|
6d4168f189 | ||
|
|
6dabfa3984 | ||
|
|
f5a95710a4 | ||
|
|
5de0435d99 | ||
|
|
a6526abadb | ||
|
|
ee6b58be97 | ||
|
|
37c0225f7d | ||
|
|
4c95f57996 | ||
|
|
e5cb4e5455 | ||
|
|
165f395aef | ||
|
|
911ec07f33 | ||
|
|
784653ad6f | ||
|
|
434bb7f68d | ||
|
|
a13dd43f96 | ||
|
|
30463c452b | ||
|
|
e4c9057a47 | ||
|
|
8f993266fe | ||
|
|
c14bc2f34f | ||
|
|
065fdfdde6 | ||
|
|
854d069627 | ||
|
|
909993ae3e | ||
|
|
3f29ff74ee | ||
|
|
5dfe5069ab | ||
|
|
df397e1d68 | ||
|
|
98d93ad74a | ||
|
|
f26bfadaef | ||
|
|
b80a676e9a | ||
|
|
16a085ff67 | ||
|
|
f6b2e3fe63 | ||
|
|
cf4d4cc260 | ||
|
|
06a94e2688 | ||
|
|
edf3335e60 | ||
|
|
4fb9f48f1a | ||
|
|
8b40c6ef90 | ||
|
|
0f36bc769f | ||
|
|
3a93402720 | ||
|
|
49d553808d | ||
|
|
fa563476a3 | ||
|
|
c6116d4e55 | ||
|
|
f2cf2ea203 | ||
|
|
8be6b5862e | ||
|
|
368a2f26e8 | ||
|
|
eaebc57f7c | ||
|
|
4e2d1809ba | ||
|
|
12d36551c5 | ||
|
|
2417eb8e3f | ||
|
|
1cda08cf11 | ||
|
|
1aeda7ffdb | ||
|
|
5df39072db | ||
|
|
b37e8d6fb7 | ||
|
|
48e8fdeb25 | ||
|
|
0b70c8f239 | ||
|
|
0768fa502f | ||
|
|
0e8fad8fc3 | ||
|
|
7fca3b151b | ||
|
|
9a6eb7ddb8 | ||
|
|
61845d390c | ||
|
|
d248dc4084 | ||
|
|
499147f256 | ||
|
|
cb530da8db | ||
|
|
aa155c8cd4 | ||
|
|
b3a27357d6 | ||
|
|
58f212d85d | ||
|
|
ca83c24d90 | ||
|
|
5b5caf7c58 | ||
|
|
c39a8d2fac | ||
|
|
e9c7a49961 | ||
|
|
e409a16d2c | ||
|
|
37eaea65d9 | ||
|
|
0b33ee53c1 | ||
|
|
a84cac9787 | ||
|
|
53ae602eeb | ||
|
|
4e17974139 | ||
|
|
7b15963025 | ||
|
|
69893797f7 | ||
|
|
1c9221df0d | ||
|
|
fb7f3bc01c | ||
|
|
578b1d3dd2 | ||
|
|
6b7a81690d | ||
|
|
808e07d97c | ||
|
|
d5653b22b9 | ||
|
|
e626d1ca95 | ||
|
|
06d56de0e9 | ||
|
|
88fcda9a02 | ||
|
|
17e7c2a42e | ||
|
|
6a9b817717 | ||
|
|
38c21f361f | ||
|
|
81ced236e5 | ||
|
|
fa3d2f9102 | ||
|
|
2db53bdbfa | ||
|
|
4a427b8b7b | ||
|
|
802c94fa05 | ||
|
|
91ae8e25d3 | ||
|
|
e48f537590 | ||
|
|
b8d7d82b73 | ||
|
|
ff7a18aeb9 | ||
|
|
ea6723eca8 | ||
|
|
f954914db1 | ||
|
|
9c562b24d5 | ||
|
|
f2d1048e15 | ||
|
|
abace42835 | ||
|
|
c0da37443b | ||
|
|
50efd6827b | ||
|
|
f4d031ca4d | ||
|
|
df33284028 | ||
|
|
a5cf75c3ab | ||
|
|
f8061618ef | ||
|
|
c197fb4ffe | ||
|
|
88a3f9bfb9 | ||
|
|
6cf0d72587 | ||
|
|
e0e90ce5c4 | ||
|
|
bd50902da0 | ||
|
|
ef030bbfc2 | ||
|
|
c112f27362 | ||
|
|
6cb027cd4f | ||
|
|
4c63a562be | ||
|
|
af5281b6d7 | ||
|
|
c948b3e2e5 | ||
|
|
e204b96d1d | ||
|
|
ea2ec0740f | ||
|
|
2725e2e705 | ||
|
|
f40ea70ac4 | ||
|
|
2366b43b1e | ||
|
|
6af9320dc8 | ||
|
|
7f1eedaaa6 | ||
|
|
46341a6e17 | ||
|
|
d7fd378b3c | ||
|
|
8688b1626c | ||
|
|
bd1ff948b8 | ||
|
|
1505ef2c99 | ||
|
|
213ea60923 | ||
|
|
e9406531a9 | ||
|
|
8b78a64e8a | ||
|
|
3bc4126246 | ||
|
|
e2c0f2b7c2 | ||
|
|
bd5fb5e7d1 | ||
|
|
fee3cd5367 | ||
|
|
081986eae2 | ||
|
|
941cf83a1a | ||
|
|
656290d156 | ||
|
|
b80b30b10b | ||
|
|
946e35c2ec | ||
|
|
ad8d16a31f | ||
|
|
c404aa66ac | ||
|
|
9110eebadb | ||
|
|
830985e259 | ||
|
|
87f1aa7875 | ||
|
|
41919e69dc | ||
|
|
03927e8cff | ||
|
|
000759790e | ||
|
|
99b92853cf | ||
|
|
e676d6c30e | ||
|
|
6841c919fa | ||
|
|
347e2a55f4 | ||
|
|
607a6691be | ||
|
|
c1a46219ed | ||
|
|
0c16cce8bc | ||
|
|
6286b69fbd | ||
|
|
4d12dbf30e | ||
|
|
045e8b8782 | ||
|
|
61e57aa5f4 | ||
|
|
aad3576953 | ||
|
|
1d3af800df | ||
|
|
51d6ff6c7f | ||
|
|
56e93548f5 | ||
|
|
e28086d23b | ||
|
|
e805498f95 | ||
|
|
f80561edc9 | ||
|
|
c98970b7c3 | ||
|
|
75f5260a89 | ||
|
|
d070fc62a7 | ||
|
|
6b28e558cf | ||
|
|
4bbe058e02 | ||
|
|
242f3f0343 | ||
|
|
edd92d8ae6 | ||
|
|
9205519217 | ||
|
|
a9821eae7b | ||
|
|
97378f7cdc | ||
|
|
377d2dc190 | ||
|
|
7e62f21a23 | ||
|
|
e18d5da193 | ||
|
|
ffbb835b05 | ||
|
|
fbf081814c | ||
|
|
9615382856 | ||
|
|
8e6cf48908 | ||
|
|
15d45cecbd | ||
|
|
7403ca0ce3 | ||
|
|
30ec740011 | ||
|
|
792d9cbe61 | ||
|
|
2ed4447b64 | ||
|
|
ef378ccd94 | ||
|
|
7007d4c0fc | ||
|
|
3cb30021c5 | ||
|
|
169ff83b11 | ||
|
|
5908cac18f | ||
|
|
b7f0567273 | ||
|
|
8a4ca55e8b | ||
|
|
e5b216c523 | ||
|
|
2f7f3fbf59 | ||
|
|
e7aa7d5b39 | ||
|
|
36d8b337af | ||
|
|
13e53d12a0 | ||
|
|
392850cf2f | ||
|
|
47bfece156 | ||
|
|
cda24bf8c3 | ||
|
|
b4df1ec96f | ||
|
|
246349b8e5 | ||
|
|
48da5b8cc6 | ||
|
|
af259157a2 | ||
|
|
0dcb732a3c | ||
|
|
f54f4a5b97 | ||
|
|
1e6d30da83 | ||
|
|
4e2d754ae7 | ||
|
|
e1ee929dad | ||
|
|
6446a9626c | ||
|
|
6fb1bdefec | ||
|
|
bd1c03fb15 | ||
|
|
6aa543978c | ||
|
|
b9d38d94b9 | ||
|
|
7c39836ce6 | ||
|
|
c498805966 | ||
|
|
32c28ffe90 | ||
|
|
a5375e3a1a | ||
|
|
c63a0ab56a | ||
|
|
a77cdaf5eb | ||
|
|
d2835395ad | ||
|
|
917e5ea8c7 | ||
|
|
8b856c799a | ||
|
|
b97f85af46 | ||
|
|
80b05a889a | ||
|
|
53ba16d747 | ||
|
|
1d2b5962b0 | ||
|
|
8d1e7b3fd5 | ||
|
|
975ebef19d | ||
|
|
548865b621 | ||
|
|
db187bb8a9 | ||
|
|
5e29c80ca3 | ||
|
|
08e2f779e3 | ||
|
|
7c4e5444d6 | ||
|
|
169ac6d13d | ||
|
|
fe1e3c0fa9 | ||
|
|
97f077a120 | ||
|
|
0a0203fa43 | ||
|
|
ecc711cb73 | ||
|
|
83b91b81ca | ||
|
|
9f6dc1d8a8 | ||
|
|
ac34415bf3 | ||
|
|
ed40837b45 | ||
|
|
2f69aece64 | ||
|
|
516f2e1083 | ||
|
|
a5d136d5e6 | ||
|
|
db7ecb2721 | ||
|
|
6adcb5ed68 | ||
|
|
ff6d24d104 | ||
|
|
c73f4cff8e | ||
|
|
aac92169b7 | ||
|
|
4f76085494 | ||
|
|
c8ddbddc19 | ||
|
|
e4d363122a | ||
|
|
a568530c6b | ||
|
|
55596e9d07 | ||
|
|
6eb2ca4689 | ||
|
|
8731c086f9 | ||
|
|
2d36a5da6b | ||
|
|
56b245e4a6 |
@@ -6,112 +6,26 @@
|
||||
"docsSlug": "doctrine-orm",
|
||||
"versions": [
|
||||
{
|
||||
"name": "4.0",
|
||||
"branchName": "4.0.x",
|
||||
"name": "3.0",
|
||||
"branchName": "master",
|
||||
"slug": "latest",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.2",
|
||||
"branchName": "3.2.x",
|
||||
"slug": "3.2",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "3.1",
|
||||
"branchName": "3.1.x",
|
||||
"slug": "3.1",
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"name": "3.0",
|
||||
"branchName": "3.0.x",
|
||||
"slug": "3.0",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.20",
|
||||
"branchName": "2.20.x",
|
||||
"slug": "2.20",
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.19",
|
||||
"branchName": "2.19.x",
|
||||
"slug": "2.19",
|
||||
"maintained": true
|
||||
},
|
||||
{
|
||||
"name": "2.18",
|
||||
"branchName": "2.18.x",
|
||||
"slug": "2.18",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.17",
|
||||
"branchName": "2.17.x",
|
||||
"slug": "2.17",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.16",
|
||||
"branchName": "2.16.x",
|
||||
"slug": "2.16",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.15",
|
||||
"branchName": "2.15.x",
|
||||
"slug": "2.15",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.14",
|
||||
"branchName": "2.14.x",
|
||||
"slug": "2.14",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.13",
|
||||
"branchName": "2.13.x",
|
||||
"slug": "2.13",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.12",
|
||||
"branchName": "2.12.x",
|
||||
"slug": "2.12",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.11",
|
||||
"branchName": "2.11.x",
|
||||
"slug": "2.11",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.10",
|
||||
"branchName": "2.10.x",
|
||||
"slug": "2.10",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.9",
|
||||
"branchName": "2.9.x",
|
||||
"slug": "2.9",
|
||||
"maintained": false
|
||||
},
|
||||
{
|
||||
"name": "2.8",
|
||||
"branchName": "2.8.x",
|
||||
"slug": "2.8",
|
||||
"maintained": false
|
||||
"upcoming": true
|
||||
},
|
||||
{
|
||||
"name": "2.7",
|
||||
"branchName": "2.7",
|
||||
"slug": "2.7",
|
||||
"maintained": false
|
||||
"current": true,
|
||||
"aliases": [
|
||||
"current",
|
||||
"stable"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2.6",
|
||||
|
||||
14
.gitattributes
vendored
14
.gitattributes
vendored
@@ -1,20 +1,14 @@
|
||||
/.github export-ignore
|
||||
/ci export-ignore
|
||||
/docs export-ignore
|
||||
/tests export-ignore
|
||||
/tools 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
|
||||
CONTRIBUTING.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
run-all.sh export-ignore
|
||||
phpcs.xml.dist export-ignore
|
||||
phpbench.json export-ignore
|
||||
phpstan.neon export-ignore
|
||||
phpstan-baseline.neon export-ignore
|
||||
phpstan-dbal2.neon export-ignore
|
||||
phpstan-params.neon export-ignore
|
||||
phpstan-persistence2.neon export-ignore
|
||||
composer.lock export-ignore
|
||||
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
patreon: phpdoctrine
|
||||
tidelift: packagist/doctrine/orm
|
||||
custom: https://www.doctrine-project.org/sponsorship.html
|
||||
2
.github/ISSUE_TEMPLATE/BC_Break.md
vendored
2
.github/ISSUE_TEMPLATE/BC_Break.md
vendored
@@ -4,7 +4,7 @@ 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
|
||||
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/master/UPGRADE.md
|
||||
-->
|
||||
|
||||
### BC Break Report
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/Support_Question.md
vendored
16
.github/ISSUE_TEMPLATE/Support_Question.md
vendored
@@ -3,4 +3,18 @@ name: ❓ Support Question
|
||||
about: Have a problem that you can't figure out? 🤔
|
||||
---
|
||||
|
||||
Please use https://github.com/doctrine/orm/discussions instead.
|
||||
<!-- Fill in the relevant information below to help triage your issue. -->
|
||||
|
||||
| Q | A
|
||||
|------------ | -----
|
||||
| Version | x.y.z
|
||||
|
||||
<!--
|
||||
Before asking question here, please try asking on Gitter or Slack first.
|
||||
Find out more about Doctrine support channels here: https://www.doctrine-project.org/community/
|
||||
Keep in mind that GitHub is primarily an issue tracker.
|
||||
-->
|
||||
|
||||
### Support Question
|
||||
|
||||
<!-- Describe the issue you are facing here. -->
|
||||
|
||||
6
.github/PULL_REQUEST_TEMPLATE/New_Feature.md
vendored
6
.github/PULL_REQUEST_TEMPLATE/New_Feature.md
vendored
@@ -6,9 +6,9 @@ about: You have implemented some neat idea that you want to make part of Doctrin
|
||||
<!--
|
||||
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
|
||||
* submitting a bugfix: target the lowest active stable branch: 2.7
|
||||
* submitting a new feature: target the next minor branch: 2.8.x
|
||||
* submitting a BC-breaking change: target the master branch
|
||||
-->
|
||||
|
||||
### New Feature
|
||||
|
||||
47
.github/workflows/ci.yml
vendored
Normal file
47
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
coding-standards:
|
||||
name: "Coding Standards"
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.4'
|
||||
extensions: mbstring
|
||||
tools: composer, cs2pr
|
||||
|
||||
- name: composer install
|
||||
run: "composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader"
|
||||
|
||||
- name: phpcs
|
||||
run: "php vendor/bin/phpcs -q --report=checkstyle --no-colors | cs2pr"
|
||||
|
||||
static-analysis:
|
||||
name: "Static Analysis"
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '7.4'
|
||||
extensions: mbstring
|
||||
tools: composer, cs2pr
|
||||
|
||||
- name: composer install
|
||||
run: "composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader"
|
||||
|
||||
- name: phpstan
|
||||
run: "php vendor/bin/phpstan analyse --error-format=checkstyle --no-progress | cs2pr"
|
||||
27
.github/workflows/coding-standards.yml
vendored
27
.github/workflows/coding-standards.yml
vendored
@@ -1,27 +0,0 @@
|
||||
name: "Coding Standards"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/coding-standards.yml
|
||||
- bin/**
|
||||
- composer.*
|
||||
- src/**
|
||||
- phpcs.xml.dist
|
||||
- tests/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/coding-standards.yml
|
||||
- bin/**
|
||||
- composer.*
|
||||
- src/**
|
||||
- phpcs.xml.dist
|
||||
- tests/**
|
||||
|
||||
jobs:
|
||||
coding-standards:
|
||||
uses: "doctrine/.github/.github/workflows/coding-standards.yml@14.0.0"
|
||||
20
.github/workflows/composer-lint.yml
vendored
20
.github/workflows/composer-lint.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: "Composer Lint"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- ".github/workflows/composer-lint.yml"
|
||||
- "composer.json"
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- ".github/workflows/composer-lint.yml"
|
||||
- "composer.json"
|
||||
|
||||
jobs:
|
||||
composer-lint:
|
||||
name: "Composer Lint"
|
||||
uses: "doctrine/.github/.github/workflows/composer-lint.yml@14.0.0"
|
||||
424
.github/workflows/continuous-integration.yml
vendored
424
.github/workflows/continuous-integration.yml
vendored
@@ -1,424 +0,0 @@
|
||||
name: "CI: PHPUnit"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/continuous-integration.yml
|
||||
- ci/**
|
||||
- composer.*
|
||||
- src/**
|
||||
- phpunit.xml.dist
|
||||
- tests/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/continuous-integration.yml
|
||||
- ci/**
|
||||
- composer.*
|
||||
- src/**
|
||||
- phpunit.xml.dist
|
||||
- tests/**
|
||||
|
||||
env:
|
||||
fail-fast: true
|
||||
|
||||
jobs:
|
||||
phpunit-smoke-check:
|
||||
name: >
|
||||
SQLite -
|
||||
${{ format('PHP {0} - DBAL {1} - ext. {2} - proxy {3}',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø',
|
||||
matrix.proxy || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.2"
|
||||
- "7.3"
|
||||
- "7.4"
|
||||
- "8.0"
|
||||
- "8.1"
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
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@v6"
|
||||
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: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
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@v7"
|
||||
with:
|
||||
name: "phpunit-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.proxy }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
phpunit-postgres:
|
||||
name: >
|
||||
${{ format('PostgreSQL {0} - PHP {1} - DBAL {2} - ext. {3}',
|
||||
matrix.postgres-version || 'Ø',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
postgres-version:
|
||||
- "17"
|
||||
extension:
|
||||
- pdo_pgsql
|
||||
- pgsql
|
||||
include:
|
||||
- php-version: "8.0"
|
||||
dbal-version: "2.13"
|
||||
postgres-version: "14"
|
||||
extension: pdo_pgsql
|
||||
- php-version: "8.2"
|
||||
dbal-version: "default"
|
||||
postgres-version: "9.6"
|
||||
extension: pdo_pgsql
|
||||
|
||||
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@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
php-version: "${{ matrix.php-version }}"
|
||||
extensions: "pgsql pdo_pgsql"
|
||||
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: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
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@v7"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.postgres-version }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-${{ matrix.extension }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
phpunit-mariadb:
|
||||
name: >
|
||||
${{ format('MariaDB {0} - PHP {1} - DBAL {2} - ext. {3}',
|
||||
matrix.mariadb-version || 'Ø',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
dbal-version:
|
||||
- "default"
|
||||
- "3@dev"
|
||||
mariadb-version:
|
||||
- "11.4"
|
||||
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:
|
||||
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: yes
|
||||
MARIADB_DATABASE: "doctrine_tests"
|
||||
|
||||
options: >-
|
||||
--health-cmd "healthcheck.sh --connect --innodb_initialized"
|
||||
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v6"
|
||||
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: "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@v4"
|
||||
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@v7"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mariadb-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage.xml"
|
||||
|
||||
|
||||
phpunit-mysql:
|
||||
name: >
|
||||
${{ format('MySQL {0} - PHP {1} - DBAL {2} - ext. {3}',
|
||||
matrix.mysql-version || 'Ø',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.dbal-version || 'Ø',
|
||||
matrix.extension || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
needs: "phpunit-smoke-check"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "8.2"
|
||||
- "8.3"
|
||||
- "8.4"
|
||||
- "8.5"
|
||||
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@v6"
|
||||
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: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
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@v7"
|
||||
with:
|
||||
name: "${{ github.job }}-${{ matrix.mysql-version }}-${{ matrix.extension }}-${{ matrix.php-version }}-${{ matrix.dbal-version }}-coverage"
|
||||
path: "coverage*.xml"
|
||||
|
||||
|
||||
phpunit-lower-php-versions:
|
||||
name: >
|
||||
SQLite -
|
||||
${{ format('PHP {0} - deps {1}',
|
||||
matrix.php-version || 'Ø',
|
||||
matrix.deps || 'Ø'
|
||||
) }}
|
||||
runs-on: "ubuntu-22.04"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version:
|
||||
- "7.1"
|
||||
deps:
|
||||
- "highest"
|
||||
- "lowest"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v6"
|
||||
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@v4"
|
||||
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"
|
||||
# Only run on PRs from forks
|
||||
if: "github.event.pull_request.head.repo.full_name != github.repository"
|
||||
needs:
|
||||
- "phpunit-smoke-check"
|
||||
- "phpunit-postgres"
|
||||
- "phpunit-mariadb"
|
||||
- "phpunit-mysql"
|
||||
|
||||
steps:
|
||||
- name: "Checkout"
|
||||
uses: "actions/checkout@v6"
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: "Download coverage files"
|
||||
uses: "actions/download-artifact@v8"
|
||||
with:
|
||||
path: "reports"
|
||||
|
||||
- name: "Upload to Codecov"
|
||||
uses: "codecov/codecov-action@v6"
|
||||
with:
|
||||
directory: reports
|
||||
env:
|
||||
CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}"
|
||||
20
.github/workflows/documentation.yml
vendored
20
.github/workflows/documentation.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: "Documentation"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- ".github/workflows/documentation.yml"
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- ".github/workflows/documentation.yml"
|
||||
- "docs/**"
|
||||
|
||||
jobs:
|
||||
documentation:
|
||||
name: "Documentation"
|
||||
uses: "doctrine/.github/.github/workflows/documentation.yml@14.0.0"
|
||||
54
.github/workflows/phpbench.yml
vendored
54
.github/workflows/phpbench.yml
vendored
@@ -1,54 +0,0 @@
|
||||
|
||||
name: "Performance benchmark"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/phpbench.yml
|
||||
- composer.*
|
||||
- src/**
|
||||
- phpbench.json
|
||||
- tests/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/phpbench.yml
|
||||
- composer.*
|
||||
- src/**
|
||||
- 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@v6"
|
||||
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: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
|
||||
- name: "Run PHPBench"
|
||||
run: "vendor/bin/phpbench run --report=default"
|
||||
@@ -1,15 +0,0 @@
|
||||
name: "Automatic Releases"
|
||||
|
||||
on:
|
||||
milestone:
|
||||
types:
|
||||
- "closed"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: "doctrine/.github/.github/workflows/release-on-milestone-closed.yml@14.0.0"
|
||||
secrets:
|
||||
GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
|
||||
GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
|
||||
ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}
|
||||
SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}
|
||||
73
.github/workflows/static-analysis.yml
vendored
73
.github/workflows/static-analysis.yml
vendored
@@ -1,73 +0,0 @@
|
||||
name: "Static Analysis"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/static-analysis.yml
|
||||
- composer.*
|
||||
- src/**
|
||||
- phpstan*
|
||||
- tests/StaticAnalysis/**
|
||||
push:
|
||||
branches:
|
||||
- "*.x"
|
||||
paths:
|
||||
- .github/workflows/static-analysis.yml
|
||||
- composer.*
|
||||
- src/**
|
||||
- phpstan*
|
||||
- tests/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@v6"
|
||||
|
||||
- name: "Install PHP"
|
||||
uses: "shivammathur/setup-php@v2"
|
||||
with:
|
||||
coverage: "none"
|
||||
php-version: "8.4"
|
||||
|
||||
- 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: "Install dependencies with Composer"
|
||||
uses: "ramsey/composer-install@v4"
|
||||
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'}}"
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -3,15 +3,17 @@ logs/
|
||||
reports/
|
||||
dist/
|
||||
download/
|
||||
lib/api/
|
||||
lib/Doctrine/Common
|
||||
lib/Doctrine/DBAL
|
||||
/.settings/
|
||||
*.iml
|
||||
.buildpath
|
||||
.project
|
||||
.idea
|
||||
*.iml
|
||||
vendor/
|
||||
composer.phar
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
composer.lock
|
||||
.phpunit.cache
|
||||
.phpunit.result.cache
|
||||
/*.phpunit.xml
|
||||
phpbench.phar
|
||||
phpbench.phar.pubkey
|
||||
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "docs/en/_theme"]
|
||||
path = docs/en/_theme
|
||||
url = git://github.com/doctrine/doctrine-sphinx-theme.git
|
||||
[submodule "lib/vendor/doctrine-build-common"]
|
||||
path = lib/vendor/doctrine-build-common
|
||||
url = git://github.com/doctrine/doctrine-build-common.git
|
||||
32
.scrutinizer.yml
Normal file
32
.scrutinizer.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
environment:
|
||||
php:
|
||||
version: 7.4
|
||||
cache:
|
||||
disabled: false
|
||||
directories:
|
||||
- ~/.composer/cache
|
||||
project_setup:
|
||||
override: true
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
- phpcs-run
|
||||
dependencies:
|
||||
override:
|
||||
- composer install --no-interaction --prefer-dist
|
||||
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 3600
|
||||
|
||||
filter:
|
||||
excluded_paths:
|
||||
- docs
|
||||
|
||||
build_failure_conditions:
|
||||
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
|
||||
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
|
||||
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection
|
||||
82
.travis.yml
Normal file
82
.travis.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 7.3
|
||||
- 7.4
|
||||
|
||||
env:
|
||||
- DB=mariadb
|
||||
- DB=mysql
|
||||
- DB=pgsql
|
||||
- DB=sqlite
|
||||
|
||||
before_install:
|
||||
- |
|
||||
if [[ "$COVERAGE" != "1" ]]; then
|
||||
phpenv config-rm ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini || echo "xdebug is not installed"
|
||||
fi
|
||||
- echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
|
||||
- travis_retry composer self-update
|
||||
|
||||
install:
|
||||
- rm composer.lock
|
||||
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress
|
||||
|
||||
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
|
||||
# temporarily disabled
|
||||
#- 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.4"
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite DEPENDENCIES=low
|
||||
install:
|
||||
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress --prefer-lowest
|
||||
|
||||
- stage: Test
|
||||
if: type = cron
|
||||
php: 7.3
|
||||
env: DB=sqlite DEV_DEPENDENCIES
|
||||
install:
|
||||
- rm composer.lock
|
||||
- composer config minimum-stability dev
|
||||
- travis_retry composer update --no-interaction --prefer-dist --no-suggest --no-progress
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite COVERAGE
|
||||
before_script:
|
||||
- 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_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover build/logs/clover.xml
|
||||
|
||||
- stage: Code Quality
|
||||
env: DB=none BENCHMARK
|
||||
before_script:
|
||||
- wget https://phpbench.github.io/phpbench/phpbench.phar https://phpbench.github.io/phpbench/phpbench.phar.pubkey
|
||||
script:
|
||||
- php phpbench.phar run --bootstrap=tests/Doctrine/Tests/TestInit.php -l dots --report=default
|
||||
|
||||
allow_failures:
|
||||
# temporarily disabled
|
||||
- env: DB=mysql
|
||||
- env: DB=mariadb
|
||||
- env: DB=pgsql
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
100
CONTRIBUTING.md
100
CONTRIBUTING.md
@@ -1,64 +1,81 @@
|
||||
# Contribute to Doctrine
|
||||
# Contributing to Doctrine ORM
|
||||
|
||||
Thank you for contributing to Doctrine!
|
||||
Thank you for contributing to Doctrine ORM!
|
||||
|
||||
Before we can merge your Pull-Request here are some guidelines that you need to follow.
|
||||
Before we can merge your pull request here are some guidelines that you need to follow.
|
||||
These guidelines exist not to annoy you, but to keep the code base clean,
|
||||
unified and future proof.
|
||||
|
||||
Doctrine has [general contributing guidelines][contributor workflow], make
|
||||
sure you follow them.
|
||||
## Obtaining a copy
|
||||
|
||||
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html
|
||||
In order to submit a pull request, you will need to [fork the project][Fork] and obtain a
|
||||
fresh copy of the source code:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:<your-github-name>/orm.git
|
||||
cd orm
|
||||
```
|
||||
|
||||
Then you will have to run a Composer installation in the project:
|
||||
```sh
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
./composer.phar install
|
||||
```
|
||||
|
||||
## Choosing the branch
|
||||
|
||||
* I am submitting a bugfix for a stable release
|
||||
* Your PR should target the [lowest active stable branch (2.7)][2.7].
|
||||
* I am submitting a new feature
|
||||
* Your PR should target the [master branch (3.0)][Master].
|
||||
* I am submitting a BC-breaking change
|
||||
* Your PR must target the [master branch (3.0)][Master].
|
||||
* Please also try to provide a deprecation path in a PR targeting the [2.8 branch][2.8].
|
||||
|
||||
Please always create a new branch for your changes (i.e. do not commit directly into `master`
|
||||
in your fork), otherwise you would run into troubles with creating multiple pull requests.
|
||||
|
||||
## Coding Standard
|
||||
|
||||
This project follows [`doctrine/coding-standard`][coding standard homepage].
|
||||
You may fix many some of the issues with `vendor/bin/phpcbf`.
|
||||
We follow the [Doctrine Coding Standard][CS].
|
||||
Please refer to this repository to learn about the rules your code should follow.
|
||||
You can also use `vendor/bin/phpcs` to validate your changes locally.
|
||||
|
||||
[coding standard homepage]: https://github.com/doctrine/coding-standard
|
||||
## Tests
|
||||
|
||||
## Unit-Tests
|
||||
|
||||
Please try to add a test for your pull-request.
|
||||
Please try to add a test for your pull request.
|
||||
|
||||
* If you want to fix a bug or provide a reproduce case, create a test file in
|
||||
``tests/Tests/ORM/Functional/Ticket`` with the name of the ticket,
|
||||
``DDC1234Test.php`` for example.
|
||||
* If you want to contribute new functionality add unit- or functional tests
|
||||
``tests/Doctrine/Tests/ORM/Functional/Ticket`` with the identifier of the issue,
|
||||
i.e. ``GH1234Test.php`` for an issue with id `#1234`.
|
||||
* If you want to contribute new functionality, add unit or functional tests
|
||||
depending on the scope of the feature.
|
||||
|
||||
You can run the unit-tests by calling ``vendor/bin/phpunit`` from the root of the project.
|
||||
It will run all the tests with an in memory SQLite database.
|
||||
|
||||
In order to do that, you will need a fresh copy of the ORM, and you
|
||||
will have to run a composer installation in the project:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:doctrine/orm.git
|
||||
cd orm
|
||||
composer install
|
||||
```
|
||||
|
||||
You will also need to enable the PHP extension that provides the SQLite driver
|
||||
for PDO: `pdo_sqlite`. How to do so depends on your system, but checking that it
|
||||
is enabled can universally be done with `php -m`: that command should list the
|
||||
extension.
|
||||
You can run the tests by calling ``vendor/bin/phpunit`` from the root of the project.
|
||||
It will run all the tests with an in-memory SQLite database.
|
||||
|
||||
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 ``ci/github/phpunit`` directory for some examples. Then run:
|
||||
take a look at the ``tests/travis`` folder 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/2.8.x/tests/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
example.
|
||||
1. If you put a test into the `Ticket` namespace as described above, put the testcase
|
||||
and all entities into the same file.
|
||||
See [DDC2306Test][Test Example] for an example.
|
||||
|
||||
## CI
|
||||
|
||||
We automatically run all pull requests through [Travis CI][Travis].
|
||||
|
||||
* The test suite is ran against SQLite, MySQL, MariaDB and PostgreSQL on all supported PHP versions.
|
||||
* The code is validated against our [Coding Standard](#coding-standard).
|
||||
* The code is checked by a static analysis tool.
|
||||
|
||||
If you break the tests, we cannot merge your code,
|
||||
so please make sure that your code is working before opening a pull request.
|
||||
|
||||
## Getting merged
|
||||
|
||||
@@ -67,3 +84,10 @@ everything as fast as possible, but cannot always live up to our own expectation
|
||||
|
||||
Thank you very much again for your contribution!
|
||||
|
||||
[Master]: https://github.com/doctrine/orm/tree/master
|
||||
[2.8]: https://github.com/doctrine/orm/tree/2.8.x
|
||||
[2.7]: https://github.com/doctrine/orm/tree/2.7
|
||||
[CS]: https://github.com/doctrine/coding-standard
|
||||
[Fork]: https://guides.github.com/activities/forking/
|
||||
[Travis]: https://www.travis-ci.org
|
||||
[Test Example]: https://github.com/doctrine/orm/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php
|
||||
|
||||
71
README.md
71
README.md
@@ -1,45 +1,52 @@
|
||||
| [4.0.x][4.0] | [3.6.x][3.6] | [3.5.x][3.5] | [2.21.x][2.21] | [2.20.x][2.20] |
|
||||
|:------------------------------------------------------:|:------------------------------------------------------:|:------------------------------------------------------:|:--------------------------------------------------------:|:--------------------------------------------------------:|
|
||||
| [![Build status][4.0 image]][4.0 workflow] | [![Build status][3.6 image]][3.6 workflow] | [![Build status][3.5 image]][3.5 workflow] | [![Build status][2.21 image]][2.21 workflow] | [![Build status][2.20 image]][2.20 workflow] |
|
||||
| [![Coverage Status][4.0 coverage image]][4.0 coverage] | [![Coverage Status][3.6 coverage image]][3.6 coverage] | [![Coverage Status][3.5 coverage image]][3.5 coverage] | [![Coverage Status][2.21 coverage image]][2.21 coverage] | [![Coverage Status][2.20 coverage image]][2.20 coverage] |
|
||||
[](https://tidelift.com/subscription/pkg/packagist-doctrine-orm?utm_source=packagist-doctrine-orm&utm_medium=referral&utm_campaign=readme)
|
||||
|
||||
[<h1 align="center">🇺🇦 UKRAINE NEEDS YOUR HELP NOW!</h1>](https://www.doctrine-project.org/stop-war.html)
|
||||
| [Master][Master] | [2.8][2.8] | [2.7][2.7] |
|
||||
|:----------------:|:----------:|:----------:|
|
||||
| [![Build status][Master image]][Master] | [![Build status][2.8 image]][2.8] | [![Build status][2.7 image]][2.7] |
|
||||
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.8 coverage image]][2.8 coverage] | [![Coverage Status][2.7 coverage image]][2.7 coverage] |
|
||||
|
||||
Doctrine ORM is an object-relational mapper for PHP 7.1+ that provides transparent persistence
|
||||
##### :warning: You are browsing the code of upcoming Doctrine 3.0.
|
||||
##### Things changed a lot here and major code changes should be expected. If you are rather looking for a stable version, refer to the [2.7 branch][2.7] for the current stable release or [2.8 branch][2.8] for the upcoming release. If you are submitting a pull request, please see the _[Which branch should I choose?](#which-branch-should-i-choose)_ section below.
|
||||
|
||||
-----
|
||||
|
||||
Doctrine 3 is an object-relational mapper (ORM) for PHP 7.2+ 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
|
||||
without requiring unnecessary code duplication.
|
||||
|
||||
-----
|
||||
|
||||
### Which branch should I choose?
|
||||
|
||||
Please see [Choosing the branch](CONTRIBUTING.md#choosing-the-branch) to get more information about which branch
|
||||
you should target your pull request at.
|
||||
|
||||
## Doctrine ORM for enterprise
|
||||
|
||||
Available as part of the Tidelift Subscription.
|
||||
|
||||
The maintainers of Doctrine ORM and thousands of other packages are working with Tidelift to deliver commercial support
|
||||
and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve
|
||||
code health, while paying the maintainers of the exact dependencies you use.
|
||||
[Learn more.](https://tidelift.com/subscription/pkg/packagist-doctrine-orm?utm_source=packagist-doctrine-orm&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
|
||||
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org)
|
||||
* [Documentation](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/index.html)
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
|
||||
|
||||
[4.0 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0.x
|
||||
[4.0]: https://github.com/doctrine/orm/tree/4.0.x
|
||||
[4.0 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A4.0.x
|
||||
[4.0 coverage image]: https://codecov.io/gh/doctrine/orm/branch/4.0.x/graph/badge.svg
|
||||
[4.0 coverage]: https://codecov.io/gh/doctrine/orm/branch/4.0.x
|
||||
[3.6 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.6.x
|
||||
[3.6]: https://github.com/doctrine/orm/tree/3.6.x
|
||||
[3.6 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.6.x
|
||||
[3.6 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.6.x/graph/badge.svg
|
||||
[3.6 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.6.x
|
||||
[3.5 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=3.5.x
|
||||
[3.5]: https://github.com/doctrine/orm/tree/3.5.x
|
||||
[3.5 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A3.5.x
|
||||
[3.5 coverage image]: https://codecov.io/gh/doctrine/orm/branch/3.5.x/graph/badge.svg
|
||||
[3.5 coverage]: https://codecov.io/gh/doctrine/orm/branch/3.5.x
|
||||
[2.21 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.21.x
|
||||
[2.21]: https://github.com/doctrine/orm/tree/2.21.x
|
||||
[2.21 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.21.x
|
||||
[2.21 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.21.x/graph/badge.svg
|
||||
[2.21 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.21.x
|
||||
[2.20 image]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml/badge.svg?branch=2.20.x
|
||||
[2.20]: https://github.com/doctrine/orm/tree/2.20.x
|
||||
[2.20 workflow]: https://github.com/doctrine/orm/actions/workflows/continuous-integration.yml?query=branch%3A2.20.x
|
||||
[2.20 coverage image]: https://codecov.io/gh/doctrine/orm/branch/2.20.x/graph/badge.svg
|
||||
[2.20 coverage]: https://codecov.io/gh/doctrine/orm/branch/2.20.x
|
||||
[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://img.shields.io/scrutinizer/coverage/g/doctrine/orm/master.svg?style=flat-square
|
||||
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=master
|
||||
[2.8 image]: https://img.shields.io/travis/doctrine/orm/2.8.x.svg?style=flat-square
|
||||
[2.8]: https://github.com/doctrine/orm/tree/2.8.x
|
||||
[2.8 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.8.x.svg?style=flat-square
|
||||
[2.8 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.8.x
|
||||
[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://img.shields.io/scrutinizer/coverage/g/doctrine/orm/2.7.svg?style=flat-square
|
||||
[2.7 coverage]: https://scrutinizer-ci.com/g/doctrine/orm/?branch=2.7
|
||||
|
||||
@@ -10,8 +10,9 @@ we cannot protect you from SQL injection.
|
||||
Please read the documentation chapter on Security in Doctrine DBAL and ORM to
|
||||
understand the assumptions we make.
|
||||
|
||||
- [DBAL Security Page](https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/security.html)
|
||||
- [ORM Security Page](https://www.doctrine-project.org/projects/doctrine-orm/en/stable/reference/security.html)
|
||||
- [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)
|
||||
|
||||
If you find a Security bug in Doctrine, please follow our
|
||||
[Security reporting guidelines](https://www.doctrine-project.org/policies/security.html#reporting).
|
||||
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
|
||||
developers and you only.
|
||||
|
||||
1062
UPGRADE.md
1062
UPGRADE.md
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
include(__DIR__ . '/doctrine.php');
|
||||
declare(strict_types=1);
|
||||
|
||||
include('doctrine.php');
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
<?php
|
||||
|
||||
fwrite(
|
||||
STDERR,
|
||||
'[Warning] The use of this script is discouraged. See'
|
||||
. ' https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/tools.html#doctrine-console'
|
||||
. ' for instructions on bootstrapping the console runner.'
|
||||
. PHP_EOL
|
||||
);
|
||||
|
||||
echo PHP_EOL . PHP_EOL;
|
||||
declare(strict_types=1);
|
||||
|
||||
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,18 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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'
|
||||
|
||||
0
build.properties
Normal file
0
build.properties
Normal file
16
build.properties.dev
Normal file
16
build.properties.dev
Normal file
@@ -0,0 +1,16 @@
|
||||
version=2.0.0BETA2
|
||||
dependencies.common=2.0.0BETA4
|
||||
dependencies.dbal=2.0.0BETA4
|
||||
stability=beta
|
||||
build.dir=build
|
||||
dist.dir=dist
|
||||
report.dir=reports
|
||||
log.archive.dir=logs
|
||||
project.pirum_dir=
|
||||
project.download_dir=
|
||||
project.xsd_dir=
|
||||
test.phpunit_configuration_file=
|
||||
test.phpunit_generate_coverage=0
|
||||
test.pmd_reports=0
|
||||
test.pdepend_exec=
|
||||
test.phpmd_exec=
|
||||
78
build.xml
Normal file
78
build.xml
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="DoctrineORM" default="build" basedir=".">
|
||||
<property file="build.properties" />
|
||||
|
||||
<target name="php">
|
||||
<exec executable="which" outputproperty="php_executable">
|
||||
<arg value="php" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="prepare">
|
||||
<mkdir dir="build" />
|
||||
</target>
|
||||
|
||||
<target name="build" depends="check-git-checkout-clean,prepare,php,composer">
|
||||
<exec executable="${php_executable}">
|
||||
<arg value="build/composer.phar" />
|
||||
<arg value="archive" />
|
||||
<arg value="--dir=build" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="composer" depends="php,composer-check,composer-download">
|
||||
<exec executable="${php_executable}">
|
||||
<arg value="build/composer.phar" />
|
||||
<arg value="install" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="composer-check" depends="prepare">
|
||||
<available file="build/composer.phar" property="composer.present"/>
|
||||
</target>
|
||||
|
||||
<target name="composer-download" unless="composer.present">
|
||||
<exec executable="wget">
|
||||
<arg value="-Obuild/composer.phar" />
|
||||
<arg value="http://getcomposer.org/composer.phar" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<target name="check-git-checkout-clean">
|
||||
<exec executable="git" failonerror="true">
|
||||
<arg value="diff-index" />
|
||||
<arg value="--quiet" />
|
||||
<arg value="HEAD" />
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
<macrodef name="git-commit">
|
||||
<attribute name="file" default="NOT SET"/>
|
||||
<attribute name="message" default="NOT SET"/>
|
||||
|
||||
<sequential>
|
||||
<exec executable="git">
|
||||
<arg value="add" />
|
||||
<arg value="@{file}" />
|
||||
</exec>
|
||||
<exec executable="git">
|
||||
<arg value="commit" />
|
||||
<arg value="-m" />
|
||||
<arg value="@{message}" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<macrodef name="git-tag">
|
||||
<attribute name="version" default="NOT SET" />
|
||||
|
||||
<sequential>
|
||||
<exec executable="git">
|
||||
<arg value="tag" />
|
||||
<arg value="-m" />
|
||||
<arg value="v@{version}" />
|
||||
<arg value="v@{version}" />
|
||||
</exec>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
</project>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?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">../../../src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?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">../../../src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?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">../../../src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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">../../../src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?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="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">../../../src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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">../../../src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<groups>
|
||||
<exclude>
|
||||
<group>performance</group>
|
||||
<group>locking_functional</group>
|
||||
</exclude>
|
||||
</groups>
|
||||
</phpunit>
|
||||
130
composer.json
130
composer.json
@@ -1,99 +1,71 @@
|
||||
{
|
||||
"name": "doctrine/orm",
|
||||
"description": "Object-Relational-Mapper for PHP",
|
||||
"license": "MIT",
|
||||
"type": "library",
|
||||
"description": "PHP object relational mapper (ORM) that 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). This provides developers with a powerful alternative to SQL that maintains flexibility without requiring unnecessary code duplication.",
|
||||
"keywords": [
|
||||
"php",
|
||||
"orm",
|
||||
"database"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marco Pivetta",
|
||||
"email": "ocramius@gmail.com"
|
||||
}
|
||||
"mysql",
|
||||
"object",
|
||||
"data",
|
||||
"mapper",
|
||||
"mapping",
|
||||
"query",
|
||||
"dql"
|
||||
],
|
||||
"homepage": "https://www.doctrine-project.org/projects/orm.html",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
|
||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
|
||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
|
||||
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
|
||||
],
|
||||
"support": {
|
||||
"chat": "https://www.doctrine-project.org/slack",
|
||||
"docs": "https://www.doctrine-project.org/projects/orm.html",
|
||||
"email": "doctrine-user@googlegroups.com",
|
||||
"issues": "https://github.com/doctrine/orm/issues",
|
||||
"rss": "https://github.com/doctrine/orm/releases.atom",
|
||||
"source": "https://github.com/doctrine/orm"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0",
|
||||
"php": "^7.3",
|
||||
"ext-ctype": "*",
|
||||
"composer-runtime-api": "^2",
|
||||
"doctrine/cache": "^1.12.1 || ^2.1.1",
|
||||
"doctrine/collections": "^1.5 || ^2.1",
|
||||
"doctrine/common": "^3.0.3",
|
||||
"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 || ^2",
|
||||
"doctrine/lexer": "^2 || ^3",
|
||||
"doctrine/persistence": "^2.4 || ^3",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0",
|
||||
"symfony/polyfill-php72": "^1.23",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
"doctrine/annotations": "~1.7",
|
||||
"doctrine/cache": "~1.6",
|
||||
"doctrine/collections": "^1.4",
|
||||
"doctrine/dbal": "dev-missed-commits",
|
||||
"doctrine/event-manager": "^1.0",
|
||||
"doctrine/inflector": "~1.0",
|
||||
"doctrine/instantiator": "~1.1",
|
||||
"doctrine/persistence": "^1.1",
|
||||
"doctrine/reflection": "^1.0",
|
||||
"ocramius/package-versions": "^1.1.2",
|
||||
"ocramius/proxy-manager": "^2.1.1",
|
||||
"symfony/console": "~4.0|~5.0",
|
||||
"symfony/var-dumper": "^4.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.13 || ^2",
|
||||
"doctrine/coding-standard": "^9.0.2 || ^14.0",
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0",
|
||||
"phpstan/extension-installer": "~1.1.0 || ^1.4",
|
||||
"phpstan/phpstan": "~1.4.10 || 2.1.23",
|
||||
"phpstan/phpstan-deprecation-rules": "^1 || ^2",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6",
|
||||
"psr/log": "^1 || ^2 || ^3",
|
||||
"symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0",
|
||||
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.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"
|
||||
"doctrine/coding-standard": "^6.0",
|
||||
"phpstan/phpstan": "^0.11",
|
||||
"phpunit/phpunit": "^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\ORM\\": "src"
|
||||
}
|
||||
"psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Performance\\": "tests/Performance",
|
||||
"Doctrine\\StaticAnalysis\\": "tests/StaticAnalysis",
|
||||
"Doctrine\\Tests\\": "tests/Tests"
|
||||
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
|
||||
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/doctrine"
|
||||
],
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"phpstan/extension-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "composer --working-dir docs update && ./docs/vendor/bin/build-docs.sh @additional_args"
|
||||
"bin": ["bin/doctrine"],
|
||||
"archive": {
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
|
||||
}
|
||||
}
|
||||
|
||||
4214
composer.lock
generated
Normal file
4214
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
composer.lock
|
||||
vendor/
|
||||
output/
|
||||
@@ -1,4 +1,4 @@
|
||||
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)
|
||||
The Doctrine2 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
|
||||
|
||||
@@ -337,7 +337,6 @@ BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
License is not intended to restrict the license of any rights under
|
||||
applicable law.
|
||||
|
||||
|
||||
Creative Commons Notice
|
||||
|
||||
Creative Commons is not a party to this License, and makes no warranty
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Doctrine ORM Documentation
|
||||
|
||||
The documentation is written in [ReStructured Text](https://docutils.sourceforge.io/rst.html).
|
||||
|
||||
## How to Generate:
|
||||
|
||||
In the project root, run
|
||||
|
||||
composer docs
|
||||
|
||||
This will generate the documentation into the `docs/output` subdirectory.
|
||||
|
||||
To browse the documentation, you need to run a webserver:
|
||||
|
||||
cd docs/output
|
||||
php -S localhost:8000
|
||||
|
||||
Now the documentation is available at [http://localhost:8000](http://localhost:8000).
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "doctrine/orm-docs",
|
||||
"description": "Documentation for the Object-Relational Mapper\"",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"require-dev": {
|
||||
"doctrine/docs-builder": "^1.0"
|
||||
}
|
||||
}
|
||||
@@ -32,39 +32,61 @@ The entity class:
|
||||
|
||||
namespace Geo\Entity;
|
||||
|
||||
use Geo\ValueObject\Point;
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
#[Entity]
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Location
|
||||
{
|
||||
#[Column(type: 'point')]
|
||||
private Point $point;
|
||||
/**
|
||||
* @ORM\Column(type="point")
|
||||
*
|
||||
* @var \Geo\ValueObject\Point
|
||||
*/
|
||||
private $point;
|
||||
|
||||
#[Column]
|
||||
private string $address;
|
||||
/**
|
||||
* @ORM\Column(type="string")
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $address;
|
||||
|
||||
public function setPoint(Point $point): void
|
||||
/**
|
||||
* @param \Geo\ValueObject\Point $point
|
||||
*/
|
||||
public function setPoint(\Geo\ValueObject\Point $point)
|
||||
{
|
||||
$this->point = $point;
|
||||
}
|
||||
|
||||
public function getPoint(): Point
|
||||
/**
|
||||
* @return \Geo\ValueObject\Point
|
||||
*/
|
||||
public function getPoint()
|
||||
{
|
||||
return $this->point;
|
||||
}
|
||||
|
||||
public function setAddress(string $address): void
|
||||
/**
|
||||
* @param string $address
|
||||
*/
|
||||
public function setAddress($address)
|
||||
{
|
||||
$this->address = $address;
|
||||
}
|
||||
|
||||
public function getAddress(): string
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
}
|
||||
|
||||
We use the custom type ``point`` in the ``#[Column]`` attribute of the
|
||||
We use the custom type ``point`` in the ``@Column`` docblock annotation of the
|
||||
``$point`` field. We will create this custom mapping type in the next chapter.
|
||||
|
||||
The point class:
|
||||
@@ -77,18 +99,29 @@ The point class:
|
||||
|
||||
class Point
|
||||
{
|
||||
public function __construct(
|
||||
private float $latitude,
|
||||
private float $longitude,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @param float $latitude
|
||||
* @param float $longitude
|
||||
*/
|
||||
public function __construct($latitude, $longitude)
|
||||
{
|
||||
$this->latitude = $latitude;
|
||||
$this->longitude = $longitude;
|
||||
}
|
||||
|
||||
public function getLatitude(): float
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getLatitude()
|
||||
{
|
||||
return $this->latitude;
|
||||
}
|
||||
|
||||
public function getLongitude(): float
|
||||
/**
|
||||
* @return float
|
||||
*/
|
||||
public function getLongitude()
|
||||
{
|
||||
return $this->longitude;
|
||||
}
|
||||
@@ -196,7 +229,7 @@ Example usage
|
||||
<?php
|
||||
|
||||
// Bootstrapping stuff...
|
||||
// $em = new \Doctrine\ORM\EntityManager($connection, $config);
|
||||
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
|
||||
|
||||
// Setup custom mapping type
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
@@ -218,8 +251,8 @@ Example usage
|
||||
$em->clear();
|
||||
|
||||
// Fetch the Location object
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location l WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'");
|
||||
$location = $query->getSingleResult();
|
||||
|
||||
/** @var Geo\ValueObject\Point */
|
||||
/** @var Geo\ValueObject\Point $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 ORM offers several ways to get access to
|
||||
traditionally. Doctrine 2 offers several ways to get access to
|
||||
these values and this article will describe all of them from
|
||||
different perspectives.
|
||||
|
||||
@@ -22,7 +22,7 @@ into the account can either be of positive or negative money
|
||||
values. Each account has a credit limit and the account is never
|
||||
allowed to have a balance below that value.
|
||||
|
||||
For simplicity we live in a world where money is composed of
|
||||
For simplicity we live in a world were money is composed of
|
||||
integers only. Also we omit the receiver/sender name, stated reason
|
||||
for transfer and the execution date. These all would have to be
|
||||
added on the ``Entry`` object.
|
||||
@@ -35,52 +35,55 @@ Our entities look like:
|
||||
|
||||
namespace Bank\Entities;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Account
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id;
|
||||
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
|
||||
private $id;
|
||||
|
||||
#[ORM\OneToMany(targetEntity: Entry::class, mappedBy: 'account', cascade: ['persist'])]
|
||||
private Collection $entries;
|
||||
/** @ORM\Column(type="string", unique=true) */
|
||||
private $no;
|
||||
|
||||
/** @ORM\OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"}) */
|
||||
private $entries;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\Column(type: 'string', unique: true)]
|
||||
private string $no,
|
||||
/** @ORM\Column(type="integer") */
|
||||
private $maxCredit = 0;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $maxCredit = 0,
|
||||
) {
|
||||
$this->entries = new ArrayCollection();
|
||||
public function __construct($no, $maxCredit = 0)
|
||||
{
|
||||
$this->no = $no;
|
||||
$this->maxCredit = $maxCredit;
|
||||
$this->entries = new \Doctrine\Common\Collections\ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
#[ORM\Entity]
|
||||
/**
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Entry
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id;
|
||||
/** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
|
||||
private $id;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\ManyToOne(targetEntity: Account::class, inversedBy: 'entries')]
|
||||
private Account $account,
|
||||
/** @ORM\ManyToOne(targetEntity="Account", inversedBy="entries") */
|
||||
private $account;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $amount,
|
||||
) {
|
||||
/** @ORM\Column(type="integer") */
|
||||
private $amount;
|
||||
|
||||
public function __construct($account, $amount)
|
||||
{
|
||||
$this->account = $account;
|
||||
$this->amount = $amount;
|
||||
// more stuff here, from/to whom, stated reason, execution date and such
|
||||
}
|
||||
|
||||
public function getAmount(): Amount
|
||||
public function getAmount()
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
@@ -138,14 +141,12 @@ collection, which means we can compute this value at runtime:
|
||||
class Account
|
||||
{
|
||||
// .. previous code
|
||||
|
||||
public function getBalance(): int
|
||||
public function getBalance()
|
||||
{
|
||||
$balance = 0;
|
||||
foreach ($this->entries as $entry) {
|
||||
$balance += $entry->getAmount();
|
||||
}
|
||||
|
||||
return $balance;
|
||||
}
|
||||
}
|
||||
@@ -169,11 +170,13 @@ relation with this method:
|
||||
<?php
|
||||
class Account
|
||||
{
|
||||
public function addEntry(int $amount): void
|
||||
public function addEntry($amount)
|
||||
{
|
||||
$this->assertAcceptEntryAllowed($amount);
|
||||
|
||||
$this->entries[] = new Entry($this, $amount);
|
||||
$e = new Entry($this, $amount);
|
||||
$this->entries[] = $e;
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,14 +185,11 @@ Now look at the following test-code for our entities:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class AccountTest extends TestCase
|
||||
class AccountTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testAddEntry()
|
||||
{
|
||||
$account = new Account("123456", maxCredit: 200);
|
||||
$account = new Account("123456", $maxCredit = 200);
|
||||
$this->assertEquals(0, $account->getBalance());
|
||||
|
||||
$account->addEntry(500);
|
||||
@@ -201,9 +201,9 @@ Now look at the following test-code for our entities:
|
||||
|
||||
public function testExceedMaxLimit()
|
||||
{
|
||||
$account = new Account("123456", maxCredit: 200);
|
||||
$account = new Account("123456", $maxCredit = 200);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->setExpectedException("Exception");
|
||||
$account->addEntry(-1000);
|
||||
}
|
||||
}
|
||||
@@ -214,12 +214,9 @@ To enforce our rule we can now implement the assertion in
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Account
|
||||
{
|
||||
// .. previous code
|
||||
|
||||
private function assertAcceptEntryAllowed(int $amount): void
|
||||
private function assertAcceptEntryAllowed($amount)
|
||||
{
|
||||
$futureBalance = $this->getBalance() + $amount;
|
||||
$allowedMinimalBalance = ($this->maxCredit * -1);
|
||||
@@ -263,20 +260,24 @@ entries collection) we want to add an aggregate field called
|
||||
<?php
|
||||
class Account
|
||||
{
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $balance = 0;
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
private $balance = 0;
|
||||
|
||||
public function getBalance(): int
|
||||
public function getBalance()
|
||||
{
|
||||
return $this->balance;
|
||||
}
|
||||
|
||||
public function addEntry(int $amount): void
|
||||
public function addEntry($amount)
|
||||
{
|
||||
$this->assertAcceptEntryAllowed($amount);
|
||||
|
||||
$this->entries[] = new Entry($this, $amount);
|
||||
$e = new Entry($this, $amount);
|
||||
$this->entries[] = $e;
|
||||
$this->balance += $amount;
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,15 +301,12 @@ 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(Account::class, $accId);
|
||||
$account1 = $em->find('Bank\Entities\Account', $accId);
|
||||
|
||||
// request 2 account
|
||||
$account2 = $em->find(Account::class, $accId);
|
||||
$account2 = $em->find('Bank\Entities\Account', $accId);
|
||||
|
||||
$account1->addEntry(-200);
|
||||
$account2->addEntry(-200);
|
||||
@@ -329,12 +327,10 @@ Optimistic locking is as easy as adding a version column:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
class Account
|
||||
{
|
||||
#[ORM\Column(type: 'integer')]
|
||||
#[ORM\Version]
|
||||
private int $version;
|
||||
/** @ORM\Column(type="integer") @ORM\Version */
|
||||
private $version;
|
||||
}
|
||||
|
||||
The previous example would then throw an exception in the face of
|
||||
@@ -348,11 +344,9 @@ the database using a FOR UPDATE.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Bank\Entities\Account;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
|
||||
$account = $em->find(Account::class, $accId, LockMode::PESSIMISTIC_READ);
|
||||
$account = $em->find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ);
|
||||
|
||||
Keeping Updates and Deletes in Sync
|
||||
-----------------------------------
|
||||
@@ -373,3 +367,4 @@ 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.
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class name. Now the new type can be used when mapping columns:
|
||||
<?php
|
||||
class MyPersistentClass
|
||||
{
|
||||
/** @Column(type="mytype") */
|
||||
/** @ORM\Column(type="mytype") */
|
||||
private $field;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ 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 ORM to persist an implementation of the
|
||||
Doctrine 2 to persist an implementation of the
|
||||
`Decorator Pattern <https://en.wikipedia.org/wiki/Decorator_pattern>`_
|
||||
|
||||
Component
|
||||
@@ -23,32 +23,51 @@ concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``.
|
||||
|
||||
namespace Test;
|
||||
|
||||
#[Entity]
|
||||
#[InheritanceType('SINGLE_TABLE')]
|
||||
#[DiscriminatorColumn(name: 'discr', type: 'string')]
|
||||
#[DiscriminatorMap(['cc' => Component\ConcreteComponent::class,
|
||||
'cd' => Decorator\ConcreteDecorator::class])]
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\InheritanceType("SINGLE_TABLE")
|
||||
* @ORM\DiscriminatorColumn(name="discr", type="string")
|
||||
* @ORM\DiscriminatorMap({
|
||||
* "cc" = "Test\Component\ConcreteComponent",
|
||||
* "cd" = "Test\Decorator\ConcreteDecorator"
|
||||
* })
|
||||
*/
|
||||
abstract class Component
|
||||
{
|
||||
/**
|
||||
* @ORM\Id @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
#[Id, Column]
|
||||
#[GeneratedValue(strategy: 'AUTO')]
|
||||
protected int|null $id = null;
|
||||
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
/** @ORM\Column(type="string", nullable=true) */
|
||||
protected $name;
|
||||
|
||||
public function getId(): int|null
|
||||
/**
|
||||
* Get id
|
||||
* @return integer $id
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setName(string $name): void
|
||||
/**
|
||||
* Set name
|
||||
* @param string $name
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
/**
|
||||
* Get name
|
||||
* @return string $name
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
@@ -68,9 +87,10 @@ purpose of keeping this example simple).
|
||||
|
||||
namespace Test\Component;
|
||||
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
use Test\Component;
|
||||
|
||||
#[Entity]
|
||||
/** @ORM\Entity */
|
||||
class ConcreteComponent extends Component
|
||||
{}
|
||||
|
||||
@@ -87,11 +107,16 @@ use a ``MappedSuperclass`` for this.
|
||||
|
||||
namespace Test;
|
||||
|
||||
#[MappedSuperclass]
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
/** @ORM\MappedSuperclass */
|
||||
abstract class Decorator extends Component
|
||||
{
|
||||
#[OneToOne(targetEntity: Component::class, cascade: ['all'])]
|
||||
#[JoinColumn(name: 'decorates', referencedColumnName: 'id')]
|
||||
|
||||
/**
|
||||
* @ORM\OneToOne(targetEntity="Test\Component", cascade={"all"})
|
||||
* @ORM\JoinColumn(name="decorates", referencedColumnName="id")
|
||||
*/
|
||||
protected $decorates;
|
||||
|
||||
/**
|
||||
@@ -107,19 +132,25 @@ use a ``MappedSuperclass`` for this.
|
||||
* (non-PHPdoc)
|
||||
* @see Test.Component::getName()
|
||||
*/
|
||||
public function getName(): string
|
||||
public function getName()
|
||||
{
|
||||
return 'Decorated ' . $this->getDecorates()->getName();
|
||||
}
|
||||
|
||||
/** the component being decorated */
|
||||
protected function getDecorates(): Component
|
||||
/**
|
||||
* the component being decorated
|
||||
* @return Component
|
||||
*/
|
||||
protected function getDecorates()
|
||||
{
|
||||
return $this->decorates;
|
||||
}
|
||||
|
||||
/** sets the component being decorated */
|
||||
protected function setDecorates(Component $c): void
|
||||
/**
|
||||
* sets the component being decorated
|
||||
* @param Component $c
|
||||
*/
|
||||
protected function setDecorates(Component $c)
|
||||
{
|
||||
$this->decorates = $c;
|
||||
}
|
||||
@@ -160,21 +191,30 @@ of the getSpecial() method to its return value.
|
||||
|
||||
namespace Test\Decorator;
|
||||
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
use Test\Decorator;
|
||||
|
||||
#[Entity]
|
||||
/** @ORM\Entity */
|
||||
class ConcreteDecorator extends Decorator
|
||||
{
|
||||
|
||||
#[Column(type: 'string', nullable: true)]
|
||||
protected string|null $special = null;
|
||||
/** @ORM\Column(type="string", nullable=true) */
|
||||
protected $special;
|
||||
|
||||
public function setSpecial(string|null $special): void
|
||||
/**
|
||||
* Set special
|
||||
* @param string $special
|
||||
*/
|
||||
public function setSpecial($special)
|
||||
{
|
||||
$this->special = $special;
|
||||
}
|
||||
|
||||
public function getSpecial(): string|null
|
||||
/**
|
||||
* Get special
|
||||
* @return string $special
|
||||
*/
|
||||
public function getSpecial()
|
||||
{
|
||||
return $this->special;
|
||||
}
|
||||
@@ -183,7 +223,7 @@ of the getSpecial() method to its return value.
|
||||
* (non-PHPdoc)
|
||||
* @see Test.Component::getName()
|
||||
*/
|
||||
public function getName(): string
|
||||
public function getName()
|
||||
{
|
||||
return '[' . $this->getSpecial()
|
||||
. '] ' . parent::getName();
|
||||
@@ -204,7 +244,7 @@ objects
|
||||
use Test\Component\ConcreteComponent,
|
||||
Test\Decorator\ConcreteDecorator;
|
||||
|
||||
// assumes Doctrine ORM is configured and an instance of
|
||||
// assumes Doctrine 2 is configured and an instance of
|
||||
// an EntityManager is available as $em
|
||||
|
||||
// create a new concrete component
|
||||
@@ -237,3 +277,4 @@ objects
|
||||
|
||||
echo $d->getName();
|
||||
// prints: [Really] Decorated Test Component 2
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Extending DQL in Doctrine ORM: Custom AST Walkers
|
||||
Extending DQL in Doctrine 2: Custom AST Walkers
|
||||
===============================================
|
||||
|
||||
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||
@@ -12,7 +12,7 @@ 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 ORM in contrast has a real parser for the DQL language,
|
||||
Doctrine 2 in contrast has a real parser for the DQL language,
|
||||
which transforms the DQL statement into an
|
||||
`Abstract Syntax Tree <https://en.wikipedia.org/wiki/Abstract_syntax_tree>`_
|
||||
and generates the appropriate SQL statement for it. Since this
|
||||
@@ -28,19 +28,17 @@ generating the SQL statement.
|
||||
There are two types of custom tree walkers that you can hook into
|
||||
the DQL parser:
|
||||
|
||||
|
||||
- An output walker. This one actually generates the SQL, and there
|
||||
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
|
||||
example:
|
||||
|
||||
|
||||
- Modify the AST to generate a Count Query to be used with a
|
||||
paginator for any given DQL query.
|
||||
- Modify the Output Walker to generate vendor-specific SQL
|
||||
@@ -50,7 +48,7 @@ example:
|
||||
- Modify the Output walker to pretty print the SQL for debugging
|
||||
purposes.
|
||||
|
||||
In this cookbook-entry I will show examples of the first two
|
||||
In this cookbook-entry I will show examples on the first two
|
||||
points. There are probably much more use-cases.
|
||||
|
||||
Generic count query for pagination
|
||||
@@ -64,7 +62,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 it's the
|
||||
Now in this query the blog post is the root entity, meaning its 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 +77,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. Let's see how the
|
||||
can use a tree walker to modify the AST for you. Lets see how the
|
||||
API would look for this use-case:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -101,16 +99,8 @@ The ``Paginate::count(Query $query)`` looks like:
|
||||
{
|
||||
static public function count(Query $query)
|
||||
{
|
||||
/*
|
||||
To avoid changing the $query passed into the method and to make sure a possibly existing
|
||||
ResultSetMapping is discarded, we create a new query object any copy relevant data over.
|
||||
*/
|
||||
$countQuery = new Query($query->getEntityManager());
|
||||
$countQuery->setDQL($query->getDQL());
|
||||
$countQuery->setParameters(clone $query->getParameters());
|
||||
foreach ($query->getHints() as $name => $value) {
|
||||
$countQuery->setHint($name, $value);
|
||||
}
|
||||
/** @var Query $countQuery */
|
||||
$countQuery = clone $query;
|
||||
|
||||
$countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker'));
|
||||
$countQuery->setFirstResult(null)->setMaxResults(null);
|
||||
@@ -119,7 +109,7 @@ The ``Paginate::count(Query $query)`` looks like:
|
||||
}
|
||||
}
|
||||
|
||||
This resets the limit clause first and max results
|
||||
It clones the query, resets the limit clause first and max results
|
||||
and registers the ``CountSqlWalker`` custom tree walker which
|
||||
will modify the AST to execute a count query. The walkers
|
||||
implementation is:
|
||||
@@ -138,7 +128,7 @@ implementation is:
|
||||
{
|
||||
$parent = null;
|
||||
$parentName = null;
|
||||
foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) {
|
||||
foreach ($this->getQueryComponents() as $dqlAlias => $qComp) {
|
||||
if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) {
|
||||
$parent = $qComp;
|
||||
$parentName = $dqlAlias;
|
||||
@@ -175,7 +165,7 @@ can be set via ``Query::setHint($name, $value)`` as shown in the
|
||||
previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint.
|
||||
|
||||
We will implement a custom Output Walker that allows to specify the
|
||||
``SQL_NO_CACHE`` query hint.
|
||||
SQL\_NO\_CACHE query hint.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -188,7 +178,7 @@ We will implement a custom Output Walker that allows to specify the
|
||||
|
||||
Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will
|
||||
modify the generation of the SELECT clause, adding the
|
||||
``SQL_NO_CACHE`` on those queries that need it:
|
||||
SQL\_NO\_CACHE on those queries that need it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -222,4 +212,3 @@ understanding of the DQL Parser and Walkers, but may offer your
|
||||
huge benefits with using vendor specific features. This would still
|
||||
allow you write DQL queries instead of NativeQueries to make use of
|
||||
vendor specific features.
|
||||
|
||||
|
||||
@@ -10,14 +10,13 @@ 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 ORM also allows you to handwrite
|
||||
It is worth to mention that Doctrine 2 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
|
||||
``EntityManager#createNativeQuery()`` API as described in
|
||||
the :doc:`Native Query <../reference/native-sql>` chapter.
|
||||
|
||||
|
||||
The DQL Parser has hooks to register functions that can then be
|
||||
used in your DQL queries and transformed into SQL, allowing to
|
||||
extend Doctrines Query capabilities to the vendors strength. This
|
||||
@@ -46,7 +45,7 @@ configuration:
|
||||
$config->addCustomNumericFunction($name, $class);
|
||||
$config->addCustomDatetimeFunction($name, $class);
|
||||
|
||||
$em = new EntityManager($connection, $config);
|
||||
$em = EntityManager::create($dbParams, $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
|
||||
@@ -99,12 +98,12 @@ discuss it step by step:
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$parser->match(TokenType::T_IDENTIFIER); // (2)
|
||||
$parser->match(TokenType::T_OPEN_PARENTHESIS); // (3)
|
||||
$parser->match(Lexer::T_IDENTIFIER); // (2)
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS); // (3)
|
||||
$this->firstDateExpression = $parser->ArithmeticPrimary(); // (4)
|
||||
$parser->match(TokenType::T_COMMA); // (5)
|
||||
$parser->match(Lexer::T_COMMA); // (5)
|
||||
$this->secondDateExpression = $parser->ArithmeticPrimary(); // (6)
|
||||
$parser->match(TokenType::T_CLOSE_PARENTHESIS); // (3)
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3)
|
||||
}
|
||||
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
@@ -131,8 +130,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 :ref:`DQL EBNF grammar
|
||||
<dql_ebnf_grammar>`
|
||||
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>`_
|
||||
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
|
||||
@@ -183,23 +182,23 @@ I'll skip the blah and show the code for this function:
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$parser->match(TokenType::T_IDENTIFIER);
|
||||
$parser->match(TokenType::T_OPEN_PARENTHESIS);
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->firstDateExpression = $parser->ArithmeticPrimary();
|
||||
|
||||
$parser->match(TokenType::T_COMMA);
|
||||
$parser->match(TokenType::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
$this->intervalExpression = $parser->ArithmeticPrimary();
|
||||
|
||||
$parser->match(TokenType::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
|
||||
/** @var Lexer $lexer */
|
||||
$lexer = $parser->getLexer();
|
||||
$this->unit = $lexer->token['value'];
|
||||
|
||||
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
|
||||
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||
@@ -240,7 +239,7 @@ 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 ORM we will come with the current set of functions, however for
|
||||
For 2.0 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.
|
||||
|
||||
|
||||
@@ -3,91 +3,53 @@ Entities in the Session
|
||||
|
||||
There are several use-cases to save entities in the session, for example:
|
||||
|
||||
1. User data
|
||||
1. User object
|
||||
2. Multi-step forms
|
||||
|
||||
To achieve this with Doctrine you have to pay attention to some details to get
|
||||
this working.
|
||||
|
||||
Updating an entity
|
||||
------------------
|
||||
Merging entity into an EntityManager
|
||||
------------------------------------
|
||||
|
||||
In Doctrine an entity objects has to be "managed" by an EntityManager to be
|
||||
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.
|
||||
In Doctrine, an entity objects has to be "managed" by an EntityManager to be
|
||||
updated. 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.
|
||||
|
||||
For a representative User object the code to get data from the session into a
|
||||
managed Doctrine object can look like these examples:
|
||||
It is a good idea to avoid storing entities in serialized formats such as
|
||||
``$_SESSION``: instead, store the entity identifiers or raw data.
|
||||
|
||||
Working with scalars
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In simpler applications there is no need to work with objects in sessions and you can use
|
||||
separate session elements.
|
||||
For a representative User object the code to get turn an instance from
|
||||
the session into a managed Doctrine object looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
require_once 'bootstrap.php';
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
|
||||
session_start();
|
||||
if (isset($_SESSION['userId']) && is_int($_SESSION['userId'])) {
|
||||
$userId = $_SESSION['userId'];
|
||||
if (isset($_SESSION['user'])) {
|
||||
$user = $em->find(User::class, $_SESSION['user']);
|
||||
|
||||
$em = GetEntityManager(); // creates an EntityManager
|
||||
$user = $em->find(User::class, $userId);
|
||||
|
||||
$user->setValue($_SESSION['storedValue']);
|
||||
|
||||
$em->flush();
|
||||
if (! $user instanceof User) {
|
||||
// user not found in the database
|
||||
$_SESSION['user'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
Working with custom data transfer objects
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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
|
||||
-----------------------------------
|
||||
|
||||
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 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);
|
||||
$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.
|
||||
|
||||
$_SESSION['user'] = $userDto;
|
||||
Serializing entities into the session
|
||||
-------------------------------------
|
||||
|
||||
Serializing entities in the session means serializing also all associated
|
||||
entities and collections. While this might look like a quick solution in
|
||||
simple applications, you will encounter problems due to the fact that the
|
||||
data in the session is stale.
|
||||
|
||||
In order to prevent working with stale data, try saving only minimal
|
||||
information about your entities in your session, without storing entire
|
||||
entity objects. Should you need the full information of an object, so it
|
||||
is suggested to re-query the database, which is usually the most
|
||||
authoritative source of information in typical PHP applications.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
Generated Columns
|
||||
=================
|
||||
|
||||
Generated columns, sometimes also called virtual columns, are populated by
|
||||
the database engine itself. They are a tool for performance optimization, to
|
||||
avoid calculating a value on each query.
|
||||
|
||||
You can define generated columns on entities and have Doctrine map the values
|
||||
to your entity.
|
||||
|
||||
Declaring a generated column
|
||||
----------------------------
|
||||
|
||||
There is no explicit mapping instruction for generated columns. Instead, you
|
||||
specify that the column should not be written to, and define a custom column
|
||||
definition.
|
||||
|
||||
.. literalinclude:: generated-columns/Person.php
|
||||
:language: php
|
||||
|
||||
* ``insertable``, ``updatable``: Setting these to false tells Doctrine to never
|
||||
write this column - writing to a generated column would result in an error
|
||||
from the database.
|
||||
* ``columnDefinition``: We specify the full DDL to create the column. To allow
|
||||
to use database specific features, this attribute does not use Doctrine Query
|
||||
Language but native SQL. Note that you need to reference columns by their
|
||||
database name (either explicitly set in the mapping or per the current
|
||||
:doc:`naming strategy <../reference/namingstrategy>`).
|
||||
Be aware that specifying a column definition makes the ``SchemaTool``
|
||||
completely ignore all other configuration for this column. See also
|
||||
:ref:`#[Column] <attrref_column>`
|
||||
* ``generated``: Specifying that this column is always generated tells Doctrine
|
||||
to update the field on the entity with the value from the database after
|
||||
every write operation.
|
||||
|
||||
Advanced example: Extracting a value from a JSON structure
|
||||
----------------------------------------------------------
|
||||
|
||||
Lets assume we have an entity that stores a blogpost as structured JSON.
|
||||
To avoid extracting all titles on the fly when listing the posts, we create a
|
||||
generated column with the field.
|
||||
|
||||
.. literalinclude:: generated-columns/Article.php
|
||||
:language: php
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class Article
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* When working with Postgres, it is recommended to use the jsonb
|
||||
* format for better performance.
|
||||
*/
|
||||
#[ORM\Column(options: ['jsonb' => true])]
|
||||
private array $content;
|
||||
|
||||
/**
|
||||
* Because we specify NOT NULL, inserting will fail if the content does
|
||||
* not have a string in the title field.
|
||||
*/
|
||||
#[ORM\Column(
|
||||
insertable: false,
|
||||
updatable: false,
|
||||
columnDefinition: "VARCHAR(255) generated always as (content->>'title') stored NOT NULL",
|
||||
generated: 'ALWAYS',
|
||||
)]
|
||||
private string $title;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class Person
|
||||
{
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $firstName;
|
||||
|
||||
#[ORM\Column(type: 'string', name: 'name')]
|
||||
private string $lastName;
|
||||
|
||||
#[ORM\Column(
|
||||
type: 'string',
|
||||
insertable: false,
|
||||
updatable: false,
|
||||
columnDefinition: "VARCHAR(255) GENERATED ALWAYS AS (concat(firstName, ' ', name) stored NOT NULL",
|
||||
generated: 'ALWAYS',
|
||||
)]
|
||||
private string $fullName;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
Implementing ArrayAccess for Domain Objects
|
||||
===========================================
|
||||
|
||||
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
This recipe will show you how to implement ArrayAccess for your
|
||||
domain objects in order to allow more uniform access, for example
|
||||
@@ -16,7 +16,6 @@ In this implementation we will make use of PHPs highly dynamic
|
||||
nature to dynamically access properties of a subtype in a supertype
|
||||
at runtime. Note that this implementation has 2 main caveats:
|
||||
|
||||
|
||||
- It will not work with private fields
|
||||
- It will not go through any getters/setters
|
||||
|
||||
@@ -28,15 +27,15 @@ at runtime. Note that this implementation has 2 main caveats:
|
||||
public function offsetExists($offset) {
|
||||
return isset($this->$offset);
|
||||
}
|
||||
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
$this->$offset = $value;
|
||||
}
|
||||
|
||||
|
||||
public function offsetGet($offset) {
|
||||
return $this->$offset;
|
||||
}
|
||||
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
$this->$offset = null;
|
||||
}
|
||||
@@ -50,7 +49,6 @@ Again we use PHPs dynamic nature to invoke methods on a subtype
|
||||
from a supertype at runtime. This implementation has the following
|
||||
caveats:
|
||||
|
||||
|
||||
- It relies on a naming convention
|
||||
- The semantics of offsetExists can differ
|
||||
- offsetUnset will not work with typehinted setters
|
||||
@@ -65,15 +63,15 @@ caveats:
|
||||
$value = $this->{"get$offset"}();
|
||||
return $value !== null;
|
||||
}
|
||||
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
$this->{"set$offset"}($value);
|
||||
}
|
||||
|
||||
|
||||
public function offsetGet($offset) {
|
||||
return $this->{"get$offset"}();
|
||||
}
|
||||
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
$this->{"set$offset"}(null);
|
||||
}
|
||||
@@ -95,18 +93,17 @@ exception (i.e. BadMethodCallException).
|
||||
public function offsetExists($offset) {
|
||||
// option 1 or option 2
|
||||
}
|
||||
|
||||
|
||||
public function offsetSet($offset, $value) {
|
||||
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
|
||||
}
|
||||
|
||||
|
||||
public function offsetGet($offset) {
|
||||
// option 1 or option 2
|
||||
}
|
||||
|
||||
|
||||
public function offsetUnset($offset) {
|
||||
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Implementing the Notify ChangeTracking Policy
|
||||
=============================================
|
||||
|
||||
.. sectionauthor:: Roman Borschel <roman@code-factory.org>
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
The NOTIFY change-tracking policy is the most effective
|
||||
change-tracking policy provided by Doctrine but it requires some
|
||||
@@ -10,11 +10,6 @@ code should look like. We will implement it on a
|
||||
`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
|
||||
----------------------------------
|
||||
|
||||
@@ -27,8 +22,8 @@ implement the ``NotifyPropertyChanged`` interface from the
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Persistence\NotifyPropertyChanged;
|
||||
use Doctrine\Persistence\PropertyChangedListener;
|
||||
use Doctrine\Common\NotifyPropertyChanged;
|
||||
use Doctrine\Common\PropertyChangedListener;
|
||||
|
||||
abstract class DomainObject implements NotifyPropertyChanged
|
||||
{
|
||||
@@ -55,7 +50,7 @@ listeners:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Mapping not shown, either in attributes, annotations, xml or yaml as usual
|
||||
// Mapping not shown, either in annotations or xml as usual
|
||||
class MyEntity extends DomainObject
|
||||
{
|
||||
private $data;
|
||||
@@ -73,3 +68,4 @@ 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,12 +1,12 @@
|
||||
Mysql Enums
|
||||
===========
|
||||
|
||||
The type system of Doctrine ORM consists of flyweights, which means there is only
|
||||
The type system of Doctrine 2 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 ORM application you will get
|
||||
When using Enums with a non-tweaked Doctrine 2 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
|
||||
@@ -43,13 +43,16 @@ entities:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
/** @ORM\Entity */
|
||||
class Article
|
||||
{
|
||||
const STATUS_VISIBLE = 'visible';
|
||||
const STATUS_INVISIBLE = 'invisible';
|
||||
|
||||
/** @Column(type="string") */
|
||||
/** @ORM\Column(type="string") */
|
||||
private $status;
|
||||
|
||||
public function setStatus($status)
|
||||
@@ -67,10 +70,13 @@ the **columnDefinition** attribute.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
/** @ORM\Entity */
|
||||
class Article
|
||||
{
|
||||
/** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
|
||||
/** @ORM\Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
|
||||
private $status;
|
||||
}
|
||||
|
||||
@@ -131,10 +137,13 @@ Then in your entity you can just use this type:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity */
|
||||
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
/** @ORM\Entity */
|
||||
class Article
|
||||
{
|
||||
/** @Column(type="enumvisibility") */
|
||||
/** @ORM\Column(type="enumvisibility") */
|
||||
private $status;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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 ORM includes a new utility called the ``ResolveTargetEntityListener``,
|
||||
Doctrine 2.2 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
|
||||
@@ -47,8 +49,10 @@ A Customer entity
|
||||
use Acme\CustomerModule\Entity\Customer as BaseCustomer;
|
||||
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'customer')]
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="customer")
|
||||
*/
|
||||
class Customer extends BaseCustomer implements InvoiceSubjectInterface
|
||||
{
|
||||
// In our example, any methods defined in the InvoiceSubjectInterface
|
||||
@@ -64,15 +68,22 @@ An Invoice entity
|
||||
|
||||
namespace Acme\InvoiceModule\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping AS ORM;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'invoice')]
|
||||
/**
|
||||
* Represents an Invoice.
|
||||
*
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="invoice")
|
||||
*/
|
||||
class Invoice
|
||||
{
|
||||
#[ORM\ManyToOne(targetEntity: InvoiceSubjectInterface::class)]
|
||||
protected InvoiceSubjectInterface $subject;
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface")
|
||||
* @var InvoiceSubjectInterface
|
||||
*/
|
||||
protected $subject;
|
||||
}
|
||||
|
||||
An InvoiceSubjectInterface
|
||||
@@ -118,8 +129,7 @@ the targetEntity resolution will occur reliably:
|
||||
// Add the ResolveTargetEntityListener
|
||||
$evm->addEventListener(Doctrine\ORM\Events::loadClassMetadata, $rtel);
|
||||
|
||||
$connection = \Doctrine\DBAL\DriverManager::getConnection($connectionOptions, $config, $evm);
|
||||
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
|
||||
Final Thoughts
|
||||
--------------
|
||||
@@ -128,3 +138,4 @@ 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.
|
||||
|
||||
|
||||
@@ -23,31 +23,32 @@ appropriate autoloaders.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
namespace DoctrineExtensions;
|
||||
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
|
||||
|
||||
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||
use Doctrine\ORM\Mapping;
|
||||
|
||||
class TablePrefix
|
||||
{
|
||||
protected $prefix = '';
|
||||
|
||||
|
||||
public function __construct($prefix)
|
||||
{
|
||||
$this->prefix = (string) $prefix;
|
||||
}
|
||||
|
||||
|
||||
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
|
||||
{
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
|
||||
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
|
||||
$classMetadata->setPrimaryTable([
|
||||
'name' => $this->prefix . $classMetadata->getTableName()
|
||||
]);
|
||||
if ($classMetadata->inheritanceType !== Mapping\InheritanceType::SINGLE_TABLE ||
|
||||
$classMetadata->getName() === $classMetadata->rootEntityName) {
|
||||
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
|
||||
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
|
||||
foreach ($classMetadata->associationMappings as $fieldName => $mapping) {
|
||||
if ($mapping['type'] == Mapping\ClassMetadata::MANY_TO_MANY && $mapping['isOwningSide']) {
|
||||
$mappedTableName = $mapping['joinTable']['name'];
|
||||
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
|
||||
}
|
||||
@@ -68,17 +69,17 @@ before the prefix has been set.
|
||||
If you set this listener up, be aware that you will need
|
||||
to clear your caches and drop then recreate your database schema.
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
// $connectionOptions and $config set earlier
|
||||
|
||||
|
||||
$evm = new \Doctrine\Common\EventManager;
|
||||
|
||||
|
||||
// Table Prefix
|
||||
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
|
||||
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
|
||||
|
||||
$em = new \Doctrine\ORM\EntityManager($connection, $config, $evm);
|
||||
|
||||
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ Scenario / Problem
|
||||
Given a Content-Management-System, we probably want to add / edit
|
||||
some so-called "blocks" and "panels". What are they for?
|
||||
|
||||
|
||||
- A block might be a registration form, some text content, a table
|
||||
with information. A good example might also be a small calendar.
|
||||
- A panel is by definition a block that can itself contain blocks.
|
||||
@@ -23,14 +22,13 @@ So, in this scenario, when building your CMS, you will surely add
|
||||
lots of blocks and panels to your pages and you will find yourself
|
||||
highly uncomfortable because of the following:
|
||||
|
||||
|
||||
- Every existing page needs to know about the panels it contains -
|
||||
therefore, you'll have an association to your panels. But if you've
|
||||
got several types of panels - what do you do? Add an association to
|
||||
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 ORM would do the rest for you, i.e.
|
||||
AbstractPanelType and Doctrine 2 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
|
||||
@@ -58,7 +56,6 @@ the middle of your page, for example).
|
||||
|
||||
Such an interface could look like this:
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
@@ -152,16 +149,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 ORM)
|
||||
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
|
||||
*
|
||||
* This is a doctrine field, so make sure that you use a
|
||||
#[Column] attribute or setup your yaml or xml files correctly
|
||||
* This is a doctrine field, so make sure that you use an @column annotation or setup your
|
||||
* xml files correctly
|
||||
* @var string
|
||||
*/
|
||||
protected $strategyClassName;
|
||||
|
||||
/**
|
||||
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine ORM.
|
||||
* This var contains an instance of $this->blockStrategy. Will not be persisted by Doctrine 2.
|
||||
*
|
||||
* @var BlockStrategyInterface
|
||||
*/
|
||||
@@ -199,7 +196,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 ORM
|
||||
Now, the important point is that $strategyClassName is a Doctrine 2
|
||||
field, i.e. Doctrine will persist this value. This is only the
|
||||
class name of your strategy and not an instance!
|
||||
|
||||
@@ -213,15 +210,14 @@ This might look like this:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\EventSubscriber;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM,
|
||||
Doctrine\Common;
|
||||
|
||||
/**
|
||||
* The BlockStrategyEventListener will initialize a strategy after the
|
||||
* block itself was loaded.
|
||||
*/
|
||||
class BlockStrategyEventListener implements EventSubscriber {
|
||||
class BlockStrategyEventListener implements Common\EventSubscriber {
|
||||
|
||||
protected $view;
|
||||
|
||||
@@ -230,11 +226,11 @@ This might look like this:
|
||||
}
|
||||
|
||||
public function getSubscribedEvents() {
|
||||
return array(Events::postLoad);
|
||||
return array(ORM\Events::postLoad);
|
||||
}
|
||||
|
||||
public function postLoad(LifecycleEventArgs $args) {
|
||||
$blockItem = $args->getObject();
|
||||
public function postLoad(ORM\Event\LifecycleEventArgs $args) {
|
||||
$blockItem = $args->getEntity();
|
||||
|
||||
// Both blocks and panels are instances of Block\AbstractBlock
|
||||
if ($blockItem instanceof Block\AbstractBlock) {
|
||||
@@ -251,3 +247,4 @@ 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 ORM does not ship with any internal validators, the reason
|
||||
Doctrine 2 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.
|
||||
@@ -11,11 +11,10 @@ What we offer are hooks to execute any kind of validation.
|
||||
.. note::
|
||||
|
||||
You don't need to validate your entities in the lifecycle
|
||||
events. It is only one of many options. Of course you can also
|
||||
events. Its only one of many options. Of course you can also
|
||||
perform validations in value setters or any other method of your
|
||||
entities that are used in your code.
|
||||
|
||||
|
||||
Entities can register lifecycle event methods with Doctrine that
|
||||
are called on different occasions. For validation we would need to
|
||||
hook into the events called before persisting and updating. Even
|
||||
@@ -25,8 +24,8 @@ the additional benefit of being able to re-use your validation in
|
||||
any other part of your domain.
|
||||
|
||||
Say we have an ``Order`` with several ``OrderLine`` instances. We
|
||||
never want to allow any customer to order for a larger sum than they
|
||||
are allowed to:
|
||||
never want to allow any customer to order for a larger sum than he
|
||||
is allowed to:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -53,37 +52,22 @@ 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 Attributes:
|
||||
First Annotations:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\HasLifecycleCallbacks;
|
||||
use Doctrine\ORM\Mapping\PrePersist;
|
||||
use Doctrine\ORM\Mapping\PreUpdate;
|
||||
|
||||
#[Entity]
|
||||
#[HasLifecycleCallbacks]
|
||||
class Order
|
||||
{
|
||||
#[PrePersist, PreUpdate]
|
||||
public function assertCustomerAllowedBuying() {}
|
||||
}
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
As Annotations:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @HasLifecycleCallbacks
|
||||
* @ORM\Entity
|
||||
* @ORM\HasLifecycleCallbacks
|
||||
*/
|
||||
class Order
|
||||
{
|
||||
/**
|
||||
* @PrePersist @PreUpdate
|
||||
* @ORM\PrePersist @ORM\PreUpdate
|
||||
*/
|
||||
public function assertCustomerAllowedBuying() {}
|
||||
}
|
||||
@@ -95,8 +79,8 @@ In XML Mappings:
|
||||
<doctrine-mapping>
|
||||
<entity name="Order">
|
||||
<lifecycle-callbacks>
|
||||
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
|
||||
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
|
||||
<lifecycle-callback type="prePersist" method="assertCustomerAllowedBuying" />
|
||||
<lifecycle-callback type="preUpdate" method="assertCustomerAllowedBuying" />
|
||||
</lifecycle-callbacks>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -114,9 +98,14 @@ validation callbacks.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
class Order
|
||||
{
|
||||
#[PrePersist, PreUpdate]
|
||||
/**
|
||||
* @ORM\PrePersist @ORM\PreUpdate
|
||||
*/
|
||||
public function validate()
|
||||
{
|
||||
if (!($this->plannedShipDate instanceof DateTime)) {
|
||||
|
||||
@@ -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 ORM.
|
||||
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
|
||||
|
||||
DateTime changes are detected by Reference
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -16,15 +16,15 @@ these comparisons are always made **BY REFERENCE**. That means the following cha
|
||||
|
||||
<?php
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
#[Entity]
|
||||
/** @ORM\Entity */
|
||||
class Article
|
||||
{
|
||||
#[Column(type: 'datetime')]
|
||||
private DateTime $updated;
|
||||
/** @ORM\Column(type="datetime") */
|
||||
private $updated;
|
||||
|
||||
public function setUpdated(): void
|
||||
public function setUpdated()
|
||||
{
|
||||
// will NOT be saved in the database
|
||||
$this->updated->modify("now");
|
||||
@@ -36,14 +36,12 @@ The way to go would be:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use DateTime;
|
||||
|
||||
class Article
|
||||
{
|
||||
public function setUpdated(): void
|
||||
public function setUpdated()
|
||||
{
|
||||
// WILL be saved in the database
|
||||
$this->updated = new DateTime("now");
|
||||
$this->updated = new \DateTime("now");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +61,7 @@ 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 ORM)
|
||||
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
|
||||
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 <https://derickrethans.nl/storing-date-time-in-database.html>`_.
|
||||
@@ -72,7 +70,7 @@ The problem is simple. Not a single database vendor saves the timezone, only the
|
||||
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 ORM. However there is a workaround
|
||||
The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround
|
||||
that even allows correct date-time handling with timezones:
|
||||
|
||||
1. Always convert any DateTime instance to UTC.
|
||||
@@ -89,14 +87,13 @@ 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
|
||||
{
|
||||
private static DateTimeZone $utc;
|
||||
static private $utc;
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
@@ -129,10 +126,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()
|
||||
{
|
||||
return self::$utc ??= new DateTimeZone('UTC');
|
||||
return self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +149,6 @@ code before bootstrapping the ORM:
|
||||
Type::overrideType('datetime', UTCDateTimeType::class);
|
||||
Type::overrideType('datetimetz', UTCDateTimeType::class);
|
||||
|
||||
|
||||
To be able to transform these values
|
||||
back into their real timezone you have to save the timezone in a separate field of the entity
|
||||
requiring timezoned datetimes:
|
||||
@@ -160,17 +156,20 @@ requiring timezoned datetimes:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
namespace Shipping;
|
||||
|
||||
use Doctrine\ORM\Annotation as ORM;
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @ORM\Entity
|
||||
*/
|
||||
class Event
|
||||
{
|
||||
/** @Column(type="datetime") */
|
||||
/** @ORM\Column(type="datetime") */
|
||||
private $created;
|
||||
|
||||
/** @Column(type="string") */
|
||||
/** @ORM\Column(type="string") */
|
||||
private $timezone;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
Welcome to Doctrine 2 ORM's documentation!
|
||||
==========================================
|
||||
ORM Documentation
|
||||
=================
|
||||
|
||||
The Doctrine documentation is comprised of tutorials, a reference section and
|
||||
cookbook articles that explain different parts of the Object Relational mapper.
|
||||
|
||||
Doctrine DBAL and Doctrine Common both have their own documentation.
|
||||
The Doctrine ORM documentation is comprised of tutorials, a reference section and
|
||||
cookbook articles that explain different parts of the Object Relational Mapper.
|
||||
|
||||
Getting Help
|
||||
------------
|
||||
@@ -13,115 +11,14 @@ If this documentation is not helping to answer questions you have about
|
||||
Doctrine ORM don't panic. You can get help from different sources:
|
||||
|
||||
- There is a :doc:`FAQ <reference/faq>` with answers to frequent questions.
|
||||
- The `Doctrine Mailing List <https://groups.google.com/group/doctrine-user>`_
|
||||
- Slack chat room `#orm <https://www.doctrine-project.org/slack>`_
|
||||
- Report a bug on `GitHub <https://github.com/doctrine/orm/issues>`_.
|
||||
- On `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 table
|
||||
of contents.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
* **Tutorial**:
|
||||
:doc:`Getting Started with Doctrine <tutorials/getting-started>`
|
||||
|
||||
* **Setup**:
|
||||
:doc:`Installation & Configuration <reference/configuration>`
|
||||
|
||||
Mapping Objects onto a Database
|
||||
-------------------------------
|
||||
|
||||
* **Mapping**:
|
||||
:doc:`Objects <reference/basic-mapping>` \|
|
||||
:doc:`Associations <reference/association-mapping>` \|
|
||||
:doc:`Inheritance <reference/inheritance-mapping>`
|
||||
|
||||
* **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>`
|
||||
|
||||
Working with Objects
|
||||
--------------------
|
||||
|
||||
* **Basic Reference**:
|
||||
:doc:`Entities <reference/working-with-objects>` \|
|
||||
:doc:`Associations <reference/working-with-associations>` \|
|
||||
:doc:`Events <reference/events>`
|
||||
|
||||
* **Query Reference**:
|
||||
:doc:`DQL <reference/dql-doctrine-query-language>` \|
|
||||
:doc:`QueryBuilder <reference/query-builder>` \|
|
||||
:doc:`Native SQL <reference/native-sql>`
|
||||
|
||||
* **Internals**:
|
||||
:doc:`Internals explained <reference/unitofwork>` \|
|
||||
:doc:`Associations <reference/unitofwork-associations>`
|
||||
|
||||
Advanced Topics
|
||||
---------------
|
||||
|
||||
* :doc:`Architecture <reference/architecture>`
|
||||
* :doc:`Advanced Configuration <reference/advanced-configuration>`
|
||||
* :doc:`Limitations and known issues <reference/limitations-and-known-issues>`
|
||||
* :doc:`Commandline Tools <reference/tools>`
|
||||
* :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>`
|
||||
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||
* :doc:`Best Practices <reference/best-practices>`
|
||||
* :doc:`Metadata Drivers <reference/metadata-drivers>`
|
||||
* :doc:`Batch Processing <reference/batch-processing>`
|
||||
* :doc:`Second Level Cache <reference/second-level-cache>`
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
|
||||
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
|
||||
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
|
||||
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
|
||||
* :doc:`Ordered associations <tutorials/ordered-associations>`
|
||||
* :doc:`Pagination <tutorials/pagination>`
|
||||
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
|
||||
* :doc:`Embeddables <tutorials/embeddables>`
|
||||
|
||||
Changelogs
|
||||
----------
|
||||
|
||||
* `Upgrade <https://github.com/doctrine/orm/blob/HEAD/UPGRADE.md>`_
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
* **Patterns**:
|
||||
:doc:`Aggregate Fields <cookbook/aggregate-fields>` \|
|
||||
:doc:`Generated/Virtual Columns <cookbook/generated-columns>` \|
|
||||
:doc:`Decorator Pattern <cookbook/decorator-pattern>` \|
|
||||
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||
|
||||
* **DQL Extension Points**:
|
||||
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` \|
|
||||
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
|
||||
|
||||
* **Implementation**:
|
||||
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` \|
|
||||
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` \|
|
||||
:doc:`Working with DateTime <cookbook/working-with-datetime>` \|
|
||||
:doc:`Validation <cookbook/validation-of-entities>` \|
|
||||
:doc:`Entities in the Session <cookbook/entities-in-session>` \|
|
||||
:doc:`Keeping your Modules independent <cookbook/resolve-target-entity-listener>`
|
||||
|
||||
* **Hidden Gems**
|
||||
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
|
||||
|
||||
* **Custom Datatypes**
|
||||
:doc:`MySQL Enums <cookbook/mysql-enums>`
|
||||
:doc:`Custom Mapping Types <cookbook/custom-mapping-types>`
|
||||
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
|
||||
The best way to get started is with the :doc:`Getting Started with Doctrine <tutorials/getting-started>` tutorial.
|
||||
Use the sidebar to browse other tutorials and documentation for the Doctrine PHP ORM.
|
||||
|
||||
@@ -9,61 +9,50 @@ steps of configuration.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
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;
|
||||
use Doctrine\ORM\Configuration;
|
||||
use Doctrine\Common\Proxy\ProxyFactory;
|
||||
|
||||
// ...
|
||||
|
||||
if ($applicationMode == "development") {
|
||||
$queryCache = new ArrayAdapter();
|
||||
$metadataCache = new ArrayAdapter();
|
||||
$cache = new \Doctrine\Common\Cache\ArrayCache;
|
||||
} else {
|
||||
$queryCache = new PhpFilesAdapter('doctrine_queries');
|
||||
$metadataCache = new PhpFilesAdapter('doctrine_metadata');
|
||||
$cache = new \Doctrine\Common\Cache\ApcuCache;
|
||||
}
|
||||
|
||||
$config = new Configuration;
|
||||
$config->setMetadataCache($metadataCache);
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
$config->setQueryCache($queryCache);
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies');
|
||||
$config->setProxyNamespace('MyProject\Proxies');
|
||||
$config->setAutoGenerateProxyClasses($applicationMode === 'development')
|
||||
|
||||
if ($applicationMode == "development") {
|
||||
$config->setAutoGenerateProxyClasses(true);
|
||||
} else {
|
||||
$config->setAutoGenerateProxyClasses(false);
|
||||
if ('development' === $applicationMode) {
|
||||
$config->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
|
||||
}
|
||||
|
||||
$connection = DriverManager::getConnection([
|
||||
$connectionOptions = [
|
||||
'driver' => 'pdo_sqlite',
|
||||
'path' => 'database.sqlite',
|
||||
], $config);
|
||||
'path' => 'database.sqlite'
|
||||
];
|
||||
|
||||
$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.
|
||||
$em = EntityManager::create($connectionOptions, $config);
|
||||
|
||||
.. 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 `APCu <https://php.net/apcu>`_. APCu provides you with
|
||||
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
|
||||
---------------------
|
||||
@@ -71,35 +60,37 @@ Configuration Options
|
||||
The following sections describe all the configuration options
|
||||
available on a ``Doctrine\ORM\Configuration`` instance.
|
||||
|
||||
Proxy Directory (**REQUIRED**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Proxy Directory
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setProxyDir($dir);
|
||||
$config->getProxyDir();
|
||||
|
||||
Gets or sets the directory where Doctrine generates any proxy
|
||||
Sets the directory where Doctrine generates any proxy
|
||||
classes. For a detailed explanation on proxy classes and how they
|
||||
are used in Doctrine, refer to the "Proxy Objects" section further
|
||||
down.
|
||||
|
||||
Proxy Namespace (**REQUIRED**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Setting the proxy target directory will also implicitly cause a
|
||||
call to ``Doctrine\ORM\Configuration#setAutoGenerateProxyClasses()``
|
||||
with a value of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``.
|
||||
|
||||
Proxy Namespace
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setProxyNamespace($namespace);
|
||||
$config->getProxyNamespace();
|
||||
|
||||
Gets or sets the namespace to use for generated proxy classes. For
|
||||
Sets the namespace to use for generated proxy classes. For
|
||||
a detailed explanation on proxy classes and how they are used in
|
||||
Doctrine, refer to the "Proxy Objects" section further down.
|
||||
|
||||
Metadata Driver (**REQUIRED**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Metadata Driver (***REQUIRED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -111,69 +102,69 @@ Gets or sets the metadata driver implementation that is used by
|
||||
Doctrine to acquire the object-relational metadata for your
|
||||
classes.
|
||||
|
||||
There are currently 5 available implementations:
|
||||
There are currently 4 available implementations:
|
||||
|
||||
|
||||
- ``Doctrine\ORM\Mapping\Driver\AttributeDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver``
|
||||
- ``Doctrine\ORM\Mapping\Driver\XmlDriver``
|
||||
- ``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 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``.
|
||||
Throughout the most part of this manual the AnnotationDriver is
|
||||
used in the examples. For information on the usage of the XmlDriver
|
||||
please refer to the dedicated chapters ``XML Mapping``.
|
||||
|
||||
The attribute driver can be injected in the ``Doctrine\ORM\Configuration``:
|
||||
The annotation driver can be configured with a factory method on
|
||||
the ``Doctrine\ORM\Configuration``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
|
||||
|
||||
$driverImpl = new AttributeDriver(['/path/to/lib/MyProject/Entities'], true);
|
||||
$driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities');
|
||||
$config->setMetadataDriverImpl($driverImpl);
|
||||
|
||||
The path information to the entities is required for the attribute
|
||||
The path information to the entities is required for the annotation
|
||||
driver, because otherwise mass-operations on all entities through
|
||||
the console could not work correctly. All of metadata drivers
|
||||
accept either a single directory as a string or an array of
|
||||
directories. With this feature a single driver can support multiple
|
||||
directories of Entities.
|
||||
|
||||
Metadata Cache (**RECOMMENDED**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Metadata Cache (***RECOMMENDED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataCache($cache);
|
||||
$config->getMetadataCache();
|
||||
$config->setMetadataCacheImpl($cache);
|
||||
$config->getMetadataCacheImpl();
|
||||
|
||||
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
|
||||
Gets or sets the cache implementation to use for caching metadata
|
||||
information, that is, all the information you supply via
|
||||
annotations or xml, so that they do not need to be parsed and
|
||||
loaded from scratch on every single request which is a waste of
|
||||
resources. The cache implementation must implement the PSR-6
|
||||
``Psr\Cache\CacheItemPoolInterface`` interface.
|
||||
resources. The cache implementation must implement the
|
||||
``Doctrine\Common\Cache\Cache`` interface.
|
||||
|
||||
Usage of a metadata cache is highly recommended.
|
||||
|
||||
For development you should use an array cache like
|
||||
``Symfony\Component\Cache\Adapter\ArrayAdapter``
|
||||
which only caches data on a per-request basis.
|
||||
The recommended implementations for production are:
|
||||
|
||||
Query Cache (**RECOMMENDED**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- ``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.
|
||||
|
||||
Query Cache (***RECOMMENDED***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setQueryCache($cache);
|
||||
$config->getQueryCache();
|
||||
$config->setQueryCacheImpl($cache);
|
||||
$config->getQueryCacheImpl();
|
||||
|
||||
Gets or sets the cache implementation to use for caching DQL
|
||||
queries, that is, the result of a DQL parsing process that includes
|
||||
@@ -185,12 +176,19 @@ minimal memory usage in your cache).
|
||||
|
||||
Usage of a query cache is highly recommended.
|
||||
|
||||
For development you should use an array cache like
|
||||
``Symfony\Component\Cache\Adapter\ArrayAdapter``
|
||||
which only caches data on a per-request basis.
|
||||
The recommended implementations for production are:
|
||||
|
||||
SQL Logger (**Optional**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
- ``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.
|
||||
|
||||
SQL Logger (***Optional***)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -200,10 +198,13 @@ SQL Logger (**Optional**)
|
||||
|
||||
Gets or sets the logger to use for logging all SQL statements
|
||||
executed by Doctrine. The logger class must implement the
|
||||
deprecated ``Doctrine\DBAL\Logging\SQLLogger`` interface.
|
||||
``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``.
|
||||
|
||||
Auto-generating Proxy Classes (**OPTIONAL**)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Auto-generating Proxy Classes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Proxy classes can either be generated manually through the Doctrine
|
||||
Console or automatically at runtime by Doctrine. The configuration
|
||||
@@ -216,45 +217,76 @@ option that controls this behavior is:
|
||||
|
||||
Possible values for ``$mode`` are:
|
||||
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
|
||||
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
|
||||
|
||||
Never autogenerate a proxy. You will need to generate the proxies
|
||||
manually, for this use the Doctrine Console like so:
|
||||
Generate the proxy class when the proxy file does not exist.
|
||||
This strategy can potentially cause disk access.
|
||||
Note that autoloading will be attempted before falling back
|
||||
to generating a proxy class: if an already existing proxy class
|
||||
is found, then no file write operations will be performed.
|
||||
|
||||
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
|
||||
|
||||
Generate the proxy classes and evaluate them on the fly via ``eval()``,
|
||||
avoiding writing the proxies to disk.
|
||||
This strategy is only sane for development and long running
|
||||
processes.
|
||||
|
||||
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_NEVER``
|
||||
|
||||
This flag is deprecated, and is an alias
|
||||
of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
|
||||
|
||||
- ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
|
||||
|
||||
This flag is deprecated, and is an alias
|
||||
of ``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
|
||||
|
||||
Before v2.4, ``setAutoGenerateProxyClasses`` would accept a boolean
|
||||
value. This is still possible, ``FALSE`` being equivalent to
|
||||
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
|
||||
|
||||
Manually generating Proxy Classes for performance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While the ORM can generate proxy classes when required, it is suggested
|
||||
to not let this happen for production environments, as it has a major
|
||||
impact on your application's performance.
|
||||
|
||||
In a production environment, it is highly recommended to use
|
||||
``Doctrine\Common\Proxy\ProxyFactory::AUTOGENERATE_FILE_NOT_EXISTS``
|
||||
in combination with a well-configured
|
||||
`composer class autoloader<https://getcomposer.org/doc/01-basic-usage.md#autoloading>`_.
|
||||
|
||||
Here is an example of such setup:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MyProject\\": "path/to/project/sources/",
|
||||
"GeneratedProxies\\": "path/to/generated/proxies/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
You would then configure the ORM to use the ``"GeneratedProxies"``
|
||||
and the ``"path/to/generated/proxies/"`` for the proxy classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine orm:generate-proxies
|
||||
<?php
|
||||
$config->setProxyDir('path/to/generated/proxies/');
|
||||
$config->setProxyNamespace('GeneratedProxies');
|
||||
|
||||
When you do this in a development environment,
|
||||
be aware that you may get class/file not found errors if certain proxies
|
||||
are not yet generated. You may also get failing lazy-loads if new
|
||||
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.
|
||||
To make sure proxies are never generated by Doctrine, you'd forcefully
|
||||
generate them during deployment operations:
|
||||
|
||||
- ``Doctrine\ORM\Proxy\ProxyFactory::AUTOGENERATE_ALWAYS``
|
||||
.. code-block:: sh
|
||||
|
||||
Always generates a new proxy in every request and writes it to disk.
|
||||
|
||||
- ``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\ORM\Proxy\ProxyFactory::AUTOGENERATE_EVAL``
|
||||
|
||||
Generate the proxy classes and evaluate them on the fly via eval(),
|
||||
avoiding writing the proxies to disk.
|
||||
This strategy is only sane for development.
|
||||
|
||||
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.
|
||||
|
||||
``setAutoGenerateProxyClasses`` can accept a boolean
|
||||
value. This is still possible, ``FALSE`` being equivalent to
|
||||
AUTOGENERATE_NEVER and ``TRUE`` to AUTOGENERATE_ALWAYS.
|
||||
$ ./vendor/bin/doctrine orm:generate-proxies
|
||||
$ composer dump-autoload
|
||||
|
||||
Development vs Production Configuration
|
||||
---------------------------------------
|
||||
@@ -264,25 +296,20 @@ runtime models in mind. There are some serious benefits of using
|
||||
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 an array cache for development.
|
||||
recommend the ``ArrayCache`` for development.
|
||||
|
||||
Furthermore you should have the Auto-generating Proxy Classes
|
||||
option to true in development and to false in production. If this
|
||||
option is set to ``TRUE`` it can seriously hurt your script
|
||||
performance if several proxy classes are re-generated during script
|
||||
execution. Filesystem calls of that magnitude can even slower than
|
||||
all the database queries Doctrine issues. Additionally writing a
|
||||
proxy sets an exclusive file lock which can cause serious
|
||||
performance bottlenecks in systems with regular concurrent
|
||||
requests.
|
||||
Furthermore you should disable the Auto-generating Proxy Classes
|
||||
option in production.
|
||||
|
||||
Connection
|
||||
----------
|
||||
Connection Options
|
||||
------------------
|
||||
|
||||
The ``$connection`` passed as the first argument to the constructor of
|
||||
``EntityManager`` has to be an instance of ``Doctrine\DBAL\Connection``.
|
||||
You can use the factory ``Doctrine\DBAL\DriverManager::getConnection()``
|
||||
to create such a connection. The DBAL configuration is explained in the
|
||||
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
|
||||
`DBAL section <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/configuration.html>`_.
|
||||
|
||||
Proxy Objects
|
||||
@@ -290,7 +317,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 ORM,
|
||||
being proxied without that object being aware of it. In Doctrine 2,
|
||||
proxy objects are used to realize several features but mainly for
|
||||
transparent lazy-loading.
|
||||
|
||||
@@ -300,7 +327,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 ORM implements a variant of the proxy pattern where it
|
||||
Doctrine 2 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
|
||||
@@ -311,47 +338,27 @@ Reference Proxies
|
||||
|
||||
The method ``EntityManager#getReference($entityName, $identifier)``
|
||||
lets you obtain a reference to an entity for which the identifier
|
||||
is known, without necessarily loading that entity from the database.
|
||||
This is useful, for example, as a performance enhancement, when you
|
||||
want to establish an association to an entity for which you have the
|
||||
identifier.
|
||||
|
||||
Consider the following example:
|
||||
is known, without loading that entity from the database. This is
|
||||
useful, for example, as a performance enhancement, when you want to
|
||||
establish an association to an entity for which you have the
|
||||
identifier. You could simply do this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
|
||||
// $itemId comes from somewhere, probably a request parameter
|
||||
$item = $em->getReference('MyProject\Model\Item', $itemId);
|
||||
$item = $em->getReference(\MyProject\Model\Item::class, $itemId);
|
||||
$cart->addItem($item);
|
||||
|
||||
Whether the object being returned from ``EntityManager#getReference()``
|
||||
is a proxy or a direct instance of the entity class may depend on different
|
||||
factors, including whether the entity has already been loaded into memory
|
||||
or entity inheritance being used. But your code does not need to care
|
||||
and in fact it **should not care**. Proxy objects should be transparent to your
|
||||
code.
|
||||
|
||||
When using the ``EntityManager#getReference()`` method, you need to be aware
|
||||
of a few peculiarities.
|
||||
|
||||
At the best case, the ORM can avoid querying the database at all. But, that
|
||||
also means that this method will not throw an exception when an invalid value
|
||||
for the ``$identifier`` parameter is passed. ``$identifier`` values are
|
||||
not checked and there is no guarantee that the requested entity instance even
|
||||
exists – the method will still return a proxy object.
|
||||
|
||||
Its only when the proxy has to be fully initialized or associations cannot
|
||||
be written to the database that invalid ``$identifier`` values may lead to
|
||||
exceptions.
|
||||
|
||||
The ``EntityManager#getReference()`` is mostly useful when you only
|
||||
need a reference to some entity to make an association, like in the example
|
||||
above. In that case, it can save you from loading data from the database
|
||||
that you don't need. But remember – as soon as you read any property values
|
||||
besides those making up the ID, a database request will be made to initialize
|
||||
all fields.
|
||||
Here, we added an ``Item`` to a ``Cart`` without loading the Item from the
|
||||
database.
|
||||
If you access any persistent 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 code.
|
||||
|
||||
Association proxies
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -359,7 +366,7 @@ Association proxies
|
||||
The second most important situation where Doctrine uses proxy
|
||||
objects is when querying for objects. Whenever you query for an
|
||||
object that has a single-valued association to another object that
|
||||
is configured LAZY, without joining that association in the same
|
||||
is configured ``LAZY``, without joining that association in the same
|
||||
query, Doctrine puts proxy objects in place where normally the
|
||||
associated object would be. Just like other proxies it will
|
||||
transparently initialize itself on first access.
|
||||
@@ -371,71 +378,22 @@ transparently initialize itself on first access.
|
||||
This will override the 'fetch' option specified in the mapping for
|
||||
that association, but only for that query.
|
||||
|
||||
|
||||
Generating Proxy classes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In a production environment, it is highly recommended to use
|
||||
``AUTOGENERATE_NEVER`` to allow for optimal performances.
|
||||
However you will be required to generate the proxies manually
|
||||
using the Doctrine Console:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ ./doctrine orm:generate-proxies
|
||||
|
||||
The other options are interesting in development environment:
|
||||
|
||||
- ``AUTOGENERATE_ALWAYS`` will require you to create and configure
|
||||
a proxy directory. Proxies will be generated and written to file
|
||||
on each request, so any modification to your code will be acknowledged.
|
||||
|
||||
- ``AUTOGENERATE_FILE_NOT_EXISTS`` will not overwrite an existing
|
||||
proxy file. If your code changes, you will need to regenerate the
|
||||
proxies manually.
|
||||
|
||||
- ``AUTOGENERATE_EVAL`` will regenerate each proxy on each request,
|
||||
but without writing them to disk.
|
||||
|
||||
Autoloading Proxies
|
||||
-------------------
|
||||
|
||||
When you deserialize proxy objects from the session or any other storage
|
||||
it is necessary to have an autoloading mechanism in place for these classes.
|
||||
For implementation reasons Proxy class names are not PSR-0 compliant. This
|
||||
means that you have to register a special autoloader for these classes:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Proxy\Autoloader;
|
||||
|
||||
$proxyDir = "/path/to/proxies";
|
||||
$proxyNamespace = "MyProxies";
|
||||
|
||||
Autoloader::register($proxyDir, $proxyNamespace);
|
||||
|
||||
If you want to execute additional logic to intercept the proxy file not found
|
||||
state you can pass a closure as the third argument. It will be called with
|
||||
the arguments proxydir, namespace and className when the proxy file could not
|
||||
be found.
|
||||
|
||||
Multiple Metadata Sources
|
||||
-------------------------
|
||||
|
||||
When using different components using Doctrine ORM you may end up
|
||||
When using different components using Doctrine 2 you may end up
|
||||
with them using two different metadata drivers, for example XML and
|
||||
YAML. You can use the MappingDriverChain Metadata implementations to
|
||||
annotationsL. You can use the DriverChain Metadata implementations to
|
||||
aggregate these drivers based on namespaces:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
|
||||
use Doctrine\ORM\Mapping\Driver\DriverChain;
|
||||
|
||||
$chain = new MappingDriverChain();
|
||||
$chain = new DriverChain();
|
||||
$chain->addDriver($xmlDriver, 'Doctrine\Tests\Models\Company');
|
||||
$chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping');
|
||||
$chain->addDriver($annotationDriver, 'Doctrine\Tests\ORM\Mapping');
|
||||
|
||||
Based on the namespace of the entity the loading of entities is
|
||||
delegated to the appropriate driver. The chain semantics come from
|
||||
@@ -445,8 +403,7 @@ the entity class name against the namespace using a
|
||||
correctly if sub-namespaces use different metadata driver
|
||||
implementations.
|
||||
|
||||
|
||||
Default Repository (**OPTIONAL**)
|
||||
Default Repository (***OPTIONAL***)
|
||||
-----------------------------------
|
||||
|
||||
Specifies the FQCN of a subclass of the EntityRepository.
|
||||
@@ -461,22 +418,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
|
||||
|
||||
Ignoring entities (**OPTIONAL**)
|
||||
-----------------------------------
|
||||
|
||||
Specifies the Entity FQCNs to ignore.
|
||||
SchemaTool will then skip these (e.g. when comparing schemas).
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$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.
|
||||
line interface. You can take a look at the ``vendor/bin/doctrine.php``
|
||||
script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command
|
||||
for inspiration how to setup the cli.
|
||||
|
||||
In general the required code looks like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
|
||||
$cli->setCatchExceptions(true);
|
||||
$cli->setHelperSet($helperSet);
|
||||
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
|
||||
$cli->run();
|
||||
|
||||
|
||||
@@ -1,46 +1,33 @@
|
||||
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 ORM generalizes the concept
|
||||
then be processed by some tool. Doctrine 2 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 ORM
|
||||
chances of clashes with other docblock annotations, the Doctrine 2
|
||||
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/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.
|
||||
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.
|
||||
|
||||
.. note::
|
||||
|
||||
If you're not comfortable with the concept of docblock
|
||||
annotations, don't worry, as mentioned earlier Doctrine ORM provides
|
||||
XML and YAML alternatives and you could easily implement your own
|
||||
annotations, don't worry, as mentioned earlier Doctrine 2 provides
|
||||
the XML alternative and you could easily implement your own
|
||||
favourite mechanism for defining ORM metadata.
|
||||
|
||||
In this chapter a reference of every Doctrine ORM Annotation is given
|
||||
In this chapter a reference of every Doctrine 2 Annotation is given
|
||||
with short explanations on their context and usage.
|
||||
|
||||
Index
|
||||
@@ -102,7 +89,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. Default to ``string`` or :ref:`Type from PHP property type <reference-php-mapping-types>`
|
||||
and Database representation.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
@@ -112,7 +99,7 @@ Optional attributes:
|
||||
|
||||
- **length**: Used by the "string" type to determine its maximum
|
||||
length in the database. Doctrine does not validate the length of a
|
||||
string value for you.
|
||||
string values for you.
|
||||
|
||||
- **precision**: The precision for a decimal (exact numeric) column
|
||||
(applies only for decimal column), which is the maximum number of
|
||||
@@ -128,18 +115,6 @@ 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
|
||||
@@ -210,13 +185,6 @@ 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
|
||||
@@ -245,7 +213,7 @@ Optional attributes:
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Change Tracking Policy annotation allows to specify how the
|
||||
Doctrine ORM UnitOfWork should detect changes in properties of
|
||||
Doctrine 2 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
|
||||
@@ -308,13 +276,11 @@ to a string column of length 255 called ``dtype``.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **name**: The column name of the discriminator. This name is also
|
||||
used during Array hydration as key to specify the class-name.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **type**: By default this is string.
|
||||
- **length**: By default this is 255.
|
||||
|
||||
@@ -344,7 +310,6 @@ depending on whether the classes are in the namespace or not.
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
.. _annref_embeddable:
|
||||
|
||||
@Embeddable
|
||||
@@ -371,7 +336,6 @@ annotation to establish the relationship between the two classes.
|
||||
*/
|
||||
private $address;
|
||||
|
||||
|
||||
.. _annref_embedded:
|
||||
|
||||
@Embedded
|
||||
@@ -382,8 +346,7 @@ in order to specify that it is an embedded class.
|
||||
|
||||
Required attributes:
|
||||
|
||||
- **class**: The embeddable class. You can omit this value if you use a PHP property type instead.
|
||||
|
||||
- **class**: The embeddable class
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -404,7 +367,6 @@ Required attributes:
|
||||
{
|
||||
// ...
|
||||
|
||||
|
||||
.. _annref_entity:
|
||||
|
||||
@Entity
|
||||
@@ -415,12 +377,11 @@ the persistence of all classes marked as entities.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **repositoryClass**: Specifies the FQCN of a subclass of the
|
||||
EntityRepository. Use of repositories for entities is encouraged to keep
|
||||
specialized DQL and SQL operations separated from the Model/Domain
|
||||
Layer.
|
||||
- **readOnly**: Specifies that this entity is marked as read only and not
|
||||
- **readOnly**: (>= 2.1) 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.
|
||||
|
||||
@@ -465,7 +426,6 @@ Required attributes:
|
||||
|
||||
- **name**: Name of the persistent field or property of the class.
|
||||
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- **column**: Name of the column in the SELECT clause.
|
||||
@@ -485,9 +445,8 @@ used as default.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **strategy**: Set the name of the identifier generation strategy.
|
||||
Valid values are ``AUTO``, ``SEQUENCE``, ``IDENTITY``, ``UUID`` (deprecated), ``CUSTOM`` and ``NONE``, explained
|
||||
Valid values are ``AUTO``, ``SEQUENCE``, ``TABLE``, ``IDENTITY``, ``CUSTOM`` and ``NONE``, explained
|
||||
in the :ref:`Identifier Generation Strategies <identifier-generation-strategies>` section.
|
||||
If not specified, default value is AUTO.
|
||||
|
||||
@@ -544,13 +503,11 @@ has meaning in the SchemaTool schema generation context.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
|
||||
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
|
||||
- **name**: Name of the Index
|
||||
- **columns**: Array of columns.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- **name**: Name of the Index. If not provided, a generated name will be assigned.
|
||||
- **options**: Array of platform specific options:
|
||||
|
||||
- ``where``: SQL WHERE condition to be used for partial indexes. It will
|
||||
@@ -569,19 +526,6 @@ 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
|
||||
@@ -666,17 +610,20 @@ Examples:
|
||||
This annotation is used in the context of relations in
|
||||
:ref:`@ManyToOne <annref_manytoone>`, :ref:`@OneToOne <annref_onetoone>` fields
|
||||
and in the Context of :ref:`@JoinTable <annref_jointable>` nested inside
|
||||
a @ManyToMany. If this annotation or both *name* and *referencedColumnName*
|
||||
are missing they will be computed considering the field's name and the current
|
||||
:doc:`naming strategy <namingstrategy>`.
|
||||
a @ManyToMany. This annotation is not required. If it is not
|
||||
specified the attributes *name* and *referencedColumnName* are
|
||||
inferred from the table and primary key names.
|
||||
|
||||
Optional attributes:
|
||||
Required attributes:
|
||||
|
||||
- **name**: Column name that holds the foreign key identifier for
|
||||
this relation. In the context of @JoinTable it specifies the column
|
||||
name in the join table.
|
||||
- **referencedColumnName**: Name of the primary key identifier that
|
||||
is used for joining of this relation. Defaults to *id*.
|
||||
is used for joining of this relation.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- **unique**: Determines whether this relation is exclusive between the
|
||||
affected entities and should be enforced as such on the database
|
||||
constraint level. Defaults to false.
|
||||
@@ -727,7 +674,6 @@ using the affected table and the column names.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **name**: Database name of the join-table
|
||||
- **joinColumns**: An array of @JoinColumn annotations describing the
|
||||
join-relation between the owning entities table and the join table.
|
||||
@@ -759,15 +705,12 @@ describes a many-to-one relationship between two entities.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
- **cascade**: Cascade Option
|
||||
- **fetch**: One of LAZY or EAGER
|
||||
- inversedBy - The inversedBy attribute designates the field in
|
||||
@@ -796,14 +739,12 @@ entities.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **targetEntity**: FQCN of the referenced target entity. Can be the
|
||||
unqualified class name if both classes are in the same namespace.
|
||||
*IMPORTANT:* No leading backslash!
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **mappedBy**: This option specifies the property name on the
|
||||
targetEntity that is the owning side of this relation. It is a
|
||||
required attribute for the inverse side of a relationship.
|
||||
@@ -859,8 +800,7 @@ The @MappedSuperclass annotation cannot be used in conjunction with
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **repositoryClass**: Specifies the FQCN of a subclass of the EntityRepository.
|
||||
- **repositoryClass**: (>= 2.2) Specifies the FQCN of a subclass of the EntityRepository.
|
||||
That will be inherited for all subclasses of that Mapped Superclass.
|
||||
|
||||
Example:
|
||||
@@ -888,11 +828,6 @@ 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.
|
||||
|
||||
@@ -901,13 +836,11 @@ Required attributes:
|
||||
- **name**: The name used to refer to the query with the EntityManager methods that create query objects.
|
||||
- **query**: The SQL query string.
|
||||
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- **resultClass**: The class of the result.
|
||||
- **resultSetMapping**: The name of a SqlResultSetMapping, as defined in metadata.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -973,15 +906,12 @@ primary key column names apply here too.
|
||||
|
||||
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:
|
||||
|
||||
|
||||
- **cascade**: Cascade Option
|
||||
- **fetch**: One of LAZY or EAGER
|
||||
- **orphanRemoval**: Boolean that specifies if orphans, inverse
|
||||
@@ -1008,14 +938,12 @@ Example:
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **targetEntity**: FQCN of the referenced target entity. Can be the
|
||||
unqualified class name if both classes are in the same namespace.
|
||||
*IMPORTANT:* No leading backslash!
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **cascade**: Cascade Option
|
||||
- **orphanRemoval**: Boolean that specifies if orphans, inverse
|
||||
OneToOne entities that are not connected to any owning instance,
|
||||
@@ -1032,7 +960,7 @@ Example:
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove", "merge"}, orphanRemoval=true)
|
||||
* @OneToMany(targetEntity="Phonenumber", mappedBy="user", cascade={"persist", "remove"}, orphanRemoval=true)
|
||||
*/
|
||||
public $phonenumbers;
|
||||
|
||||
@@ -1140,12 +1068,10 @@ the increment size and initial values of the sequence.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **sequenceName**: Name of the sequence
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **allocationSize**: Increment the sequence by the allocation size
|
||||
when its fetched. A value larger than 1 allows optimization for
|
||||
scenarios where you create more than one new entity per request.
|
||||
@@ -1176,7 +1102,6 @@ Required attributes:
|
||||
|
||||
- **name**: The name given to the result set mapping, and used to refer to it in the methods of the Query API.
|
||||
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- **entities**: Array of @EntityResult, Specifies the result set mapping to entities.
|
||||
@@ -1277,15 +1202,13 @@ unqualified classname.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **name**: Name of the table
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **indexes**: Array of @Index annotations
|
||||
- **uniqueConstraints**: Array of @UniqueConstraint annotations.
|
||||
- **schema**: Name of the schema the table lies in.
|
||||
- **schema**: (>= 2.5) Name of the schema the table lies in.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1315,13 +1238,11 @@ context.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- **fields**: Array of fields. Exactly one of **fields**, **columns** is required.
|
||||
- **columns**: Array of columns. Exactly one of **fields**, **columns** is required.
|
||||
- **name**: Name of the Index
|
||||
- **columns**: Array of columns.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
- **name**: Name of the Index. If not provided, a generated name will be assigned.
|
||||
- **options**: Array of platform specific options:
|
||||
|
||||
- ``where``: SQL WHERE condition to be used for partial indexes. It will
|
||||
@@ -1340,19 +1261,6 @@ 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
|
||||
@@ -1387,3 +1295,4 @@ Example:
|
||||
* @Version
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
|
||||
@@ -2,56 +2,49 @@ Architecture
|
||||
============
|
||||
|
||||
This chapter gives an overview of the overall architecture,
|
||||
terminology and constraints of Doctrine ORM. It is recommended to
|
||||
terminology and constraints of Doctrine 2. It is recommended to
|
||||
read this chapter carefully.
|
||||
|
||||
Using an Object-Relational Mapper
|
||||
---------------------------------
|
||||
|
||||
As the term ORM already hints at, Doctrine ORM aims to simplify the
|
||||
As the term ORM already hints at, Doctrine 2 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 ORM is not suited very
|
||||
that do not primarily work with objects Doctrine 2 is not suited very
|
||||
well.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Doctrine ORM requires a minimum of PHP 7.1. For greatly improved
|
||||
Doctrine 2 requires a minimum of PHP 7.1. For greatly improved
|
||||
performance it is also recommended that you use APC with PHP.
|
||||
|
||||
Doctrine ORM Packages
|
||||
Doctrine 2 Packages
|
||||
-------------------
|
||||
|
||||
Doctrine ORM is divided into four main packages.
|
||||
Doctrine 2 is divided into three main packages.
|
||||
|
||||
- `Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html>`_
|
||||
- `Event Manager <https://www.doctrine-project.org/projects/doctrine-event-manager/en/stable/index.html>`_
|
||||
- `Persistence <https://www.doctrine-project.org/projects/doctrine-persistence/en/stable/index.html>`_
|
||||
- `DBAL <https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/index.html>`_
|
||||
- ORM (depends on DBAL+Persistence+Collections)
|
||||
- Common
|
||||
- DBAL (includes Common)
|
||||
- ORM (includes DBAL+Common)
|
||||
|
||||
This manual mainly covers the ORM package, sometimes touching parts
|
||||
of the underlying DBAL and Persistence packages. The Doctrine code base
|
||||
of the underlying DBAL and Common packages. The Doctrine code base
|
||||
is split in to these packages for a few reasons and they are to...
|
||||
|
||||
|
||||
- ...make things more maintainable and decoupled
|
||||
- ...allow you to use the code in Doctrine Persistence and Collections
|
||||
without the ORM or DBAL
|
||||
- ...allow you to use the code in Doctrine Common without the ORM
|
||||
or DBAL
|
||||
- ...allow you to use the DBAL without the ORM
|
||||
|
||||
Collection, Event Manager and Persistence
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The Common Package
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Collection, Event Manager and Persistence packages contain highly
|
||||
reusable components that have no dependencies beyond the packages
|
||||
themselves (and PHP, of course). The root namespace of the Persistence
|
||||
package is ``Doctrine\Persistence``. The root namespace of the
|
||||
Collection package is ``Doctrine\Common\Collections``, for historical
|
||||
reasons. The root namespace of the Event Manager package is just
|
||||
``Doctrine\Common``, also for historical reasons.
|
||||
The Common package contains highly reusable components that have no
|
||||
dependencies beyond the package itself (and PHP, of course). The
|
||||
root namespace of the Common package is ``Doctrine\Common``.
|
||||
|
||||
The DBAL Package
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -72,16 +65,17 @@ 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 nor read-only but
|
||||
it may contain final methods or read-only properties.
|
||||
- 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).
|
||||
- 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
|
||||
@@ -100,33 +94,12 @@ classes, and non-entity classes may extend entity classes.
|
||||
never calls entity constructors, thus you are free to use them as
|
||||
you wish and even have it require arguments of any type.
|
||||
|
||||
Mapped Superclasses
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A mapped superclass is an abstract or concrete class that provides
|
||||
persistent entity state and mapping information for its subclasses,
|
||||
but which is not itself an entity.
|
||||
|
||||
Mapped superclasses are explained in greater detail in the chapter
|
||||
on :doc:`inheritance mapping </reference/inheritance-mapping>`.
|
||||
|
||||
Transient Classes
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The term "transient class" appears in some places in the mapping
|
||||
drivers as well as the code dealing with metadata handling.
|
||||
|
||||
A transient class is a class that is neither an entity nor a mapped
|
||||
superclass. From the ORM's point of view, these classes can be
|
||||
completely ignored, and no class metadata is loaded for them at all.
|
||||
|
||||
Entity states
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
An entity instance can be characterized as being NEW, MANAGED,
|
||||
DETACHED or REMOVED.
|
||||
|
||||
|
||||
- A NEW entity instance has no persistent identity, and is not yet
|
||||
associated with an EntityManager and a UnitOfWork (i.e. those just
|
||||
created with the "new" operator).
|
||||
@@ -163,21 +136,24 @@ subsequent access must be through the interface type.
|
||||
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.
|
||||
By default, serializing proxy objects does not initialize them. On
|
||||
unserialization, resulting objects are detached from the entity
|
||||
manager and cannot be initialized anymore. You can implement the
|
||||
``__serialize()`` method if you want to change that behavior, but
|
||||
then you need to ensure that you won't generate large serialized
|
||||
object graphs and take care of circular associations.
|
||||
Serializing entities is generally to be avoided.
|
||||
|
||||
If you intend to serialize (and unserialize) entity
|
||||
instances that still hold references to proxy objects you may run
|
||||
into problems, because all proxy properties will be initialized
|
||||
recursively, leading to large serialized object graphs, especially
|
||||
for circular associations.
|
||||
|
||||
If you really must serialize entities, regardless if proxies are
|
||||
involved or not, then consider implementing the ``Serializable``
|
||||
interface and manually checking for cyclic dependencies in your
|
||||
object graph.
|
||||
|
||||
The EntityManager
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``EntityManager`` class is a central access point to the
|
||||
functionality provided by Doctrine ORM. The ``EntityManager`` API is
|
||||
The ``EntityManager`` class is a central access point to the ORM
|
||||
functionality provided by Doctrine 2. The ``EntityManager`` API is
|
||||
used to manage the persistence of your objects and to query for
|
||||
persistent objects.
|
||||
|
||||
@@ -194,8 +170,6 @@ 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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -205,3 +179,4 @@ typical implementation of the
|
||||
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.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,11 +14,17 @@ 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
|
||||
@@ -44,21 +50,18 @@ that describes your entity.
|
||||
Doctrine provides several different ways to specify object-relational
|
||||
mapping metadata:
|
||||
|
||||
- :doc:`Attributes <attributes-reference>`
|
||||
- :doc:`Docblock Annotations <annotations-reference>`
|
||||
- :doc:`XML <xml-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 attributes, though
|
||||
many examples also show the equivalent configuration in annotations,
|
||||
YAML and XML.
|
||||
This manual will usually show mapping metadata via docblock annotations, though
|
||||
many examples also show the equivalent configuration in XML.
|
||||
|
||||
.. note::
|
||||
|
||||
All metadata drivers perform equally. Once the metadata of a class has been
|
||||
read from the source (attributes, annotations, XML, etc.) it is stored in an instance
|
||||
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class which are
|
||||
read from the source (annotations or xml) it is stored in an instance
|
||||
of the ``Doctrine\ORM\Mapping\ClassMetadata`` class and these instances are
|
||||
stored in the metadata cache. If you're not using a metadata cache (not
|
||||
recommended!) then the XML driver is the fastest.
|
||||
|
||||
@@ -66,22 +69,9 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
#[Entity]
|
||||
class Message
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
/** @Entity */
|
||||
class Message
|
||||
{
|
||||
@@ -96,37 +86,15 @@ Marking our ``Message`` class as an entity for Doctrine is straightforward:
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Message:
|
||||
type: entity
|
||||
# ...
|
||||
|
||||
With no additional information, Doctrine expects the entity to be saved
|
||||
into a table with the same name as the class in our case ``Message``.
|
||||
You can change this by configuring information about the table:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
.. code-block:: php
|
||||
|
||||
<?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")
|
||||
@@ -144,50 +112,24 @@ You can change this by configuring information about the table:
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Message:
|
||||
type: entity
|
||||
table: message
|
||||
# ...
|
||||
|
||||
Now the class ``Message`` will be saved and fetched from the table ``message``.
|
||||
|
||||
Property Mapping
|
||||
----------------
|
||||
|
||||
The next step is mapping its properties to columns in the table.
|
||||
The next step after marking a PHP class as an entity is mapping its properties
|
||||
to columns in a table.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
.. code-block:: php
|
||||
|
||||
<?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
|
||||
{
|
||||
@@ -209,273 +151,100 @@ specified, ``string`` is used as the default.
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Message:
|
||||
type: entity
|
||||
fields:
|
||||
id:
|
||||
type: integer
|
||||
text:
|
||||
length: 140
|
||||
postedAt:
|
||||
type: datetime
|
||||
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. So in this example:
|
||||
assumes the field name is also the column name. This means that:
|
||||
|
||||
* 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.
|
||||
|
||||
Here is a complete list of ``Column``s attributes (all optional):
|
||||
The Column annotation has some more attributes. Here is a complete
|
||||
list:
|
||||
|
||||
- ``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.
|
||||
- ``generated`` (default: ``null``): Whether the generated strategy should be ``'NEVER'``, ``'INSERT'`` and ``ALWAYS``.
|
||||
- ``enumType`` (requires PHP 8.1 and ``doctrine/orm`` 2.11): The PHP enum class name to convert the database value into. See :ref:`reference-enum-mapping`.
|
||||
- ``precision`` (default: 0): The precision for a decimal (exact numeric) column
|
||||
(applies only for decimal column),
|
||||
- ``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),
|
||||
which is the maximum number of digits that are stored for the values.
|
||||
- ``scale`` (default: 0): The scale for a decimal (exact
|
||||
- ``scale``: (optional, 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``: Allows to define a custom
|
||||
not be greater than *precision*.
|
||||
- ``columnDefinition``: (optional) Allows to define a custom
|
||||
DDL snippet that is used to create the column. Warning: This normally
|
||||
confuses the :doc:`SchemaTool <tools>` to always detect the column as changed.
|
||||
- ``options``: Key-value pairs of options that get passed
|
||||
confuses the SchemaTool to always detect the column as changed.
|
||||
- ``options``: (optional) Key-value pairs of options that get passed
|
||||
to the underlying database platform when generating DDL statements.
|
||||
|
||||
Specifying default values
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While it is possible to specify default values for properties in your
|
||||
PHP class, Doctrine also allows you to specify default values for
|
||||
database columns using the ``default`` key in the ``options`` array of
|
||||
the ``Column`` attribute.
|
||||
|
||||
.. configuration-block::
|
||||
.. literalinclude:: basic-mapping/DefaultValues.php
|
||||
:language: attribute
|
||||
|
||||
.. literalinclude:: basic-mapping/default-values.xml
|
||||
:language: xml
|
||||
|
||||
.. _reference-php-mapping-types:
|
||||
|
||||
PHP Types Mapping
|
||||
_________________
|
||||
|
||||
.. 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-enum-mapping:
|
||||
|
||||
Mapping PHP Enums
|
||||
-----------------
|
||||
|
||||
.. versionadded:: 2.11
|
||||
|
||||
Doctrine natively supports mapping PHP backed enums to database columns.
|
||||
A backed enum is a PHP enum that the same scalar type (``string`` or ``int``)
|
||||
assigned to each case. Doctrine stores the scalar value in the database and
|
||||
converts it back to the enum instance when hydrating the entity.
|
||||
|
||||
Using ``enumType`` provides three main benefits:
|
||||
|
||||
- **Automatic conversion**: Doctrine handles the conversion in both directions
|
||||
transparently. When loading an entity, scalar values from the database are
|
||||
converted into enum instances. When persisting, enum instances are reduced
|
||||
to their scalar ``->value`` before being sent to the database.
|
||||
- **Type-safety**: Entity properties contain enum instances directly. Your
|
||||
getters return ``Suit`` instead of ``string``, removing the need to call
|
||||
``Suit::from()`` manually.
|
||||
- **Validation**: When a database value does not match any enum case, Doctrine
|
||||
throws a ``MappingException`` during hydration instead of silently returning
|
||||
an invalid value.
|
||||
|
||||
This feature works with all database platforms supported by Doctrine (MySQL,
|
||||
PostgreSQL, SQLite, etc.) as it relies on standard column types (``string``,
|
||||
``integer``, ``json``, ``simple_array``) rather than any vendor-specific enum
|
||||
type.
|
||||
|
||||
.. note::
|
||||
|
||||
This is unrelated to the MySQL-specific ``ENUM`` column type covered in
|
||||
:doc:`the MySQL Enums cookbook entry </cookbook/mysql-enums>`.
|
||||
|
||||
Defining an Enum
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. literalinclude:: basic-mapping/Suit.php
|
||||
:language: php
|
||||
|
||||
Only backed enums (``string`` or ``int``) are supported. Unit enums (without
|
||||
a scalar value) cannot be mapped.
|
||||
|
||||
Single-Value Columns
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Use the ``enumType`` option on ``#[Column]`` to map a property to a backed enum.
|
||||
The underlying database column stores the enum's scalar value (``string`` or ``int``).
|
||||
|
||||
.. literalinclude:: basic-mapping/EnumMapping.php
|
||||
:language: php
|
||||
|
||||
When the PHP property is typed with the enum class, Doctrine automatically
|
||||
infers the appropriate column type (``string`` for string-backed enums,
|
||||
``integer`` for int-backed enums) and sets ``enumType``. You can also specify
|
||||
the column ``type`` explicitly.
|
||||
|
||||
Storing Collections of Enums
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can store multiple enum values in a single column by combining ``enumType``
|
||||
with a collection column type: ``json`` or ``simple_array``.
|
||||
|
||||
.. note::
|
||||
|
||||
Automatic type inference does not apply to collection columns. When the
|
||||
PHP property is typed as ``array``, Doctrine cannot detect the enum class.
|
||||
You must specify both ``type`` and ``enumType`` explicitly.
|
||||
|
||||
.. literalinclude:: basic-mapping/EnumCollectionMapping.php
|
||||
:language: php
|
||||
|
||||
With ``json``, the values are stored as a JSON array (e.g. ``["hearts","spades"]``).
|
||||
With ``simple_array``, the values are stored as a comma-separated string
|
||||
(e.g. ``hearts,spades``).
|
||||
|
||||
In both cases, Doctrine converts each element to and from the enum
|
||||
automatically during hydration and persistence.
|
||||
|
||||
.. tip::
|
||||
|
||||
Use ``json`` when enum values may contain commas, when you need to store
|
||||
int-backed enums (as it preserves value types), when the column also
|
||||
stores complex/nested data structures, or when you want to query individual
|
||||
values using database-native JSON operators (e.g. PostgreSQL ``jsonb``).
|
||||
Prefer ``simple_array`` for a compact, human-readable storage of
|
||||
string-backed enums whose values do not contain commas.
|
||||
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| Column type | Database storage | PHP type |
|
||||
+===================+=============================+===============================+
|
||||
| ``string`` | ``hearts`` | ``Suit`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| ``integer`` | ``1`` | ``Priority`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| ``json`` | ``["hearts","spades"]`` | ``array<Suit>`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
| ``simple_array`` | ``hearts,spades`` | ``array<Suit>`` |
|
||||
+-------------------+-----------------------------+-------------------------------+
|
||||
|
||||
Nullable Enums
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Enum columns can be nullable. When the database value is ``NULL``, Doctrine
|
||||
preserves it as ``null`` without triggering any validation error.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
#[ORM\Column(type: 'string', nullable: true, enumType: Suit::class)]
|
||||
private Suit|null $suit = null;
|
||||
|
||||
Default Values
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
You can specify a database-level default using an enum case directly in the
|
||||
column options:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
#[ORM\Column(options: ['default' => Suit::Hearts])]
|
||||
public Suit $suit;
|
||||
|
||||
Using Enums in Queries
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Enum instances can be used directly as parameters in DQL, QueryBuilder, and
|
||||
repository methods. Doctrine converts them to their scalar value automatically.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// QueryBuilder
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('c')
|
||||
->from(Card::class, 'c')
|
||||
->where('c.suit = :suit')
|
||||
->setParameter('suit', Suit::Clubs);
|
||||
|
||||
// Repository
|
||||
$cards = $em->getRepository(Card::class)->findBy(['suit' => Suit::Clubs]);
|
||||
|
||||
XML Mapping
|
||||
~~~~~~~~~~~
|
||||
|
||||
When using XML mapping, the ``enum-type`` attribute is used on ``<field>``
|
||||
elements:
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<field name="suit" type="string" enum-type="App\Entity\Suit" />
|
||||
|
||||
.. _reference-mapping-types:
|
||||
|
||||
Doctrine Mapping Types
|
||||
----------------------
|
||||
|
||||
The ``type`` option used in the ``@Column`` accepts any of the
|
||||
`existing Doctrine DBAL types <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/types.html#reference>`_
|
||||
or :doc:`your own custom mapping types
|
||||
<../cookbook/custom-mapping-types>`. A Doctrine type defines
|
||||
The ``type`` option used in the ``@Column`` accepts any of the existing
|
||||
Doctrine types or even your own custom types. A Doctrine type defines
|
||||
the conversion between PHP and SQL types, independent from the database vendor
|
||||
you are using.
|
||||
you are using. All Mapping Types that ship with Doctrine are fully portable
|
||||
between the supported database systems.
|
||||
|
||||
As an example, the Doctrine Mapping Type ``string`` defines the
|
||||
mapping from a PHP string to a SQL VARCHAR (or VARCHAR2 etc.
|
||||
depending on the RDBMS brand). Here is a quick overview of the
|
||||
built-in mapping types:
|
||||
|
||||
- ``string``: Type that maps an SQL VARCHAR to a PHP string.
|
||||
- ``integer``: Type that maps an SQL INT to a PHP integer.
|
||||
- ``smallint``: Type that maps a database SMALLINT to a PHP
|
||||
integer.
|
||||
- ``bigint``: Type that maps a database BIGINT to a PHP string.
|
||||
- ``boolean``: Type that maps an SQL boolean or equivalent (TINYINT) to a PHP boolean.
|
||||
- ``decimal``: Type that maps an SQL DECIMAL to a PHP string.
|
||||
- ``date``: Type that maps an SQL DATETIME to a PHP DateTime
|
||||
object.
|
||||
- ``date_immutable``: Type that maps an SQL DATETIME to a PHP DateTimeImmutable
|
||||
object.
|
||||
- ``time``: Type that maps an SQL TIME to a PHP DateTime object.
|
||||
- ``time_immutable``: Type that maps an SQL TIME to a PHP DateTimeImmutable object.
|
||||
- ``datetime``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime
|
||||
object with the current timezone.
|
||||
- ``datetimetz``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime
|
||||
object with the timezone specified in the value from the database.
|
||||
- ``datetime_immutable``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTimeImmutable
|
||||
object with the current timezone.
|
||||
- ``datetimetz_immutable``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTimeImmutable
|
||||
object with the timezone specified in the value from the database.
|
||||
- ``dateinterval``: Type that maps an interval to a PHP DateInterval object
|
||||
- ``text``: Type that maps an SQL CLOB to a PHP string.
|
||||
- ``object``: Type that maps an SQL CLOB to a PHP object using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``array``: Type that maps an SQL CLOB to a PHP array using
|
||||
``serialize()`` and ``unserialize()``
|
||||
- ``simple_array``: Type that maps an SQL CLOB to a one-dimensional PHP array using
|
||||
``implode()`` and ``explode()``, with a comma as delimiter. *IMPORTANT*
|
||||
Only use this type if you are sure that your values cannot contain a ",".
|
||||
- ``json_array``: Type that maps an SQL CLOB to a PHP array using
|
||||
``json_encode()`` and ``json_decode()``. This one has been deprecated in favor
|
||||
of ``json`` type.
|
||||
- ``json``: Type that maps an SQL CLOB to a PHP array using
|
||||
``json_encode()`` and ``json_decode()``. An empty value is correctly represented as ``null``
|
||||
- ``float``: Type that maps an SQL Float (Double Precision) to a
|
||||
PHP double. *IMPORTANT*: Works only with locale settings that use
|
||||
decimal points as separator.
|
||||
- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to
|
||||
varchar but uses a specific type if the platform supports it.
|
||||
- ``blob``: Type that maps an SQL BLOB to a PHP resource stream
|
||||
- ``binary``: Type that maps an SQL binary to a PHP resource stream
|
||||
|
||||
A cookbook article shows how to define :doc:`your own custom mapping types
|
||||
<../cookbook/custom-mapping-types>`.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -500,23 +269,12 @@ 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]`` attribute.
|
||||
the field that serves as the identifier with the ``@Id``
|
||||
annotation.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class Message
|
||||
{
|
||||
#[Id]
|
||||
#[Column(type: 'integer')]
|
||||
#[GeneratedValue]
|
||||
private int|null $id = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Message
|
||||
@@ -526,7 +284,7 @@ the field that serves as the identifier with the ``#[Id]`` attribute.
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private int|null $id = null;
|
||||
private $id;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -541,24 +299,10 @@ the field that serves as the identifier with the ``#[Id]`` attribute.
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Message:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
# fields here
|
||||
|
||||
In most cases using the automatic generator strategy (``#[GeneratedValue]``) is
|
||||
what you want, but for backwards-compatibility reasons it might not. It
|
||||
defaults to the identifier generation mechanism your current database
|
||||
vendor preferred at the time that strategy was introduced:
|
||||
``AUTO_INCREMENT`` with MySQL, sequences with PostgreSQL and Oracle and
|
||||
so on.
|
||||
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, SERIAL with PostgreSQL,
|
||||
Sequences with Oracle and so on.
|
||||
|
||||
.. _identifier-generation-strategies:
|
||||
|
||||
@@ -575,26 +319,26 @@ Here is the list of possible generation strategies:
|
||||
|
||||
- ``AUTO`` (default): Tells Doctrine to pick the strategy that is
|
||||
preferred by the used database platform. The preferred strategies
|
||||
are ``IDENTITY`` for MySQL, SQLite, MsSQL and SQL Anywhere and, for
|
||||
historical reasons, ``SEQUENCE`` for Oracle and PostgreSQL. This
|
||||
strategy provides full portability.
|
||||
- ``IDENTITY``: Tells Doctrine to use special identity columns in
|
||||
the database that generate a value on insertion of a row. This
|
||||
strategy does currently not provide full portability and is
|
||||
supported by the following platforms: MySQL/SQLite/SQL Anywhere
|
||||
(``AUTO_INCREMENT``), MSSQL (``IDENTITY``) and PostgreSQL (``SERIAL``).
|
||||
are IDENTITY for MySQL, SQLite, MsSQL and SQL Anywhere and SEQUENCE
|
||||
for Oracle and PostgreSQL. This strategy provides full portability.
|
||||
- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID
|
||||
generation. This strategy does currently not provide full
|
||||
portability. Sequences are supported by Oracle, PostgreSql and
|
||||
SQL Anywhere.
|
||||
- ``UUID`` (deprecated): Tells Doctrine to use the built-in Universally
|
||||
Unique Identifier generator. This strategy provides full portability.
|
||||
- ``IDENTITY``: Tells Doctrine to use special identity columns in
|
||||
the database that generate a value on insertion of a row. This
|
||||
strategy does currently not provide full portability and is
|
||||
supported by the following platforms: MySQL/SQLite/SQL Anywhere
|
||||
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
|
||||
- ``TABLE``: Tells Doctrine to use a separate table for ID
|
||||
generation. This strategy provides full portability.
|
||||
***This strategy is not yet implemented!***
|
||||
- ``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]`` attribute.
|
||||
It will allow you to pass a :ref:`class of your own to generate the identifiers. <annref_customidgenerator>`
|
||||
same as leaving off the @GeneratedValue entirely.
|
||||
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
|
||||
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
|
||||
|
||||
Sequence Generator
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
@@ -605,19 +349,7 @@ besides specifying the sequence's name:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. 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
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class Message
|
||||
@@ -627,7 +359,7 @@ besides specifying the sequence's name:
|
||||
* @GeneratedValue(strategy="SEQUENCE")
|
||||
* @SequenceGenerator(sequenceName="message_seq", initialValue=1, allocationSize=100)
|
||||
*/
|
||||
protected int|null $id = null;
|
||||
protected $id = null;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -642,20 +374,6 @@ besides specifying the sequence's name:
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Message:
|
||||
type: entity
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
generator:
|
||||
strategy: SEQUENCE
|
||||
sequenceGenerator:
|
||||
sequenceName: message_seq
|
||||
allocationSize: 100
|
||||
initialValue: 1
|
||||
|
||||
The initial value specifies at which value the sequence should
|
||||
start.
|
||||
|
||||
@@ -664,10 +382,12 @@ 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 ORM would only
|
||||
the above example with ``allocationSize=100`` Doctrine 2 would only
|
||||
need to access the sequence once to generate the identifiers for
|
||||
100 new entities.
|
||||
|
||||
*The default allocationSize for a @SequenceGenerator is currently 10.*
|
||||
|
||||
.. caution::
|
||||
|
||||
The allocationSize is detected by SchemaTool and
|
||||
@@ -677,7 +397,6 @@ need to access the sequence once to generate the identifiers for
|
||||
configuration option is never larger than the actual sequences
|
||||
INCREMENT BY value, otherwise you may get duplicate keys.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
It is possible to use strategy="AUTO" and at the same time
|
||||
@@ -686,16 +405,14 @@ need to access the sequence once to generate the identifiers for
|
||||
of the underlying platform is SEQUENCE, such as for Oracle and
|
||||
PostgreSQL.
|
||||
|
||||
|
||||
Composite Keys
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
More details on composite primary keys are discussed in a :doc:`dedicated tutorial
|
||||
<../tutorials/composite-primary-keys>`.
|
||||
@@ -711,8 +428,7 @@ 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
|
||||
@@ -725,11 +441,15 @@ 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 ORM. It is invoked for every column, table, alias and other
|
||||
was introduced in 2.3. 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:
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
|
||||
#[Entity]
|
||||
class Message
|
||||
{
|
||||
#[Column(options: ['default' => 'Hello World!'])]
|
||||
private string $text;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class Player
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private int $id;
|
||||
|
||||
/** @var list<Suit> */
|
||||
#[ORM\Column(type: 'json', enumType: Suit::class)]
|
||||
private array $favouriteSuits = [];
|
||||
|
||||
/** @var list<Suit> */
|
||||
#[ORM\Column(type: 'simple_array', enumType: Suit::class)]
|
||||
private array $allowedSuits = [];
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
class Card
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private int $id;
|
||||
|
||||
#[ORM\Column(enumType: Suit::class)]
|
||||
private Suit $suit;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
enum Suit: string
|
||||
{
|
||||
case Hearts = 'hearts';
|
||||
case Diamonds = 'diamonds';
|
||||
case Clubs = 'clubs';
|
||||
case Spades = 'spades';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<doctrine-mapping>
|
||||
<entity name="Message">
|
||||
<field name="text">
|
||||
<options>
|
||||
<option name="default">Hello World!</option>
|
||||
</options>
|
||||
</field>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -15,24 +15,6 @@ especially what the strategies presented here provide help with.
|
||||
you use the tools for your particular RDBMS for these bulk
|
||||
operations.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Having an SQL logger enabled when processing batches can have a
|
||||
serious impact on performance and resource usage.
|
||||
To avoid that, you should use a PSR logger implementation that can be
|
||||
disabled at runtime.
|
||||
For example, with Monolog, you can use ``Logger::pushHandler()``
|
||||
to push a ``NullHandler`` to the logger instance, and then pop it
|
||||
when you need to enable logging again.
|
||||
|
||||
With DBAL 2, you can disable the SQL logger like below:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConnection()->getConfiguration()->setSQLLogger(null);
|
||||
|
||||
Bulk Inserts
|
||||
------------
|
||||
|
||||
@@ -83,7 +65,7 @@ Iterating results
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
An alternative solution for bulk updates is to use the
|
||||
``Query#toIterable()`` facility to iterate over the query results step
|
||||
``Query#iterate()`` 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:
|
||||
@@ -92,16 +74,18 @@ with the batching strategy that was already used for bulk inserts:
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 0;
|
||||
$i = 1;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
foreach ($q->toIterable() as $user) {
|
||||
$iterableResult = $q->iterate();
|
||||
foreach ($iterableResult as $row) {
|
||||
$user = $row[0];
|
||||
$user->increaseCredit();
|
||||
$user->calculateNewBonuses();
|
||||
++$i;
|
||||
if (($i % $batchSize) === 0) {
|
||||
$em->flush(); // Executes all updates.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
$em->flush();
|
||||
|
||||
@@ -117,7 +101,6 @@ with the batching strategy that was already used for bulk inserts:
|
||||
additional memory not visible to the PHP process. For large sets this
|
||||
may easily kill the process for no apparent reason.
|
||||
|
||||
|
||||
Bulk Deletes
|
||||
------------
|
||||
|
||||
@@ -143,7 +126,7 @@ Iterating results
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
An alternative solution for bulk deletes is to use the
|
||||
``Query#toIterable()`` facility to iterate over the query results step
|
||||
``Query#iterate()`` 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:
|
||||
|
||||
@@ -151,15 +134,16 @@ The following example shows how to do this:
|
||||
|
||||
<?php
|
||||
$batchSize = 20;
|
||||
$i = 0;
|
||||
$i = 1;
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
foreach($q->toIterable() as $row) {
|
||||
$em->remove($row);
|
||||
++$i;
|
||||
$iterableResult = $q->iterate();
|
||||
while (($row = $iterableResult->next()) !== false) {
|
||||
$em->remove($row[0]);
|
||||
if (($i % $batchSize) === 0) {
|
||||
$em->flush(); // Executes all deletions.
|
||||
$em->clear(); // Detaches all objects from Doctrine!
|
||||
}
|
||||
++$i;
|
||||
}
|
||||
$em->flush();
|
||||
|
||||
@@ -169,24 +153,25 @@ The following example shows how to do this:
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
|
||||
Iterating Large Results for Data-Processing
|
||||
-------------------------------------------
|
||||
|
||||
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
|
||||
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
|
||||
problems using the following approach:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$q = $this->_em->createQuery('select u from MyProject\Model\User u');
|
||||
foreach ($q->toIterable() as $row) {
|
||||
// do stuff with the data in the row
|
||||
$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
|
||||
|
||||
// detach from Doctrine, so that it can be Garbage-Collected immediately
|
||||
$this->_em->detach($row[0]);
|
||||
// detach all entities from Doctrine, so that Garbage-Collection can kick in immediately
|
||||
$this->em->clear();
|
||||
}
|
||||
|
||||
.. note::
|
||||
@@ -194,3 +179,10 @@ problems using the following approach:
|
||||
Iterating results is not possible with queries that
|
||||
fetch-join a collection-valued association. The nature of such SQL
|
||||
result sets is not suitable for incremental hydration.
|
||||
|
||||
Packages for easing Batch Processing
|
||||
------------------------------------
|
||||
|
||||
You can implement batch processing yourself, or use an existing
|
||||
package such as `DoctrineBatchUtils <https://github.com/Ocramius/DoctrineBatchUtils>`_,
|
||||
which already provides the logic described above in an encapsulated format.
|
||||
|
||||
@@ -12,14 +12,12 @@ Constrain relationships as much as possible
|
||||
It is important to constrain relationships as much as possible.
|
||||
This means:
|
||||
|
||||
|
||||
- Impose a traversal direction (avoid bidirectional associations
|
||||
if possible)
|
||||
- Eliminate nonessential associations
|
||||
|
||||
This has several benefits:
|
||||
|
||||
|
||||
- Reduced coupling in your domain model
|
||||
- Simpler code in your domain model (no need to maintain
|
||||
bidirectionality properly)
|
||||
@@ -43,7 +41,7 @@ should use events judiciously.
|
||||
Use cascades judiciously
|
||||
------------------------
|
||||
|
||||
Automatic cascades of the persist/remove/merge/etc. operations are
|
||||
Automatic cascades of the persist/remove/refresh/etc. operations are
|
||||
very handy but should be used wisely. Do NOT simply add all
|
||||
cascades to all associations. Think about which cascades actually
|
||||
do make sense for you for a particular association, given the
|
||||
@@ -76,10 +74,8 @@ collections in entities in the constructor. Example:
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class User {
|
||||
/** @var Collection<int, Address> */
|
||||
private Collection $addresses;
|
||||
/** @var Collection<int, Article> */
|
||||
private Collection $articles;
|
||||
private $addresses;
|
||||
private $articles;
|
||||
|
||||
public function __construct() {
|
||||
$this->addresses = new ArrayCollection;
|
||||
@@ -110,4 +106,3 @@ queries generally don't have any noticeable performance impact, it
|
||||
is still preferable to use fewer, well-defined transactions that
|
||||
are established through explicit transaction boundaries.
|
||||
|
||||
|
||||
|
||||
@@ -1,14 +1,291 @@
|
||||
Caching
|
||||
=======
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
.. _types-of-caches:
|
||||
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>`_.
|
||||
|
||||
APC
|
||||
~~~
|
||||
|
||||
In order to use the APC cache driver you must have it compiled and
|
||||
enabled in your php.ini. You can read about APC
|
||||
`in the PHP Documentation <https://php.net/apc>`_. 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 APC cache driver
|
||||
by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
APCu
|
||||
~~~~
|
||||
|
||||
In order to use the APCu cache driver you must have it compiled and
|
||||
enabled in your php.ini. You can read about APCu
|
||||
`in the PHP Documentation <https://php.net/apcu>`_. 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 APCu cache driver
|
||||
by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\ApcuCache();
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
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 <https://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 <https://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');
|
||||
|
||||
Xcache
|
||||
~~~~~~
|
||||
|
||||
In order to use the Xcache cache driver you must have it compiled
|
||||
and enabled in your php.ini. You can read about Xcache
|
||||
`here <https://xcache.lighttpd.net/>`_. 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 Xcache cache
|
||||
driver by itself.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
|
||||
$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 <https://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
|
||||
------------------------
|
||||
|
||||
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.
|
||||
|
||||
Query Cache
|
||||
~~~~~~~~~~~
|
||||
@@ -24,9 +301,8 @@ use on your ORM configuration.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter('doctrine_queries');
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setQueryCache($cache);
|
||||
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
Result Cache
|
||||
~~~~~~~~~~~~
|
||||
@@ -38,13 +314,7 @@ You just need to configure the result cache implementation.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
|
||||
'doctrine_results',
|
||||
0,
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setResultCache($cache);
|
||||
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
Now when you're executing DQL queries you can configure them to use
|
||||
the result cache.
|
||||
@@ -53,7 +323,7 @@ the result cache.
|
||||
|
||||
<?php
|
||||
$query = $em->createQuery('select u from \Entities\User u');
|
||||
$query->enableResultCache();
|
||||
$query->useResultCache(true);
|
||||
|
||||
You can also configure an individual query to use a different
|
||||
result cache driver.
|
||||
@@ -61,24 +331,18 @@ result cache driver.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cache = new \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
|
||||
'doctrine_results',
|
||||
0,
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$query->setResultCache($cache);
|
||||
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
.. note::
|
||||
|
||||
Setting the result cache driver on the query will
|
||||
automatically enable the result cache for the query. If you want to
|
||||
disable it use ``disableResultCache()``.
|
||||
disable it pass false to ``useResultCache()``.
|
||||
|
||||
::
|
||||
|
||||
<?php
|
||||
$query->disableResultCache();
|
||||
|
||||
$query->useResultCache(false);
|
||||
|
||||
If you want to set the time the cache has to live you can use the
|
||||
``setResultCacheLifetime()`` method.
|
||||
@@ -98,20 +362,19 @@ 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 first and second argument to ``enableResultCache()``.
|
||||
the second and third argument to ``useResultCache()``.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->enableResultCache(3600, 'my_custom_id');
|
||||
$query->useResultCache(true, 3600, 'my_custom_id');
|
||||
|
||||
Metadata Cache
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Your class metadata can be parsed from a few different sources like
|
||||
YAML, XML, Attributes, Annotations etc. Instead of parsing this
|
||||
information on each request we should cache it using one of the cache
|
||||
drivers.
|
||||
XML, 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.
|
||||
@@ -119,13 +382,7 @@ first.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cache = \Symfony\Component\Cache\Adapter\PhpFilesAdapter(
|
||||
'doctrine_metadata',
|
||||
0,
|
||||
'/path/to/writable/directory'
|
||||
);
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setMetadataCache($cache);
|
||||
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
Now the metadata information will only be parsed once and stored in
|
||||
the cache driver.
|
||||
@@ -161,12 +418,6 @@ To clear the result cache use the ``orm:clear-cache:result`` task.
|
||||
All these tasks accept a ``--flush`` option to flush the entire
|
||||
contents of the cache instead of invalidating the entries.
|
||||
|
||||
.. note::
|
||||
|
||||
None of these tasks will work with APC, APCu, or XCache drivers
|
||||
because the memory that the cache is stored in is only accessible
|
||||
to the webserver.
|
||||
|
||||
Cache Chaining
|
||||
--------------
|
||||
|
||||
@@ -175,15 +426,30 @@ 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.
|
||||
|
||||
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 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
|
||||
the chain and clearing data in the entire stack when it is deleted.
|
||||
|
||||
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>`_.
|
||||
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.
|
||||
|
||||
Cache Slams
|
||||
-----------
|
||||
@@ -200,3 +466,4 @@ 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 ORM only
|
||||
comparison at commit time. The difference is that Doctrine 2 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,9 +49,10 @@ This policy can be configured as follows:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
#[Entity]
|
||||
#[ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
@@ -60,11 +61,6 @@ 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
|
||||
@@ -75,20 +71,22 @@ follows:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Persistence\NotifyPropertyChanged,
|
||||
Doctrine\Persistence\PropertyChangedListener;
|
||||
use Doctrine\Common\NotifyPropertyChanged,
|
||||
Doctrine\Common\PropertyChangedListener;
|
||||
|
||||
#[Entity]
|
||||
#[ChangeTrackingPolicy('NOTIFY')]
|
||||
/**
|
||||
* @Entity
|
||||
* @ChangeTrackingPolicy("NOTIFY")
|
||||
*/
|
||||
class MyEntity implements NotifyPropertyChanged
|
||||
{
|
||||
// ...
|
||||
|
||||
private array $_listeners = array();
|
||||
private $listeners = array();
|
||||
|
||||
public function addPropertyChangedListener(PropertyChangedListener $listener): void
|
||||
public function addPropertyChangedListener(PropertyChangedListener $listener)
|
||||
{
|
||||
$this->_listeners[] = $listener;
|
||||
$this->listeners[] = $listener;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,57 +104,31 @@ behaviour:
|
||||
{
|
||||
// ...
|
||||
|
||||
protected function _onPropertyChanged($propName, $oldValue, $newValue): void
|
||||
protected function onPropertyChanged($propName, $oldValue, $newValue)
|
||||
{
|
||||
if ($this->_listeners) {
|
||||
foreach ($this->_listeners as $listener) {
|
||||
if ($this->listeners) {
|
||||
foreach ($this->listeners as $listener) {
|
||||
$listener->propertyChanged($this, $propName, $oldValue, $newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setData($data): void
|
||||
public function setData($data)
|
||||
{
|
||||
if ($data != $this->data) {
|
||||
$this->_onPropertyChanged('data', $this->data, $data);
|
||||
$this->onPropertyChanged('data', $this->data, $data);
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
You have to invoke ``_onPropertyChanged`` inside every method that
|
||||
You have to invoke ``onPropertyChanged`` inside every method that
|
||||
changes the persistent state of ``MyEntity``.
|
||||
|
||||
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
|
||||
@@ -175,3 +147,4 @@ 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,80 +41,88 @@ access point to ORM functionality provided by Doctrine.
|
||||
// bootstrap.php
|
||||
require_once "vendor/autoload.php";
|
||||
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
|
||||
$paths = ['/path/to/entity-files'];
|
||||
$paths = array("/path/to/entity-files");
|
||||
$isDevMode = false;
|
||||
|
||||
// the connection configuration
|
||||
$dbParams = [
|
||||
$dbParams = array(
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => 'root',
|
||||
'password' => '',
|
||||
'dbname' => 'foo',
|
||||
];
|
||||
);
|
||||
|
||||
$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.
|
||||
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
Or if you prefer XML:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$paths = ['/path/to/xml-mappings'];
|
||||
$config = ORMSetup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$connection = DriverManager::getConnection($dbParams, $config);
|
||||
$entityManager = new EntityManager($connection, $config);
|
||||
$paths = array("/path/to/xml-mappings");
|
||||
$config = Setup::createXMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
Or if you prefer YAML:
|
||||
Inside the ``Setup`` methods several assumptions are made:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$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 ``ORMSetup`` methods several assumptions are made:
|
||||
|
||||
- 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 `$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 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.
|
||||
If you want to configure Doctrine in more detail, take a look at the :doc:`Advanced Configuration <reference/advanced-configuration>` section.
|
||||
|
||||
.. note::
|
||||
|
||||
You can learn more about the database connection configuration in the
|
||||
`Doctrine DBAL connection configuration reference <https://docs.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/configuration.html>`_.
|
||||
`Doctrine DBAL connection configuration reference <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||
|
||||
Setting up the Commandline Tool
|
||||
-------------------------------
|
||||
|
||||
Doctrine ships with a number of command line tools that are very helpful
|
||||
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>`.
|
||||
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)
|
||||
));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -13,10 +13,33 @@ Database Schema
|
||||
How do I set the charset and collation for MySQL tables?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In your mapping configuration, the column definition (for example, the
|
||||
``#[Column]`` attribute) has an ``options`` parameter where you can specify
|
||||
the ``charset`` and ``collation``. The default values are ``utf8`` and
|
||||
``utf8_unicode_ci``, respectively.
|
||||
You can't set these values inside the annotations 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
|
||||
--------------
|
||||
|
||||
How can I add default values to a column?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL.
|
||||
This is not necessary however, you can just use your class properties as default values. These are then used
|
||||
upon insert:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
class User
|
||||
{
|
||||
const STATUS_DISABLED = 0;
|
||||
const STATUS_ENABLED = 1;
|
||||
|
||||
private $algorithm = "sha1";
|
||||
private $status = self:STATUS_DISABLED;
|
||||
}
|
||||
|
||||
.
|
||||
|
||||
Mapping
|
||||
-------
|
||||
@@ -57,7 +80,7 @@ You can solve this exception by:
|
||||
How can I filter an association?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You should use DQL queries to query for the filtered set of entities.
|
||||
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.
|
||||
|
||||
I call clear() on a One-To-Many collection but the entities are not deleted
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -75,10 +98,9 @@ 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 ORM.
|
||||
|
||||
See :doc:`the tutorial on composite primary keys for more information <../tutorials/composite-primary-keys>`.
|
||||
foreign keys as primary keys feature of Doctrine introduced in version 2.1.
|
||||
|
||||
See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`.
|
||||
|
||||
How can i paginate fetch-joined collections?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -105,10 +127,10 @@ See the previous question for a solution to this task.
|
||||
Inheritance
|
||||
-----------
|
||||
|
||||
Can I use Inheritance with Doctrine ORM?
|
||||
Can I use Inheritance with Doctrine 2?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Yes, you can use Single- or Joined-Table Inheritance in ORM.
|
||||
Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2.
|
||||
|
||||
See the documentation chapter on :doc:`inheritance mapping <inheritance-mapping>` for
|
||||
the details.
|
||||
@@ -120,23 +142,6 @@ If you set a many-to-one or one-to-one association target-entity to any parent c
|
||||
an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of.
|
||||
To find this out it has to execute a SQL query to look this information up in the database.
|
||||
|
||||
EntityGenerator
|
||||
---------------
|
||||
|
||||
Why does the EntityGenerator not do X?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation
|
||||
is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator
|
||||
is supposed to kick-start you, but not towards 100%.
|
||||
|
||||
Why does the EntityGenerator not generate inheritance correctly?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierarchy.
|
||||
This is why the generation of inherited entities does not fully work. You have to adjust some additional
|
||||
code to get this one working correctly.
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
@@ -175,21 +180,6 @@ No, it is not supported to sort by function in DQL. If you need this functionali
|
||||
use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow
|
||||
starting with 1000 rows.
|
||||
|
||||
Is it better to write DQL or to generate it with the query builder?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The purpose of the ``QueryBuilder`` is to generate DQL dynamically,
|
||||
which is useful when you have optional filters, conditional joins, etc.
|
||||
|
||||
But the ``QueryBuilder`` is not an alternative to DQL, it actually generates DQL
|
||||
queries at runtime, which are then interpreted by Doctrine. This means that
|
||||
using the ``QueryBuilder`` to build and run a query is actually always slower
|
||||
than only running the corresponding DQL query.
|
||||
|
||||
So if you only need to generate a query and bind parameters to it,
|
||||
you should use plain DQL, as this is a simpler and much more readable solution.
|
||||
You should only use the ``QueryBuilder`` when you can't achieve what you want to do with a DQL query.
|
||||
|
||||
A Query fails, how can I debug it?
|
||||
----------------------------------
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
Filters
|
||||
=======
|
||||
|
||||
Doctrine ORM features a filter system that allows the developer to add SQL to
|
||||
.. versionadded:: 2.2
|
||||
|
||||
Doctrine 2.2 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).
|
||||
|
||||
@@ -14,7 +16,6 @@ By adding SQL to the conditional clauses of queries, the filter system filters
|
||||
out rows belonging to the entities at the level of the SQL result set. This
|
||||
means that the filtered entities are never hydrated (which can be expensive).
|
||||
|
||||
|
||||
Example filter class
|
||||
--------------------
|
||||
Throughout this document the example ``MyLocaleFilter`` class will be used to
|
||||
@@ -28,25 +29,24 @@ table alias of the SQL table of the entity.
|
||||
In the case of joined or single table inheritance, you always get passed the ClassMetadata of the
|
||||
inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters.
|
||||
|
||||
For the filter to correctly function, the following rules must be followed. Failure to do so will lead to unexpected results from the query cache.
|
||||
1. Parameters for the query should be set on the filter object by ``SQLFilter#setParameter()`` before the filter is used by the ORM ( i.e. do not set parameters inside ``SQLFilter#addFilterConstraint()`` function ).
|
||||
2. The filter must be deterministic. Don't change the values based on external inputs.
|
||||
|
||||
The ``SQLFilter#getParameter()`` function takes care of the proper quoting of parameters.
|
||||
Parameters for the query should be set on the filter object by
|
||||
``SQLFilter#setParameter()``. Only parameters set via this function can be used
|
||||
in filters. The ``SQLFilter#getParameter()`` function takes care of the
|
||||
proper quoting of parameters.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Example;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata,
|
||||
use Doctrine\ORM\Mapping\ClassMetaData,
|
||||
Doctrine\ORM\Query\Filter\SQLFilter;
|
||||
|
||||
class MyLocaleFilter extends SQLFilter
|
||||
{
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias): string
|
||||
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||
{
|
||||
// Check if the entity implements the LocalAware interface
|
||||
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) {
|
||||
if (!$targetEntity->getReflectionClass()->implementsInterface('LocaleAware')) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -54,10 +54,6 @@ The ``SQLFilter#getParameter()`` function takes care of the proper quoting of pa
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
-------------
|
||||
Filter classes are added to the configuration as following:
|
||||
@@ -67,11 +63,9 @@ Filter classes are added to the configuration as following:
|
||||
<?php
|
||||
$config->addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter");
|
||||
|
||||
|
||||
The ``Configuration#addFilter()`` method takes a name for the filter and the name of the
|
||||
class responsible for the actual filtering.
|
||||
|
||||
|
||||
Disabling/Enabling Filters and Setting Parameters
|
||||
---------------------------------------------------
|
||||
Filters can be disabled and enabled via the ``FilterCollection`` which is
|
||||
@@ -93,34 +87,3 @@ object.
|
||||
want to refresh or reload an object after having modified a filter or the
|
||||
FilterCollection, then you should clear the EntityManager and re-fetch your
|
||||
entities, having the new rules for filtering applied.
|
||||
|
||||
|
||||
Suspending/Restoring Filters
|
||||
----------------------------
|
||||
When a filter is disabled, the instance is fully deleted and all the filter
|
||||
parameters previously set are lost. Then, if you enable it again, a new filter
|
||||
is created without the previous filter parameters. If you want to keep a filter
|
||||
(in order to use it later) but temporary disable it, you'll need to use the
|
||||
``FilterCollection#suspend($name)`` and ``FilterCollection#restore($name)``
|
||||
methods instead.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$filter = $em->getFilters()->enable("locale");
|
||||
$filter->setParameter('locale', 'en');
|
||||
|
||||
// Temporary suspend the filter
|
||||
$filter = $em->getFilters()->suspend("locale");
|
||||
|
||||
// Do things
|
||||
|
||||
// Then restore it, the locale parameter will still be set
|
||||
$filter = $em->getFilters()->restore("locale");
|
||||
|
||||
.. warning::
|
||||
If you enable a previously disabled filter, doctrine will create a new
|
||||
one without keeping any of the previously parameter set with
|
||||
``SQLFilter#setParameter()`` or ``SQLFilter#getParameterList()``. If you
|
||||
want to restore the previously disabled filter instead, you must use the
|
||||
``FilterCollection#restore($name)`` method.
|
||||
|
||||
@@ -4,7 +4,7 @@ Improving Performance
|
||||
Bytecode Cache
|
||||
--------------
|
||||
|
||||
It is highly recommended to make use of a bytecode cache like OPcache.
|
||||
It is highly recommended to make use of a bytecode cache like APC.
|
||||
A bytecode cache removes the need for parsing PHP code on every
|
||||
request and can greatly improve performance.
|
||||
|
||||
@@ -14,27 +14,17 @@ request and can greatly improve performance.
|
||||
|
||||
*Stas Malyshev, Core Contributor to PHP and Zend Employee*
|
||||
|
||||
|
||||
Metadata and Query caches
|
||||
-------------------------
|
||||
|
||||
As already mentioned earlier in the chapter about configuring
|
||||
Doctrine, it is strongly discouraged to use Doctrine without a
|
||||
Metadata and Query cache.
|
||||
|
||||
Operating Doctrine without these caches means
|
||||
Metadata and Query cache (preferably with APC or Memcache as the
|
||||
cache driver). Operating Doctrine without these caches means
|
||||
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 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:`types-of-caches`
|
||||
|
||||
Alternative Query Result Formats
|
||||
--------------------------------
|
||||
|
||||
@@ -45,32 +35,11 @@ in scenarios where data is loaded for read-only purposes.
|
||||
Read-Only Entities
|
||||
------------------
|
||||
|
||||
You can mark entities as read only. For details, see :ref:`attrref_entity`
|
||||
|
||||
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.
|
||||
|
||||
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();
|
||||
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.
|
||||
|
||||
Extra-Lazy Collections
|
||||
----------------------
|
||||
@@ -91,8 +60,6 @@ Apply Best Practices
|
||||
A lot of the points mentioned in the Best Practices chapter will
|
||||
also positively affect the performance of Doctrine.
|
||||
|
||||
See :doc:`Best Practices </reference/best-practices>`
|
||||
|
||||
Change Tracking policies
|
||||
------------------------
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
Inheritance Mapping
|
||||
===================
|
||||
|
||||
This chapter explains the available options for mapping class
|
||||
hierarchies.
|
||||
|
||||
Mapped Superclasses
|
||||
-------------------
|
||||
|
||||
@@ -15,95 +12,56 @@ is common to multiple entity classes.
|
||||
|
||||
Mapped superclasses, just as regular, non-mapped classes, can
|
||||
appear in the middle of an otherwise mapped inheritance hierarchy
|
||||
(through Single Table Inheritance or Class Table Inheritance). They
|
||||
are not query-able, and need not have an ``#[Id]`` property.
|
||||
|
||||
No database table will be created for a mapped superclass itself,
|
||||
only for entity classes inheriting from it. That implies that a
|
||||
mapped superclass cannot be the ``targetEntity`` in associations.
|
||||
|
||||
In other words, a mapped superclass can use unidirectional One-To-One
|
||||
and Many-To-One associations where it is the owning side.
|
||||
Many-To-Many associations are only possible if the mapped
|
||||
superclass is only used in exactly one entity at the moment. For further
|
||||
support of inheritance, the single or joined table inheritance features
|
||||
have to be used.
|
||||
(through Single Table Inheritance or Class Table Inheritance).
|
||||
|
||||
.. note::
|
||||
|
||||
One-To-Many associations are not generally possible on a mapped
|
||||
superclass, since they require the "many" side to hold the foreign
|
||||
key.
|
||||
|
||||
It is, however, possible to use the :doc:`ResolveTargetEntityListener </cookbook/resolve-target-entity-listener>`
|
||||
to replace references to a mapped superclass with an entity class at runtime.
|
||||
As long as there is only one entity subclass inheriting from the mapped
|
||||
superclass and all references to the mapped superclass are resolved to that
|
||||
entity class at runtime, the mapped superclass *can* use One-To-Many associations
|
||||
and be named as the ``targetEntity`` on the owning sides.
|
||||
|
||||
.. warning::
|
||||
|
||||
At least when using attributes or annotations to specify your mapping,
|
||||
it *seems* as if you could inherit from a base class that is neither
|
||||
an entity nor a mapped superclass, but has properties with mapping configuration
|
||||
on them that would also be used in the inheriting class.
|
||||
|
||||
This, however, is due to how the corresponding mapping
|
||||
drivers work and what the PHP reflection API reports for inherited fields.
|
||||
|
||||
Such a configuration is explicitly not supported. To give just one example,
|
||||
it will break for ``private`` properties.
|
||||
|
||||
.. note::
|
||||
|
||||
You may be tempted to use traits to mix mapped fields or relationships
|
||||
into your entity classes to circumvent some of the limitations of
|
||||
mapped superclasses. Before doing that, please read the section on traits
|
||||
in the :doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
|
||||
A mapped superclass cannot be an entity, it is not query-able and
|
||||
persistent relationships defined by a mapped superclass must be
|
||||
unidirectional (with an owning side only). This means that One-To-Many
|
||||
associations are not possible on a mapped superclass at all.
|
||||
Furthermore Many-To-Many associations are only possible if the
|
||||
mapped superclass is only used in exactly one entity at the moment.
|
||||
For further support of inheritance, the single or
|
||||
joined table inheritance features have to be used.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
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]
|
||||
/** @MappedSuperclass */
|
||||
class Person
|
||||
{
|
||||
#[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;
|
||||
/** @Column(type="integer") */
|
||||
protected $mapped1;
|
||||
/** @Column(type="string") */
|
||||
protected $mapped2;
|
||||
/**
|
||||
* @OneToOne(targetEntity="Toothbrush")
|
||||
* @JoinColumn(name="toothbrush_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $toothbrush;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
/** @Entity */
|
||||
class Employee extends Person
|
||||
{
|
||||
#[Id, Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
#[Column(type: 'string')]
|
||||
private string $name;
|
||||
/** @Id @Column(type="integer") */
|
||||
private $id;
|
||||
/** @Column(type="string") */
|
||||
private $name;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
/** @Entity */
|
||||
class Toothbrush
|
||||
{
|
||||
#[Id, Column(type: 'integer')]
|
||||
private int|null $id = null;
|
||||
/** @Id @Column(type="integer") */
|
||||
private $id;
|
||||
|
||||
// ... more fields and methods
|
||||
}
|
||||
@@ -113,102 +71,27 @@ like this (this is for SQLite):
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
CREATE TABLE Employee (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, toothbrush_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
|
||||
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
|
||||
|
||||
As you can see from this DDL snippet, there is only a single table
|
||||
for the entity subclass. All the mappings from the mapped
|
||||
superclass were inherited to the subclass as if they had been
|
||||
defined on that class directly.
|
||||
|
||||
Entity Inheritance
|
||||
------------------
|
||||
|
||||
As soon as one entity class inherits from another entity class, either
|
||||
directly, with a mapped superclass or other unmapped (also called
|
||||
"transient") classes in between, these entities form an inheritance
|
||||
hierarchy. The topmost entity class in this hierarchy is called the
|
||||
root entity, and the hierarchy includes all entities that are
|
||||
descendants of this root entity.
|
||||
|
||||
On the root entity class, ``#[InheritanceType]``,
|
||||
``#[DiscriminatorColumn]`` and ``#[DiscriminatorMap]`` must be specified.
|
||||
|
||||
``#[InheritanceType]`` specifies one of the two available inheritance
|
||||
mapping strategies that are explained in the following sections.
|
||||
|
||||
``#[DiscriminatorColumn]`` designates the so-called discriminator column.
|
||||
This is an extra column in the table that keeps information about which
|
||||
type from the hierarchy applies for a particular database row.
|
||||
|
||||
``#[DiscriminatorMap]`` declares the possible values for the discriminator
|
||||
column and maps them to class names in the hierarchy. This discriminator map
|
||||
has to declare all non-abstract entity classes that exist in that particular
|
||||
inheritance hierarchy. That includes the root as well as any intermediate
|
||||
entity classes, given they are not abstract.
|
||||
|
||||
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 contains the
|
||||
lowercase short name of each class as key.
|
||||
|
||||
.. note::
|
||||
|
||||
Automatically generating the discriminator map is very expensive
|
||||
computation-wise. The mapping driver has to provide all classes
|
||||
for which mapping configuration exists, and those have to be
|
||||
loaded and checked against the current inheritance hierarchy
|
||||
to see if they are part of it. The resulting map, however, can be kept
|
||||
in the metadata cache.
|
||||
|
||||
Performance impact on to-one associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There is a general performance consideration when using entity inheritance:
|
||||
If the target-entity of a many-to-one or one-to-one association is part of
|
||||
an inheritance hierarchy, it is preferable for performance reasons that it
|
||||
be a leaf entity (ie. have no subclasses).
|
||||
|
||||
Otherwise, the ORM is currently unable to tell beforehand which entity class
|
||||
will have to be used, and so no appropriate proxy instance can be created.
|
||||
That means the referred-to entities will *always* be loaded eagerly, which
|
||||
might even propagate to relationships of these entities (in the case of
|
||||
self-referencing associations).
|
||||
|
||||
Single Table Inheritance
|
||||
------------------------
|
||||
|
||||
`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.
|
||||
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
|
||||
discriminator column is used.
|
||||
|
||||
Example:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. 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
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
@@ -232,41 +115,23 @@ Example:
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
Things to note:
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Person" inheritance-type="SINGLE_TABLE">
|
||||
<discriminator-column name="discr" type="string" />
|
||||
<discriminator-map>
|
||||
<discriminator-mapping value="person" class="MyProject\Model\Person"/>
|
||||
<discriminator-mapping value="employee" class="MyProject\Model\Employee"/>
|
||||
</discriminator-map>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="MyProject\Model\Employee">
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Person:
|
||||
type: entity
|
||||
inheritanceType: SINGLE_TABLE
|
||||
discriminatorColumn:
|
||||
name: discr
|
||||
type: string
|
||||
discriminatorMap:
|
||||
person: Person
|
||||
employee: Employee
|
||||
|
||||
MyProject\Model\Employee:
|
||||
type: entity
|
||||
|
||||
In this example, the ``#[DiscriminatorMap]`` specifies that in the
|
||||
discriminator column, a value of "person" identifies a row as being of type
|
||||
``Person`` and employee" identifies a row as being of type ``Employee``.
|
||||
- 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.
|
||||
- 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, an exception will be thrown.
|
||||
|
||||
Design-time considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -282,10 +147,17 @@ Performance impact
|
||||
|
||||
This strategy is very efficient for querying across all types in
|
||||
the hierarchy or for specific types. No table joins are required,
|
||||
only a ``WHERE`` clause listing the type identifiers. In particular,
|
||||
only a WHERE clause listing the type identifiers. In particular,
|
||||
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).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -293,7 +165,7 @@ For Single-Table-Inheritance to work in scenarios where you are
|
||||
using either a legacy database schema or a self-written database
|
||||
schema you have to make sure that all columns that are not in the
|
||||
root entity but in any of the different sub-entities has to allow
|
||||
null values. Columns that have ``NOT NULL`` constraints have to be on
|
||||
null values. Columns that have NOT NULL constraints have to be on
|
||||
the root entity of the single-table inheritance hierarchy.
|
||||
|
||||
Class Table Inheritance
|
||||
@@ -303,11 +175,10 @@ Class Table Inheritance
|
||||
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.
|
||||
|
||||
The discriminator column is placed in the topmost table of the hierarchy,
|
||||
because this is the easiest way to achieve polymorphic queries with Class
|
||||
Table Inheritance.
|
||||
of a parent class through a foreign key constraint. Doctrine 2
|
||||
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.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -316,24 +187,38 @@ Example:
|
||||
<?php
|
||||
namespace MyProject\Model;
|
||||
|
||||
#[Entity]
|
||||
#[InheritanceType('JOINED')]
|
||||
#[DiscriminatorColumn(name: 'discr', type: 'string')]
|
||||
#[DiscriminatorMap(['person' => Person::class, 'employee' => Employee::class])]
|
||||
/**
|
||||
* @Entity
|
||||
* @InheritanceType("JOINED")
|
||||
* @DiscriminatorColumn(name="discr", type="string")
|
||||
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||
*/
|
||||
class Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
/** @Entity */
|
||||
class Employee extends Person
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
As before, the ``#[DiscriminatorMap]`` specifies that in the
|
||||
discriminator column, a value of "person" identifies a row as being of type
|
||||
``Person`` and "employee" identifies a row as being of type ``Employee``.
|
||||
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
|
||||
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
|
||||
``Employee``.
|
||||
- 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, an exception will be thrown.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -343,7 +228,6 @@ discriminator column, a value of "person" identifies a row as being of type
|
||||
``ON DELETE CASCADE`` in all database implementations. A failure to
|
||||
implement this yourself will lead to dead rows in the database.
|
||||
|
||||
|
||||
Design-time considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -364,13 +248,17 @@ perform just about any query which can have a negative impact on
|
||||
performance, especially with large tables and/or large hierarchies.
|
||||
When partial objects are allowed, either globally or on the
|
||||
specific query, then querying for any type will not cause the
|
||||
tables of subtypes to be ``OUTER JOIN``ed which can increase
|
||||
tables of subtypes to be OUTER JOINed which can increase
|
||||
performance but the resulting partial objects will not fully load
|
||||
themselves on access of any subtype fields, so accessing fields of
|
||||
subtypes after such a query is not safe.
|
||||
|
||||
There is also another important performance consideration that it is *not possible*
|
||||
to query for the base entity without any ``LEFT JOIN``s to the sub-types.
|
||||
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).
|
||||
Otherwise Doctrine *CANNOT* create proxy instances
|
||||
of this entity and will *ALWAYS* load the entity eagerly.
|
||||
|
||||
SQL Schema considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -387,18 +275,9 @@ column and cascading on delete.
|
||||
|
||||
Overrides
|
||||
---------
|
||||
|
||||
Overrides can only be applied to entities that extend a mapped superclass or
|
||||
use traits. They are used to override a mapping for an entity field or
|
||||
relationship defined in that mapped superclass or trait.
|
||||
|
||||
It is not supported to use overrides in entity inheritance scenarios.
|
||||
|
||||
.. note::
|
||||
|
||||
When using traits, make sure not to miss the warnings given in the
|
||||
:doc:`Limitations and Known Issues </reference/limitations-and-known-issues>` chapter.
|
||||
|
||||
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.
|
||||
|
||||
Association Override
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -411,52 +290,7 @@ Example:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. 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
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
@@ -474,15 +308,14 @@ Example:
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
|
||||
* )
|
||||
* @var Collection<int, Group>
|
||||
*/
|
||||
protected Collection $groups;
|
||||
protected $groups;
|
||||
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
*/
|
||||
protected Address|null $address = null;
|
||||
protected $address;
|
||||
}
|
||||
|
||||
// admin mapping
|
||||
@@ -517,8 +350,7 @@ Example:
|
||||
<many-to-many field="groups" target-entity="Group" inversed-by="users">
|
||||
<cascade>
|
||||
<cascade-persist/>
|
||||
<cascade-merge/>
|
||||
<cascade-detach/>
|
||||
<cascade-refresh/>
|
||||
</cascade>
|
||||
<join-table name="users_groups">
|
||||
<join-columns>
|
||||
@@ -554,59 +386,13 @@ Example:
|
||||
</association-overrides>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
# user mapping
|
||||
MyProject\Model\User:
|
||||
type: mappedSuperclass
|
||||
# other fields mapping
|
||||
manyToOne:
|
||||
address:
|
||||
targetEntity: Address
|
||||
joinColumn:
|
||||
name: address_id
|
||||
referencedColumnName: id
|
||||
cascade: [ persist, merge ]
|
||||
manyToMany:
|
||||
groups:
|
||||
targetEntity: Group
|
||||
joinTable:
|
||||
name: users_groups
|
||||
joinColumns:
|
||||
user_id:
|
||||
referencedColumnName: id
|
||||
inverseJoinColumns:
|
||||
group_id:
|
||||
referencedColumnName: id
|
||||
cascade: [ persist, merge, detach ]
|
||||
|
||||
# admin mapping
|
||||
MyProject\Model\Admin:
|
||||
type: entity
|
||||
associationOverride:
|
||||
address:
|
||||
joinColumn:
|
||||
adminaddress_id:
|
||||
name: adminaddress_id
|
||||
referencedColumnName: id
|
||||
groups:
|
||||
joinTable:
|
||||
name: users_admingroups
|
||||
joinColumns:
|
||||
adminuser_id:
|
||||
referencedColumnName: id
|
||||
inverseJoinColumns:
|
||||
admingroup_id:
|
||||
referencedColumnName: id
|
||||
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "association override" specifies the overrides based on the property
|
||||
name.
|
||||
- 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 "association override" specifies the overrides base on the property name.
|
||||
- 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 fetch to modify the fetch strategy of the extended entity.
|
||||
|
||||
@@ -618,51 +404,7 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. 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
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// user mapping
|
||||
@@ -673,10 +415,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 int|null $id = null;
|
||||
protected $id;
|
||||
|
||||
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
|
||||
protected string $name;
|
||||
protected $name;
|
||||
|
||||
// other fields mapping
|
||||
}
|
||||
@@ -719,7 +461,7 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
<many-to-one field="address" target-entity="Address">
|
||||
<cascade>
|
||||
<cascade-persist/>
|
||||
<cascade-merge/>
|
||||
<cascade-refresh/>
|
||||
</cascade>
|
||||
<join-column name="address_id" referenced-column-name="id"/>
|
||||
</many-to-one>
|
||||
@@ -740,48 +482,12 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
</attribute-overrides>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
# user mapping
|
||||
MyProject\Model\User:
|
||||
type: mappedSuperclass
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
column: user_id
|
||||
length: 150
|
||||
generator:
|
||||
strategy: AUTO
|
||||
fields:
|
||||
name:
|
||||
type: string
|
||||
column: user_name
|
||||
length: 250
|
||||
nullable: true
|
||||
unique: false
|
||||
#other fields mapping
|
||||
|
||||
|
||||
# guest mapping
|
||||
MyProject\Model\Guest:
|
||||
type: entity
|
||||
attributeOverride:
|
||||
id:
|
||||
column: guest_id
|
||||
type: integer
|
||||
length: 140
|
||||
name:
|
||||
column: guest_name
|
||||
type: string
|
||||
length: 240
|
||||
nullable: false
|
||||
unique: true
|
||||
|
||||
Things to note:
|
||||
|
||||
- The "attribute override" specifies the overrides based on the property name.
|
||||
- The column type *cannot* be changed. If the column type is not equal, you get a ``MappingException``.
|
||||
- The override can redefine all the attributes except the type.
|
||||
- The "attribute override" specifies the overrides base on the property name.
|
||||
- The column type *CANNOT* be changed. If the column type is not equal you get a ``MappingException``
|
||||
- The override can redefine all the columns except the type.
|
||||
|
||||
Query the Type
|
||||
--------------
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
:orphan:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
The installation chapter has moved to :doc:`Installation and Configuration </reference/configuration>`.
|
||||
The installation chapter has moved to :doc:`Installation and Configuration <reference/configuration>`_.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Limitations and Known Issues
|
||||
============================
|
||||
|
||||
We try to make using Doctrine ORM a very pleasant experience.
|
||||
We try to make using Doctrine2 a very pleasant experience.
|
||||
Therefore we think it is very important to be honest about the
|
||||
current limitations to our users. Much like every other piece of
|
||||
software the ORM is not perfect and far from feature complete.
|
||||
software Doctrine2 is not perfect and far from feature complete.
|
||||
This section should give you an overview of current limitations of
|
||||
Doctrine ORM as well as critical known issues that you should know
|
||||
Doctrine 2 as well as critical known issues that you should know
|
||||
about.
|
||||
|
||||
Current Limitations
|
||||
@@ -39,7 +39,7 @@ possible either. See the following example:
|
||||
name VARCHAR,
|
||||
PRIMARY KEY(id)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE product_attributes (
|
||||
product_id INTEGER,
|
||||
attribute_name VARCHAR,
|
||||
@@ -65,15 +65,6 @@ Where the ``attribute_name`` column contains the key and
|
||||
The feature request for persistence of primitive value arrays
|
||||
`is described in the DDC-298 ticket <https://github.com/doctrine/orm/issues/3743>`_.
|
||||
|
||||
Cascade Merge with Bi-directional Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are two bugs now that concern the use of cascade merge in combination with bi-directional associations.
|
||||
Make sure to study the behavior of cascade merge if you are using it:
|
||||
|
||||
- `DDC-875 <https://github.com/doctrine/orm/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <https://github.com/doctrine/orm/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
|
||||
Custom Persisters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -107,17 +98,17 @@ to the same entity.
|
||||
Behaviors
|
||||
~~~~~~~~~
|
||||
|
||||
Doctrine ORM will **never** include a behavior system like Doctrine 1
|
||||
Doctrine 2 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 <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>`_
|
||||
- `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>`_
|
||||
|
||||
Doctrine ORM has enough hooks and extension points so that **you** can
|
||||
Doctrine 2 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,75 +117,13 @@ Nested Set
|
||||
~~~~~~~~~~
|
||||
|
||||
NestedSet was offered as a behavior in Doctrine 1 and will not be
|
||||
included in the core of Doctrine ORM. However there are already two
|
||||
included in the core of Doctrine 2. However there are already two
|
||||
extensions out there that offer support for Nested Set with
|
||||
ORM:
|
||||
Doctrine 2:
|
||||
|
||||
- `Doctrine2 Hierarchical-Structural Behavior <https://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
|
||||
- `Doctrine2 NestedSet <https://github.com/blt04/doctrine2-nestedset>`_
|
||||
|
||||
Using Traits in Entity Classes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The use of traits in entity or mapped superclasses, at least when they
|
||||
include mapping configuration or mapped fields, is currently not
|
||||
endorsed by the Doctrine project. The reasons for this are as follows.
|
||||
|
||||
Traits were added in PHP 5.4 more than 10 years ago, but at the same time
|
||||
more than two years after the initial Doctrine 2 release and the time where
|
||||
core components were designed.
|
||||
|
||||
In fact, this documentation mentions traits only in the context of
|
||||
:doc:`overriding field association mappings in subclasses </tutorials/override-field-association-mappings-in-subclasses>`.
|
||||
Coverage of traits in test cases is practically nonexistent.
|
||||
|
||||
Thus, you should at least be aware that when using traits in your entity and
|
||||
mapped superclasses, you will be in uncharted terrain.
|
||||
|
||||
.. warning::
|
||||
|
||||
There be dragons.
|
||||
|
||||
From a more technical point of view, traits basically work at the language level
|
||||
as if the code contained in them had been copied into the class where the trait
|
||||
is used, and even private fields are accessible by the using class. In addition to
|
||||
that, some precedence and conflict resolution rules apply.
|
||||
|
||||
When it comes to loading mapping configuration, the annotation and attribute drivers
|
||||
rely on PHP reflection to inspect class properties including their docblocks.
|
||||
As long as the results are consistent with what a solution *without* traits would
|
||||
have produced, this is probably fine.
|
||||
|
||||
However, to mention known limitations, it is currently not possible to use "class"
|
||||
level `annotations <https://github.com/doctrine/orm/pull/1517>`_ or
|
||||
`attributes <https://github.com/doctrine/orm/issues/8868>`_ on traits, and attempts to
|
||||
improve parser support for traits as `here <https://github.com/doctrine/annotations/pull/102>`_
|
||||
or `there <https://github.com/doctrine/annotations/pull/63>`_ have been abandoned
|
||||
due to complexity.
|
||||
|
||||
XML mapping configuration probably needs to completely re-configure or otherwise
|
||||
copy-and-paste configuration for fields used from traits.
|
||||
|
||||
Mapping multiple private fields of the same name
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When two classes, say a mapped superclass and an entity inheriting from it,
|
||||
both contain a ``private`` field of the same name, this will lead to a ``MappingException``.
|
||||
|
||||
Since the fields are ``private``, both are technically separate and can contain
|
||||
different values at the same time. However, the ``ClassMetadata`` configuration used
|
||||
internally by the ORM currently refers to fields by their name only, without taking the
|
||||
class containing the field into consideration. This makes it impossible to keep separate
|
||||
mapping configuration for both fields.
|
||||
|
||||
Apart from that, in the case of having multiple ``private`` fields of the same name within
|
||||
the class hierarchy an entity or mapped superclass, the Collection filtering API cannot determine
|
||||
the right field to look at. Even if only one of these fields is actually mapped, the ``ArrayCollection``
|
||||
will not be able to tell, since it does not have access to any metadata.
|
||||
|
||||
Thus, to avoid problems in this regard, it is best to avoid having multiple ``private`` fields of the
|
||||
same name in class hierarchies containing entity and mapped superclasses.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
@@ -210,10 +139,9 @@ Identifier Quoting and Legacy Databases
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For compatibility reasons between all the supported vendors and
|
||||
edge case problems Doctrine ORM does **NOT** do automatic identifier
|
||||
edge case problems Doctrine 2 does **NOT** do automatic identifier
|
||||
quoting. This can lead to problems when trying to get
|
||||
legacy-databases to work with Doctrine ORM.
|
||||
|
||||
legacy-databases to work with Doctrine 2.
|
||||
|
||||
- You can quote column-names as described in the
|
||||
:doc:`Basic-Mapping <basic-mapping>` section.
|
||||
|
||||
@@ -11,17 +11,9 @@ Core Metadata Drivers
|
||||
Doctrine provides a few different ways for you to specify your
|
||||
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)
|
||||
|
||||
They will be removed in 3.0, make sure to avoid them.
|
||||
- **PHP Code in files or static functions** (PhpDriver)
|
||||
|
||||
Something important to note about the above drivers is they are all
|
||||
an intermediate step to the same end result. The mapping
|
||||
@@ -43,11 +35,8 @@ an entity.
|
||||
<?php
|
||||
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
|
||||
|
||||
|
||||
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
|
||||
If you want to use one of the included core metadata drivers you
|
||||
just need to configure it. All the drivers are in the
|
||||
``Doctrine\ORM\Mapping\Driver`` namespace:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -61,85 +50,71 @@ 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 ``MappingDriver`` interface:
|
||||
implements the ``Driver`` interface:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
namespace Doctrine\ORM\Mapping\Driver;
|
||||
|
||||
declare(strict_types=1);
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
|
||||
namespace Doctrine\Persistence\Mapping\Driver;
|
||||
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
|
||||
/**
|
||||
* Contract for metadata drivers.
|
||||
*/
|
||||
interface MappingDriver
|
||||
interface Driver
|
||||
{
|
||||
/**
|
||||
* Loads the metadata for the specified class into the provided container.
|
||||
*
|
||||
* @param class-string<T> $className
|
||||
* @param ClassMetadata<T> $metadata
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @template T of object
|
||||
* @param string $className
|
||||
* @param ClassMetadata $metadata
|
||||
*/
|
||||
public function loadMetadataForClass(string $className, ClassMetadata $metadata);
|
||||
function loadMetadataForClass($className, ClassMetadata $metadata);
|
||||
|
||||
/**
|
||||
* Gets the names of all mapped classes known to this driver.
|
||||
*
|
||||
* @return list<class-string> The names of all mapped classes known to this driver.
|
||||
* @return array The names of all mapped classes known to this driver.
|
||||
*/
|
||||
public function getAllClassNames();
|
||||
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.
|
||||
* 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 class-string $className
|
||||
*
|
||||
* @return bool
|
||||
* @param string $className
|
||||
* @return boolean
|
||||
*/
|
||||
public function isTransient(string $className);
|
||||
function isTransient($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 ``FileDriver`` implementation for you to extend from:
|
||||
the ``AbstractFileDriver`` implementation for you to extend from:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Persistence\Mapping\Driver\FileDriver;
|
||||
|
||||
class MyMetadataDriver extends FileDriver
|
||||
class MyMetadataDriver extends AbstractFileDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $_fileExtension = '.dcm.ext';
|
||||
protected $fileExtension = '.dcm.ext';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMetadataForClass($className, ClassMetadata $metadata)
|
||||
{
|
||||
$data = $this->_loadMappingFile($file);
|
||||
$data = $this->loadMappingFile($file);
|
||||
|
||||
// populate ClassMetadata instance from $data
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _loadMappingFile($file)
|
||||
protected function loadMappingFile($file)
|
||||
{
|
||||
// parse contents of $file and return php data structure
|
||||
}
|
||||
@@ -147,13 +122,13 @@ the ``FileDriver`` implementation for you to extend from:
|
||||
|
||||
.. note::
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Now you can use your ``MyMetadataDriver`` implementation by setting
|
||||
it with the ``setMetadataDriverImpl()`` method:
|
||||
@@ -168,13 +143,18 @@ ClassMetadata
|
||||
-------------
|
||||
|
||||
The last piece you need to know and understand about metadata in
|
||||
Doctrine ORM is the API of the ``ClassMetadata`` classes. You need to
|
||||
Doctrine 2 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 ``ClassMetadata`` 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.
|
||||
|
||||
You can read more about the API of the ``ClassMetadata`` classes in
|
||||
the PHP Mapping chapter.
|
||||
@@ -203,3 +183,4 @@ iterate over them:
|
||||
foreach ($class->fieldMappings as $fieldMapping) {
|
||||
echo $fieldMapping['fieldName'] . "\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
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
|
||||
column or table names when the column or table name is not given. 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` attribute.
|
||||
|
||||
Configuring a naming strategy
|
||||
-----------------------------
|
||||
The default strategy used by Doctrine is quite minimal.
|
||||
@@ -104,7 +102,6 @@ achieve such standards by implementing a naming strategy.
|
||||
|
||||
You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrategy``.
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
@@ -80,7 +80,9 @@ with inheritance hierarchies.
|
||||
|
||||
The builder extends the ``ResultSetMapping`` class and as such has all the functionality of it as well.
|
||||
|
||||
The ``SELECT`` clause can be generated
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Starting with Doctrine ORM 2.4 you can generate the ``SELECT`` clause
|
||||
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
|
||||
@@ -96,7 +98,6 @@ a mapping from DQL alias (key) to SQL alias (value)
|
||||
));
|
||||
$sql = "SELECT " . $selectClause . " FROM users t1 JOIN groups t2 ON t1.group_id = t2.id";
|
||||
|
||||
|
||||
The ResultSetMapping
|
||||
--------------------
|
||||
|
||||
@@ -104,7 +105,6 @@ Understanding the ``ResultSetMapping`` is the key to using a
|
||||
``NativeQuery``. A Doctrine result can contain the following
|
||||
components:
|
||||
|
||||
|
||||
- Entity results. These represent root result elements.
|
||||
- Joined entity results. These represent joined entities in
|
||||
associations of root entity results.
|
||||
@@ -130,7 +130,6 @@ components:
|
||||
``ResultSetMapping`` that describes how the results should be
|
||||
processed by the hydration routines.
|
||||
|
||||
|
||||
We will now look at each of the result types that can appear in a
|
||||
ResultSetMapping in detail.
|
||||
|
||||
@@ -250,40 +249,6 @@ The first parameter is the name of the column in the SQL result set
|
||||
and the second parameter is the result alias under which the value
|
||||
of the column will be placed in the transformed Doctrine result.
|
||||
|
||||
Special case: DTOs
|
||||
...................
|
||||
|
||||
You can also use ``ResultSetMapping`` to map the results of a native SQL
|
||||
query to a DTO (Data Transfer Object). This is done by adding scalar
|
||||
results for each argument of the DTO's constructor, then filling the
|
||||
``newObjectMappings`` property of the ``ResultSetMapping`` with
|
||||
information about where to map each scalar result:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
$rsm = new ResultSetMapping();
|
||||
$rsm->addScalarResult('name', 1, 'string');
|
||||
$rsm->addScalarResult('email', 2, 'string');
|
||||
$rsm->addScalarResult('city', 3, 'string');
|
||||
$rsm->newObjectMappings['name'] = [
|
||||
'className' => CmsUserDTO::class,
|
||||
'objIndex' => 0, // a result can contain many DTOs, this is the index of the DTO to map to
|
||||
'argIndex' => 0, // each scalar result can be mapped to a different argument of the DTO constructor
|
||||
];
|
||||
$rsm->newObjectMappings['email'] = [
|
||||
'className' => CmsUserDTO::class,
|
||||
'objIndex' => 0,
|
||||
'argIndex' => 1,
|
||||
];
|
||||
$rsm->newObjectMappings['city'] = [
|
||||
'className' => CmsUserDTO::class,
|
||||
'objIndex' => 0,
|
||||
'argIndex' => 2,
|
||||
];
|
||||
|
||||
|
||||
Meta results
|
||||
~~~~~~~~~~~~
|
||||
|
||||
@@ -355,7 +320,7 @@ entity.
|
||||
$rsm->addFieldResult('u', 'id', 'id');
|
||||
$rsm->addFieldResult('u', 'name', 'name');
|
||||
|
||||
$query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
|
||||
$query = $this->em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
@@ -391,7 +356,7 @@ thus owns the foreign key.
|
||||
$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 = $this->em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
@@ -422,7 +387,7 @@ associations that are lazy.
|
||||
|
||||
$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 = $this->em->createNativeQuery($sql, $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
@@ -455,7 +420,7 @@ to map the hierarchy (both use a discriminator column).
|
||||
$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 = $this->em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm);
|
||||
$query->setParameter(1, 'romanb');
|
||||
|
||||
$users = $query->getResult();
|
||||
@@ -469,17 +434,12 @@ 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
|
||||
using named native query (and sql resultset mappings if is a several resultset mappings).
|
||||
|
||||
Like named query, a named native query can be defined at class level or in a XML or YAML file.
|
||||
|
||||
Like named query, a named native query can be defined at class level or in an XML file.
|
||||
|
||||
A resultSetMapping parameter is defined in @NamedNativeQuery,
|
||||
it represents the name of a defined @SqlResultSetMapping.
|
||||
@@ -574,47 +534,6 @@ it represents the name of a defined @SqlResultSetMapping.
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\User:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
fetchMultipleJoinsEntityResults:
|
||||
name: fetchMultipleJoinsEntityResults
|
||||
resultSetMapping: mappingMultipleJoinsEntityResults
|
||||
query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username
|
||||
sqlResultSetMappings:
|
||||
mappingMultipleJoinsEntityResults:
|
||||
name: mappingMultipleJoinsEntityResults
|
||||
columnResult:
|
||||
0:
|
||||
name: numphones
|
||||
entityResult:
|
||||
0:
|
||||
entityClass: __CLASS__
|
||||
fieldResult:
|
||||
0:
|
||||
name: id
|
||||
column: u_id
|
||||
1:
|
||||
name: name
|
||||
column: u_name
|
||||
2:
|
||||
name: status
|
||||
column: u_status
|
||||
1:
|
||||
entityClass: Address
|
||||
fieldResult:
|
||||
0:
|
||||
name: id
|
||||
column: a_id
|
||||
1:
|
||||
name: zip
|
||||
column: a_zip
|
||||
2:
|
||||
name: country
|
||||
column: a_country
|
||||
|
||||
|
||||
Things to note:
|
||||
- The resultset mapping declares the entities retrieved by this native query.
|
||||
@@ -625,7 +544,6 @@ Things to note:
|
||||
column name as the one declared on the class property.
|
||||
- ``__CLASS__`` is an alias for the mapped class
|
||||
|
||||
|
||||
In the above example,
|
||||
the ``fetchJoinedAddress`` named query use the joinMapping result set mapping.
|
||||
This mapping returns 2 entities, User and Address, each property is declared and associated to a column name,
|
||||
@@ -691,21 +609,6 @@ Let's now see an implicit declaration of the property / column.
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Address:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
findAll:
|
||||
resultSetMapping: mappingFindAll
|
||||
query: SELECT * FROM addresses
|
||||
sqlResultSetMappings:
|
||||
mappingFindAll:
|
||||
name: mappingFindAll
|
||||
entityResult:
|
||||
address:
|
||||
entityClass: Address
|
||||
|
||||
|
||||
In this example, we only describe the entity member of the result set mapping.
|
||||
The property / column mappings is done using the entity mapping values.
|
||||
@@ -715,7 +618,6 @@ a @FieldResult element should be used for each foreign key column.
|
||||
The @FieldResult name is composed of the property name for the relationship,
|
||||
followed by a dot ("."), followed by the name or the field or property of the primary key.
|
||||
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
@@ -794,41 +696,6 @@ followed by a dot ("."), followed by the name or the field or property of the pr
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\User:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
fetchJoinedAddress:
|
||||
name: fetchJoinedAddress
|
||||
resultSetMapping: mappingJoinedAddress
|
||||
query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ?
|
||||
sqlResultSetMappings:
|
||||
mappingJoinedAddress:
|
||||
entityResult:
|
||||
0:
|
||||
entityClass: __CLASS__
|
||||
fieldResult:
|
||||
0:
|
||||
name: id
|
||||
1:
|
||||
name: name
|
||||
2:
|
||||
name: status
|
||||
3:
|
||||
name: address.id
|
||||
column: a_id
|
||||
4:
|
||||
name: address.zip
|
||||
column: a_zip
|
||||
5:
|
||||
name: address.city
|
||||
column: a_city
|
||||
6:
|
||||
name: address.country
|
||||
column: a_country
|
||||
|
||||
|
||||
|
||||
If you retrieve a single entity and if you use the default mapping,
|
||||
you can use the resultClass attribute instead of resultSetMapping:
|
||||
@@ -864,16 +731,6 @@ you can use the resultClass attribute instead of resultSetMapping:
|
||||
</named-native-queries>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Address:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
findAll:
|
||||
name: findAll
|
||||
resultClass: Address
|
||||
query: SELECT * FROM addresses
|
||||
|
||||
|
||||
In some of your native queries, you'll have to return scalar values,
|
||||
for example when building report queries.
|
||||
@@ -924,18 +781,3 @@ You actually can even mix, entities and scalar returns in the same native query
|
||||
</sql-result-set-mappings>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
.. code-block:: yaml
|
||||
|
||||
MyProject\Model\Address:
|
||||
type: entity
|
||||
namedNativeQueries:
|
||||
count:
|
||||
name: count
|
||||
resultSetMapping: mappingCount
|
||||
query: SELECT COUNT(*) AS count FROM addresses
|
||||
sqlResultSetMappings:
|
||||
mappingCount:
|
||||
name: mappingCount
|
||||
columnResult:
|
||||
count:
|
||||
name: count
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
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
|
||||
@@ -31,7 +23,6 @@ of Doctrine2 to this problem is.
|
||||
to a fully-loaded object by calling ``EntityManager#refresh()``
|
||||
or a DQL query with the refresh flag.
|
||||
|
||||
|
||||
What is the problem?
|
||||
--------------------
|
||||
|
||||
@@ -95,4 +86,3 @@ Mainly for optimization purposes, but be careful of premature
|
||||
optimization as partial objects lead to potentially more fragile
|
||||
code.
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PHP Mapping
|
||||
===========
|
||||
|
||||
Doctrine ORM also allows you to provide the ORM metadata in the form
|
||||
Doctrine 2 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,11 +9,6 @@ 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``:
|
||||
@@ -90,14 +85,12 @@ 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 attributes or
|
||||
annotations. For this you just need to use the ``StaticPHPDriver``:
|
||||
and mapping information together but don't want to use 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);
|
||||
|
||||
@@ -167,7 +160,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, $columnDefinition = null, $enumType = null, $options = [])``
|
||||
- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)``
|
||||
- ``addDiscriminatorMapClass($name, $class)``
|
||||
- ``setChangeTrackingPolicyDeferredExplicit()``
|
||||
- ``setChangeTrackingPolicyNotify()``
|
||||
@@ -188,16 +181,28 @@ It also has several methods that create builders (which are necessary for advanc
|
||||
- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance
|
||||
|
||||
ClassMetadata API
|
||||
-----------------
|
||||
---------------------
|
||||
|
||||
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.
|
||||
The ``ClassMetadata`` 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.
|
||||
|
||||
Internal
|
||||
~~~~~~~~
|
||||
|
||||
- ``getReflectionClass()``
|
||||
- ``getReflectionProperties()``
|
||||
- ``getReflectionProperty($name)``
|
||||
- ``getSingleIdReflectionProperty()``
|
||||
- ``getIdentifierValues($entity)``
|
||||
- ``assignIdentifier($entity, $id)``
|
||||
- ``setFieldValue($entity, $field, $value)``
|
||||
- ``getFieldValue($entity, $field)``
|
||||
|
||||
General Setters
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``setTableName($tableName)``
|
||||
- ``setPrimaryTable(array $primaryTableDefinition)``
|
||||
- ``setCustomRepositoryClass($repositoryClassName)``
|
||||
@@ -210,7 +215,6 @@ General Setters
|
||||
Inheritance Setters
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``setInheritanceType($type)``
|
||||
- ``setSubclasses(array $subclasses)``
|
||||
- ``setParentClasses(array $classNames)``
|
||||
@@ -220,24 +224,18 @@ Inheritance Setters
|
||||
Field Mapping Setters
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``mapField(array $mapping)``
|
||||
- ``mapOneToOne(array $mapping)``
|
||||
- ``mapOneToMany(array $mapping)``
|
||||
- ``mapManyToOne(array $mapping)``
|
||||
- ``mapManyToMany(array $mapping)``
|
||||
- ``addProperty(Property $property)``
|
||||
- ``addAssociation(AssociationMetadata $property)``
|
||||
|
||||
Lifecycle Callback Setters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``addLifecycleCallback($callback, $event)``
|
||||
- ``setLifecycleCallbacks(array $callbacks)``
|
||||
|
||||
Versioning Setters
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``setVersionMapping(array &$mapping)``
|
||||
- ``setVersioned($bool)``
|
||||
- ``setVersionField()``
|
||||
@@ -245,7 +243,6 @@ Versioning Setters
|
||||
General Getters
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``getTableName()``
|
||||
- ``getSchemaName()``
|
||||
- ``getTemporaryIdTableName()``
|
||||
@@ -253,14 +250,8 @@ General Getters
|
||||
Identifier Getters
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``getIdentifierColumnNames()``
|
||||
- ``usesIdGenerator()``
|
||||
- ``isIdentifier($fieldName)``
|
||||
- ``isIdGeneratorIdentity()``
|
||||
- ``isIdGeneratorSequence()``
|
||||
- ``isIdGeneratorTable()``
|
||||
- ``isIdentifierNatural()``
|
||||
- ``getIdentifierFieldNames()``
|
||||
- ``getSingleIdentifierFieldName()``
|
||||
- ``getSingleIdentifierColumnName()``
|
||||
@@ -268,35 +259,18 @@ Identifier Getters
|
||||
Inheritance Getters
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``isInheritanceTypeNone()``
|
||||
- ``isInheritanceTypeJoined()``
|
||||
- ``isInheritanceTypeSingleTable()``
|
||||
- ``isInheritanceTypeTablePerClass()``
|
||||
- ``isInheritedField($fieldName)``
|
||||
- ``isInheritedAssociation($fieldName)``
|
||||
|
||||
Change Tracking Getters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``isChangeTrackingDeferredExplicit()``
|
||||
- ``isChangeTrackingDeferredImplicit()``
|
||||
- ``isChangeTrackingNotify()``
|
||||
|
||||
Field & Association Getters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``isUniqueField($fieldName)``
|
||||
- ``isNullable($fieldName)``
|
||||
- ``getColumnName($fieldName)``
|
||||
- ``getFieldMapping($fieldName)``
|
||||
- ``getAssociationMapping($fieldName)``
|
||||
- ``getAssociationMappings()``
|
||||
- ``getFieldName($columnName)``
|
||||
- ``hasField($fieldName)``
|
||||
- ``getColumnNames(array $fieldNames = null)``
|
||||
- ``getTypeOfField($fieldName)``
|
||||
- ``getTypeOfColumn($columnName)``
|
||||
- ``hasAssociation($fieldName)``
|
||||
@@ -306,22 +280,6 @@ Field & Association Getters
|
||||
Lifecycle Callback Getters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
- ``hasLifecycleCallbacks($lifecycleEvent)``
|
||||
- ``getLifecycleCallbacks($event)``
|
||||
|
||||
Runtime reflection methods
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These are methods related to runtime reflection for working with the
|
||||
entities themselves.
|
||||
|
||||
|
||||
- ``getReflectionClass()``
|
||||
- ``getReflectionProperties()``
|
||||
- ``getReflectionProperty($name)``
|
||||
- ``getSingleIdReflectionProperty()``
|
||||
- ``getIdentifierValues($entity)``
|
||||
- ``setIdentifierValues($entity, $id)``
|
||||
- ``setFieldValue($entity, $field, $value)``
|
||||
- ``getFieldValue($entity, $field)``
|
||||
|
||||
@@ -9,12 +9,6 @@ programmatically build queries, and also provides a fluent API.
|
||||
This means that you can change between one methodology to the other
|
||||
as you want, or just pick a preferred one.
|
||||
|
||||
.. note::
|
||||
|
||||
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>`.
|
||||
|
||||
Constructing a new QueryBuilder object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -44,7 +38,6 @@ good example is to inspect what type of object the
|
||||
|
||||
There're currently 3 possible return values for ``getType()``:
|
||||
|
||||
|
||||
- ``QueryBuilder::SELECT``, which returns value 0
|
||||
- ``QueryBuilder::DELETE``, returning value 1
|
||||
- ``QueryBuilder::UPDATE``, which returns value 2
|
||||
@@ -72,7 +65,6 @@ performance. Any changes that may affect the generated DQL actually
|
||||
modifies the state of ``QueryBuilder`` to a stage we call
|
||||
STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
|
||||
|
||||
|
||||
- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been
|
||||
altered since last retrieval or nothing were added since its
|
||||
instantiation
|
||||
@@ -82,11 +74,10 @@ STATE\_DIRTY. One ``QueryBuilder`` can be in two different states:
|
||||
Working with QueryBuilder
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
High level API methods
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The most straightforward way to build a dynamic query with the ``QueryBuilder`` is by taking
|
||||
To simplify even more the way you build a query in Doctrine, you can take
|
||||
advantage of Helper methods. For all base code, there is a set of
|
||||
useful methods to simplify a programmer's life. To illustrate how
|
||||
to work with them, here is the same example 6 re-written using
|
||||
@@ -103,9 +94,10 @@ to work with them, here is the same example 6 re-written using
|
||||
->orderBy('u.name', 'ASC');
|
||||
|
||||
``QueryBuilder`` helper methods are considered the standard way to
|
||||
use the ``QueryBuilder``. The ``$qb->expr()->*`` methods can help you
|
||||
build conditional expressions dynamically. Here is a converted example 8 to
|
||||
suggested way to build queries with dynamic conditions:
|
||||
build DQL queries. Although it is supported, using string-based
|
||||
queries should be avoided. You are greatly encouraged to use
|
||||
``$qb->expr()->*`` methods. Here is a converted example 8 to
|
||||
suggested standard way to build queries:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -253,22 +245,7 @@ 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 DBAL
|
||||
``Doctrine\DBAL\ParameterType::*`` or a DBAL Type name for conversion.
|
||||
|
||||
.. note::
|
||||
|
||||
Even though passing DateTime instance is allowed, it impacts performance
|
||||
as by default there is an attempt to load metadata for object, and if it's not found,
|
||||
type is inferred from the original value.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
// prevents attempt to load metadata for date time class, improving performance
|
||||
$qb->setParameter('date', new \DateTimeImmutable(), Types::DATETIME_IMMUTABLE)
|
||||
Doctrine\DBAL\ParameterType::* or a DBAL Type name for conversion.
|
||||
|
||||
If you've got several parameters to bind to your query, you can
|
||||
also use setParameters() instead of setParameter() with the
|
||||
@@ -277,17 +254,10 @@ following syntax:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
|
||||
// $qb instanceof QueryBuilder
|
||||
|
||||
// Query here...
|
||||
$qb->setParameters(new ArrayCollection([
|
||||
new Parameter('1', 'value for ?1'),
|
||||
new Parameter('2', 'value for ?2')
|
||||
]));
|
||||
$qb->setParameters(array(1 => 'value for ?1', 2 => 'value for ?2'));
|
||||
|
||||
Getting already bound parameters is easy - simply use the above
|
||||
mentioned syntax with "getParameter()" or "getParameters()":
|
||||
@@ -361,7 +331,6 @@ a querybuilder instance into a Query object:
|
||||
|
||||
// Execute Query
|
||||
$result = $query->getResult();
|
||||
$iterableResult = $query->toIterable();
|
||||
$single = $query->getSingleResult();
|
||||
$array = $query->getArrayResult();
|
||||
$scalar = $query->getScalarResult();
|
||||
@@ -407,7 +376,6 @@ complete list of supported helper methods available:
|
||||
// Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->...
|
||||
public function orX($x = null); // Returns Expr\OrX instance
|
||||
|
||||
|
||||
/** Comparison objects **/
|
||||
|
||||
// Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1
|
||||
@@ -434,13 +402,6 @@ 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 **/
|
||||
|
||||
// Example - $qb->expr()->prod('u.id', '2') => u.id * 2
|
||||
@@ -455,7 +416,6 @@ complete list of supported helper methods available:
|
||||
// Example - $qb->expr()->quot('u.id', '2') => u.id / 2
|
||||
public function quot($x, $y); // Returns Expr\Math instance
|
||||
|
||||
|
||||
/** Pseudo-function objects **/
|
||||
|
||||
// Example - $qb->expr()->exists($qb2->getDql())
|
||||
@@ -490,7 +450,6 @@ complete list of supported helper methods available:
|
||||
// Example - $qb->expr()->between('u.id', '1', '10')
|
||||
public function between($val, $x, $y); // Returns Expr\Func
|
||||
|
||||
|
||||
/** Function objects **/
|
||||
|
||||
// Example - $qb->expr()->trim('u.firstname')
|
||||
@@ -526,9 +485,6 @@ 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
|
||||
|
||||
@@ -568,7 +524,6 @@ one: ``add()``. This method is responsible of building every piece
|
||||
of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
|
||||
``$append`` (default=false)
|
||||
|
||||
|
||||
- ``$dqlPartName``: Where the ``$dqlPart`` should be placed.
|
||||
Possible values: select, from, where, groupBy, having, orderBy
|
||||
- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts
|
||||
@@ -578,6 +533,8 @@ of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and
|
||||
not (no effect on the ``where`` and ``having`` DQL query parts,
|
||||
which always override all previously defined items)
|
||||
|
||||
-
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
@@ -611,3 +568,4 @@ same query of example 6 written using
|
||||
->add('from', new Expr\From('User', 'u'))
|
||||
->add('where', new Expr\Comparison('u.id', '=', '?1'))
|
||||
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ There are some flavors of caching available, but is better to cache read-only da
|
||||
Be aware that caches are not aware of changes made to the persistent store by another application.
|
||||
They can, however, be configured to regularly expire cached data.
|
||||
|
||||
|
||||
Caching Regions
|
||||
---------------
|
||||
|
||||
@@ -31,31 +30,30 @@ 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.
|
||||
A 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
|
||||
|
||||
@@ -66,32 +64,35 @@ A query region might be something like:
|
||||
'region_name:query_3_hash' => ['list' => [2, 4]]
|
||||
];
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The following data structures represents now the cache will looks like, this is not actual cached data.
|
||||
|
||||
|
||||
.. _reference-second-level-cache-regions:
|
||||
|
||||
Cache Regions
|
||||
-------------
|
||||
|
||||
``Doctrine\ORM\Cache\Region\DefaultRegion`` is the default implementation.
|
||||
``Doctrine\ORM\Cache\Region\DefaultRegion`` It's 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``
|
||||
define contracts that should be implemented by a cache provider.
|
||||
Defines 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.
|
||||
|
||||
If you want to support locking for ``READ_WRITE`` strategies you should implement ``ConcurrentRegion``; ``CacheRegion`` otherwise.
|
||||
|
||||
|
||||
Cache region
|
||||
~~~~~~~~~~~~
|
||||
|
||||
``Doctrine\ORM\Cache\Region`` defines a contract for accessing a particular
|
||||
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/latest/Doctrine/ORM/Cache/Region.html>`_.
|
||||
|
||||
Concurrent cache region
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -101,7 +102,11 @@ 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 a contract for concurrently managed data 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>`_.
|
||||
|
||||
Timestamp region
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -110,6 +115,8 @@ 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
|
||||
@@ -127,52 +134,49 @@ 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``
|
||||
|
||||
* Read Write cache employs locks before update/delete.
|
||||
* Use if data needs to be updated.
|
||||
* Slowest strategy.
|
||||
* To use it the cache region implementation must support locking.
|
||||
|
||||
* To use it a the cache region implementation must support locking.
|
||||
|
||||
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
|
||||
-------------
|
||||
Doctrine allows you to specify configurations and some points of extension for the second-level-cache
|
||||
|
||||
|
||||
Enable Second Level Cache
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To enable the second-level-cache, you should provide a cache factory.
|
||||
``Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
|
||||
``\Doctrine\ORM\Cache\DefaultCacheFactory`` is the default implementation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @var \Doctrine\ORM\Cache\RegionsConfiguration $cacheConfig */
|
||||
/** @var \Psr\Cache\CacheItemPoolInterface $cache */
|
||||
/** @var \Doctrine\Common\Cache\Cache $cache */
|
||||
/** @var \Doctrine\ORM\Configuration $config */
|
||||
|
||||
$factory = new \Doctrine\ORM\Cache\DefaultCacheFactory($cacheConfig, $cache);
|
||||
@@ -184,24 +188,20 @@ To enable the second-level-cache, you should provide a cache factory.
|
||||
$config->getSecondLevelCacheConfiguration()
|
||||
->setCacheFactory($factory);
|
||||
|
||||
|
||||
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``
|
||||
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
|
||||
* ``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>`_.
|
||||
|
||||
Region Lifetime
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -218,15 +218,14 @@ To specify a default lifetime for all regions or specify a different lifetime fo
|
||||
$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
|
||||
|
||||
@@ -239,7 +238,6 @@ By providing a cache logger you should be able to get information about all cach
|
||||
$config->getSecondLevelCacheConfiguration()
|
||||
->setCacheLogger($logger);
|
||||
|
||||
|
||||
// Collect cache statistics
|
||||
|
||||
// Get the number of entries successfully retrieved from a specific region.
|
||||
@@ -260,61 +258,41 @@ 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 the information you want.
|
||||
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>`_.
|
||||
|
||||
|
||||
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`` is an 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`` Optional value that specifies the name of the second level cache region.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. 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
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Entity
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @Cache(usage="READ_ONLY", region="my_entity_region")
|
||||
*/
|
||||
class State
|
||||
class Country
|
||||
{
|
||||
/**
|
||||
* @Id
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected int|null $id = null;
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected string $name;
|
||||
protected $name;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
@@ -322,10 +300,7 @@ level cache region.
|
||||
.. 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="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="Country">
|
||||
<cache usage="READ_ONLY" region="my_entity_region" />
|
||||
<id name="id" type="integer" column="id">
|
||||
@@ -335,61 +310,14 @@ level cache region.
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Country:
|
||||
type: entity
|
||||
cache:
|
||||
usage: READ_ONLY
|
||||
region: my_entity_region
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
id: true
|
||||
generator:
|
||||
strategy: IDENTITY
|
||||
fields:
|
||||
name:
|
||||
type: string
|
||||
|
||||
|
||||
Association cache definition
|
||||
----------------------------
|
||||
The most common use case is to cache entities. But we can also cache relationships.
|
||||
It caches the primary keys of association and cache each element will be cached into its region.
|
||||
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. 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
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
@@ -403,26 +331,25 @@ It caches the primary keys of association and cache each element will be cached
|
||||
* @GeneratedValue
|
||||
* @Column(type="integer")
|
||||
*/
|
||||
protected int|null $id = null;
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* @Column(unique=true)
|
||||
*/
|
||||
protected string $name;
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @ManyToOne(targetEntity="Country")
|
||||
* @JoinColumn(name="country_id", referencedColumnName="id")
|
||||
*/
|
||||
protected Country|null $country;
|
||||
protected $country;
|
||||
|
||||
/**
|
||||
* @Cache("NONSTRICT_READ_WRITE")
|
||||
* @OneToMany(targetEntity="City", mappedBy="state")
|
||||
* @var Collection<int, City>
|
||||
*/
|
||||
protected Collection $cities;
|
||||
protected $cities;
|
||||
|
||||
// other properties and methods
|
||||
}
|
||||
@@ -430,10 +357,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="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
<entity name="State">
|
||||
|
||||
<cache usage="NONSTRICT_READ_WRITE" />
|
||||
@@ -458,41 +382,7 @@ It caches the primary keys of association and cache each element will be cached
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
State:
|
||||
type: entity
|
||||
cache:
|
||||
usage: NONSTRICT_READ_WRITE
|
||||
id:
|
||||
id:
|
||||
type: integer
|
||||
id: true
|
||||
generator:
|
||||
strategy: IDENTITY
|
||||
fields:
|
||||
name:
|
||||
type: string
|
||||
|
||||
manyToOne:
|
||||
state:
|
||||
targetEntity: Country
|
||||
joinColumns:
|
||||
country_id:
|
||||
referencedColumnName: id
|
||||
cache:
|
||||
usage: NONSTRICT_READ_WRITE
|
||||
|
||||
oneToMany:
|
||||
cities:
|
||||
targetEntity:City
|
||||
mappedBy: state
|
||||
cache:
|
||||
usage: NONSTRICT_READ_WRITE
|
||||
|
||||
.. note::
|
||||
|
||||
for this to work, the target entity must also be marked as cacheable.
|
||||
> Note: for this to work, the target entity must also be marked as cacheable.
|
||||
|
||||
Cache usage
|
||||
~~~~~~~~~~~
|
||||
@@ -509,8 +399,8 @@ Basic entity cache
|
||||
|
||||
$country1 = $em->find('Country', 1); // Retrieve item from cache
|
||||
|
||||
$country1->setName('New Name');
|
||||
|
||||
$country1->setName("New Name");
|
||||
|
||||
$em->flush(); // Hit database to update the row and update cache
|
||||
|
||||
$em->clear(); // Clear entity manager
|
||||
@@ -518,7 +408,6 @@ Basic entity cache
|
||||
$country2 = $em->find('Country', 1); // Retrieve item from cache
|
||||
// Notice that $country1 and $country2 are not the same instance.
|
||||
|
||||
|
||||
Association cache
|
||||
|
||||
.. code-block:: php
|
||||
@@ -535,7 +424,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();
|
||||
|
||||
@@ -593,7 +482,7 @@ The query cache stores the results of the query but as identifiers, entity value
|
||||
->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')
|
||||
@@ -616,22 +505,20 @@ The Cache Mode controls how a particular query interacts with the second-level c
|
||||
/** @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(\Doctrine\ORM\Cache::MODE_GET)
|
||||
->setCacheMode(Cache::MODE_GET)
|
||||
->setCacheable(true)
|
||||
->getResult();
|
||||
|
||||
.. note::
|
||||
|
||||
The default query cache mode is ```Cache::MODE_NORMAL```
|
||||
The 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 a special query hint.
|
||||
|
||||
However the cached data could be evicted using the cache API or an special query hint.
|
||||
|
||||
Execute the ``UPDATE`` and invalidate ``all cache entries`` using ``Query::HINT_CACHE_EVICT``
|
||||
|
||||
@@ -639,30 +526,28 @@ 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(\Doctrine\ORM\Query::HINT_CACHE_EVICT, true)
|
||||
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
->setHint(Query::HINT_CACHE_EVICT, true)
|
||||
->execute();
|
||||
|
||||
|
||||
Execute the ``UPDATE`` and invalidate ``all cache entries`` using the cache API
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Execute
|
||||
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
->execute();
|
||||
// Invoke Cache API
|
||||
$em->getCache()->evictEntityRegion('Entity\Country');
|
||||
|
||||
|
||||
Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache API
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Execute
|
||||
$this->_em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
$this->em->createQuery("UPDATE Entity\Country u SET u.name = 'unknown' WHERE u.id = 1")
|
||||
->execute();
|
||||
// Invoke Cache API
|
||||
$em->getCache()->evictEntity('Entity\Country', 1);
|
||||
@@ -671,7 +556,7 @@ Using the repository query cache
|
||||
--------------------------------
|
||||
|
||||
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
|
||||
All persisters use a single timestamp cache region to keep track of the last update for each persister,
|
||||
All persister use a single timestamps cache region keeps 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.
|
||||
|
||||
@@ -690,7 +575,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 is no longer valid, the select goes straight to the database
|
||||
// At this point the query cache key if not logger valid, the select goes straight
|
||||
$entities = $em->getRepository('Entity\Country')->findAll();
|
||||
|
||||
Cache API
|
||||
@@ -726,18 +611,23 @@ 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::class, inversedBy: 'references')]
|
||||
#[JoinColumn(name: 'source_id', referencedColumnName: 'article_id')]
|
||||
private Article|null $source = null;
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity="Article", inversedBy="references")
|
||||
* @JoinColumn(name="source_id", referencedColumnName="article_id")
|
||||
*/
|
||||
private $source;
|
||||
|
||||
#[Id]
|
||||
#[ManyToOne(targetEntity: Article::class, inversedBy: 'references')]
|
||||
#[JoinColumn(name: 'target_id', referencedColumnName: 'article_id')]
|
||||
/**
|
||||
* @Id
|
||||
* @ManyToOne(targetEntity="Article")
|
||||
* @JoinColumn(name="target_id", referencedColumnName="article_id")
|
||||
*/
|
||||
private $target;
|
||||
}
|
||||
|
||||
@@ -750,11 +640,11 @@ For performance reasons the cache API does not extract from composite primary ke
|
||||
$article = $em->find('Article', $article);
|
||||
|
||||
// Supported
|
||||
$id = ['source' => 1, 'target' => 2];
|
||||
$id = array('source' => 1, 'target' => 2);
|
||||
$reference = $em->find('Reference', $id);
|
||||
|
||||
// NOT Supported
|
||||
$id = ['source' => new Article(1), 'target' => new Article(2)];
|
||||
$id = array('source' => new Article(1), 'target' => new Article(2));
|
||||
$reference = $em->find('Reference', $id);
|
||||
|
||||
Distributed environments
|
||||
@@ -767,10 +657,8 @@ should be used in conjunction with distributed caching system such as memcached,
|
||||
Caches should be used with care when using a load-balancer if you don't share the cache.
|
||||
While using APC or any file based cache update occurred in a specific machine would not reflect to the cache in other machines.
|
||||
|
||||
|
||||
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,10 +10,11 @@ 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 <https://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
|
||||
- `DBAL Security Page <http://www.doctrine-project.org/projects/doctrine-dbal/en/current/reference/security.html>`
|
||||
|
||||
If you find a Security bug in Doctrine, please follow our
|
||||
`Security reporting guidelines <https://www.doctrine-project.org/policies/security.html#reporting>`_.
|
||||
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
|
||||
developers and you only.
|
||||
|
||||
User input and Doctrine ORM
|
||||
---------------------------
|
||||
@@ -80,7 +81,6 @@ this is technically impossible. The correct way is:
|
||||
$query = $entityManager->createQuery($dql);
|
||||
$query->setParameter(1, $_GET['status']);
|
||||
|
||||
|
||||
Preventing Mass Assignment Vulnerabilities
|
||||
------------------------------------------
|
||||
|
||||
@@ -97,20 +97,19 @@ entity might look like this:
|
||||
|
||||
<?php
|
||||
|
||||
#[Entity]
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class InsecureEntity
|
||||
{
|
||||
#[Id, Column, GeneratedValue]
|
||||
private int|null $id = null;
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
/** @Column */
|
||||
private $email;
|
||||
/** @Column(type="boolean") */
|
||||
private $isAdmin;
|
||||
|
||||
#[Column]
|
||||
private string $email;
|
||||
|
||||
#[Column]
|
||||
private bool $isAdmin;
|
||||
|
||||
/** @param array<string, mixed> $userInput */
|
||||
public function fromArray(array $userInput): void
|
||||
public function fromArray(array $userInput)
|
||||
{
|
||||
foreach ($userInput as $key => $value) {
|
||||
$this->$key = $value;
|
||||
|
||||
@@ -5,48 +5,83 @@ Doctrine Console
|
||||
----------------
|
||||
|
||||
The Doctrine Console is a Command Line Interface tool for simplifying common
|
||||
administration tasks during the development of a project that uses ORM.
|
||||
administration tasks during the development of a project that uses Doctrine 2.
|
||||
|
||||
For the following examples, we will set up the CLI as ``bin/doctrine``.
|
||||
Take a look at the :doc:`Installation and Configuration <configuration>`
|
||||
chapter for more information how to setup the console command.
|
||||
|
||||
Setting Up the Console
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Whenever the ``doctrine`` command line tool is invoked, it can
|
||||
access all Commands that were registered by a developer. There is no
|
||||
access all Commands that were registered by 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``. You have to inject it into the console application.
|
||||
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.
|
||||
|
||||
Here is an example of a project-specific ``bin/doctrine`` binary.
|
||||
Whenever you invoke the Doctrine binary the current folder is searched for a
|
||||
``cli-config.php`` file. This file contains the project specific configuration:
|
||||
|
||||
.. 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);
|
||||
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider;
|
||||
When dealing with the ORM package, the EntityManagerHelper is
|
||||
required:
|
||||
|
||||
// replace with path to your own project bootstrap file
|
||||
require_once 'bootstrap.php';
|
||||
.. code-block:: php
|
||||
|
||||
// replace with mechanism to retrieve EntityManager in your app
|
||||
$entityManager = GetEntityManager();
|
||||
<?php
|
||||
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em)
|
||||
));
|
||||
$cli->setHelperSet($helperSet);
|
||||
|
||||
$commands = [
|
||||
// If you want to add your own custom console commands,
|
||||
// you can do so here.
|
||||
];
|
||||
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:
|
||||
|
||||
ConsoleRunner::run(
|
||||
new SingleManagerProvider($entityManager),
|
||||
$commands
|
||||
);
|
||||
.. 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)
|
||||
));
|
||||
|
||||
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::
|
||||
|
||||
@@ -54,24 +89,11 @@ Here is an example of a project-specific ``bin/doctrine`` binary.
|
||||
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
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The following Commands are currently available:
|
||||
|
||||
|
||||
- ``help`` Displays help for a command (?)
|
||||
- ``list`` Lists commands
|
||||
- ``dbal:import`` Import SQL file(s) directly to Database.
|
||||
@@ -85,16 +107,10 @@ The following Commands are currently available:
|
||||
cache drivers.
|
||||
- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a
|
||||
Doctrine 2.X schema.
|
||||
- ``orm:convert-mapping`` Convert mapping information between
|
||||
supported formats.
|
||||
- ``orm:ensure-production-settings`` Verify that Doctrine is
|
||||
properly configured for a production environment.
|
||||
- ``orm:generate-entities`` Generate entity classes and method
|
||||
stubs from your mapping information.
|
||||
- ``orm:generate-proxies`` Generates proxy classes for entity
|
||||
classes.
|
||||
- ``orm:generate-repositories`` Generate repository classes from
|
||||
your mapping information.
|
||||
- ``orm:run-dql`` Executes arbitrary DQL directly from the command
|
||||
line.
|
||||
- ``orm:schema-tool:create`` Processes the schema and either
|
||||
@@ -109,12 +125,8 @@ The following Commands are currently available:
|
||||
|
||||
For these commands are also available aliases:
|
||||
|
||||
|
||||
- ``orm:convert:d1-schema`` is alias for ``orm:convert-d1-schema``.
|
||||
- ``orm:convert:mapping`` is alias for ``orm:convert-mapping``.
|
||||
- ``orm:generate:entities`` is alias for ``orm:generate-entities``.
|
||||
- ``orm:generate:proxies`` is alias for ``orm:generate-proxies``.
|
||||
- ``orm:generate:repositories`` is alias for ``orm:generate-repositories``.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -140,7 +152,6 @@ Database Schema Generation
|
||||
they are not related to the current project that is using Doctrine.
|
||||
Please be careful!
|
||||
|
||||
|
||||
To generate your database schema from your Doctrine mapping files
|
||||
you can use the ``SchemaTool`` class or the ``schema-tool`` Console
|
||||
Command.
|
||||
@@ -195,197 +206,52 @@ To create the schema use the ``create`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
$ php doctrine orm:schema-tool:create
|
||||
|
||||
To drop the schema use the ``drop`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php bin/doctrine orm:schema-tool:drop
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
|
||||
If you want to drop and then recreate the schema then use both
|
||||
options:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php bin/doctrine orm:schema-tool:drop
|
||||
$ php bin/doctrine orm:schema-tool:create
|
||||
$ php doctrine orm:schema-tool:drop
|
||||
$ php doctrine orm:schema-tool:create
|
||||
|
||||
As you would think, if you want to update your schema use the
|
||||
``update`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php bin/doctrine orm:schema-tool:update
|
||||
$ php 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 bin/doctrine orm:schema-tool:create --dump-sql
|
||||
$ php doctrine orm:schema-tool:create --dump-sql
|
||||
|
||||
Entity Generation
|
||||
-----------------
|
||||
|
||||
Generate entity classes and method stubs from your mapping information.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ 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
|
||||
in your entities after using this command.
|
||||
|
||||
It is possible to use the EntityGenerator on code that you have already written. It will
|
||||
not be lost. The EntityGenerator will only append new code to your
|
||||
file and will not delete the old code. However this approach may still be prone
|
||||
to error and we suggest you use code repositories such as GIT or SVN to make
|
||||
backups of your code.
|
||||
|
||||
It makes sense to generate the entity code if you are using entities as Data
|
||||
Access Objects only and don't put much additional logic on them. If you are
|
||||
however putting much more logic on the entities you should refrain from using
|
||||
the entity-generator and code your entities manually.
|
||||
Before using the orm:schema-tool commands, remember to configure
|
||||
your cli-config.php properly.
|
||||
|
||||
.. note::
|
||||
|
||||
Even if you specified Inheritance options in your
|
||||
XML or YAML Mapping files the generator cannot generate the base and
|
||||
child classes for you correctly, because it doesn't know which
|
||||
class is supposed to extend which. You have to adjust the entity
|
||||
code manually for inheritance to work!
|
||||
|
||||
|
||||
Convert Mapping Information
|
||||
---------------------------
|
||||
|
||||
Convert mapping information between supported formats.
|
||||
|
||||
This is an **execute one-time** command. It should not be necessary for
|
||||
you to call this method multiple times, especially when using the ``--from-database``
|
||||
flag.
|
||||
|
||||
Converting an existing database schema into mapping files only solves about 70-80%
|
||||
of the necessary mapping information. Additionally the detection from an existing
|
||||
database cannot detect inverse associations, inheritance types,
|
||||
entities with foreign keys as primary keys and many of the
|
||||
semantical operations on associations such as cascade.
|
||||
|
||||
.. note::
|
||||
|
||||
There is no need to convert YAML or XML mapping files to annotations
|
||||
every time you make changes. All mapping drivers are first class citizens
|
||||
in Doctrine 2 and can be used as runtime mapping for the ORM. See the
|
||||
docs on XML and YAML Mapping for an example how to register this metadata
|
||||
drivers as primary mapping source.
|
||||
|
||||
To convert some mapping information between the various supported
|
||||
formats you can use the ``ClassMetadataExporter`` to get exporter
|
||||
instances for the different formats:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
|
||||
|
||||
Once you have a instance you can use it to get an exporter. For
|
||||
example, the yml exporter:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
|
||||
|
||||
Now you can export some ``ClassMetadata`` instances:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$classes = array(
|
||||
$em->getClassMetadata('Entities\User'),
|
||||
$em->getClassMetadata('Entities\Profile')
|
||||
);
|
||||
$exporter->setMetadata($classes);
|
||||
$exporter->export();
|
||||
|
||||
This functionality is also available from the command line to
|
||||
convert your loaded mapping information to another format. The
|
||||
``orm:convert-mapping`` command accepts two arguments, the type to
|
||||
convert to and the path to generate it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ 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 ``ClassMetadata`` instances and generate YAML, XML, etc. from
|
||||
them.
|
||||
|
||||
.. note::
|
||||
|
||||
Reverse Engineering is a **one-time** process that can get you started with a project.
|
||||
Converting an existing database schema into mapping files only detects about 70-80%
|
||||
of the necessary mapping information. Additionally the detection from an existing
|
||||
database cannot detect inverse associations, inheritance types,
|
||||
entities with foreign keys as primary keys and many of the
|
||||
semantical operations on associations such as cascade.
|
||||
|
||||
First you need to retrieve the metadata instances with the
|
||||
``DatabaseDriver``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->setMetadataDriverImpl(
|
||||
new \Doctrine\ORM\Mapping\Driver\DatabaseDriver(
|
||||
$em->getConnection()->getSchemaManager()
|
||||
)
|
||||
);
|
||||
|
||||
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
|
||||
$cmf->setEntityManager($em);
|
||||
$metadata = $cmf->getAllMetadata();
|
||||
|
||||
Now you can get an exporter instance and export the loaded metadata
|
||||
to yml:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
|
||||
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
|
||||
$exporter->setMetadata($metadata);
|
||||
$exporter->export();
|
||||
|
||||
You can also reverse engineer a database using the
|
||||
``orm:convert-mapping`` command:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$ php bin/doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml
|
||||
|
||||
.. note::
|
||||
|
||||
Reverse Engineering is not always working perfectly
|
||||
depending on special cases. It will only detect Many-To-One
|
||||
relations (even if they are One-To-One) and will try to create
|
||||
entities from Many-To-Many tables. It also has problems with naming
|
||||
of foreign keys that have multiple column names. Any Reverse
|
||||
Engineered Database-Schema needs considerable manual work to become
|
||||
a useful domain model.
|
||||
|
||||
When using the Annotation Mapping Driver you have to either setup
|
||||
your autoloader in the cli-config.php correctly to find all the
|
||||
entities, or you can use the second argument of the
|
||||
``EntityManagerHelper`` to specify all the paths of your entities
|
||||
(or mapping files), i.e.
|
||||
``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);``
|
||||
|
||||
Runtime vs Development Mapping Validation
|
||||
-----------------------------------------
|
||||
|
||||
For performance reasons Doctrine ORM has to skip some of the
|
||||
For performance reasons Doctrine 2 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.
|
||||
@@ -396,11 +262,6 @@ 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
|
||||
@@ -432,7 +293,6 @@ number of elements with error messages.
|
||||
prefix backslash. PHP does this with ``get_class()`` or Reflection
|
||||
methods for backwards compatibility reasons.
|
||||
|
||||
|
||||
Adding own commands
|
||||
-------------------
|
||||
|
||||
@@ -478,7 +338,6 @@ defined ones) is possible through the command:
|
||||
new \MyProject\Tools\Console\Commands\OneMoreCommand(),
|
||||
));
|
||||
|
||||
|
||||
Re-use console application
|
||||
--------------------------
|
||||
|
||||
@@ -495,3 +354,4 @@ 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 ORM already takes care of proper
|
||||
For the most part, Doctrine 2 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 ORM also allows (and encourages) you to take over
|
||||
However, Doctrine 2 also allows (and encourages) you to take over
|
||||
and control transaction demarcation yourself.
|
||||
|
||||
These are two ways to deal with transactions when using the
|
||||
@@ -88,7 +88,7 @@ requirement.
|
||||
|
||||
A more convenient alternative for explicit transaction demarcation is the use
|
||||
of provided control abstractions in the form of
|
||||
``Connection#transactional($func)`` and ``EntityManager#wrapInTransaction($func)``.
|
||||
``Connection#transactional($func)`` and ``EntityManager#transactional($func)``.
|
||||
When used, these control abstractions ensure that you never forget to rollback
|
||||
the transaction, in addition to the obvious code reduction. An example that is
|
||||
functionally equivalent to the previously shown code looks as follows:
|
||||
@@ -96,17 +96,8 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// transactional with Connection instance
|
||||
// $conn instanceof Connection
|
||||
$conn->transactional(function($conn) {
|
||||
// ... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
});
|
||||
|
||||
// transactional with EntityManager instance
|
||||
// $em instanceof EntityManager
|
||||
$em->wrapInTransaction(function($em) {
|
||||
$em->transactional(function($em) {
|
||||
// ... do some work
|
||||
$user = new User;
|
||||
$user->setName('George');
|
||||
@@ -116,8 +107,7 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
commit and in case of an exception the ``EntityManager`` gets closed
|
||||
in addition to the transaction rollback.
|
||||
commit.
|
||||
|
||||
.. _transactions-and-concurrency_exception-handling:
|
||||
|
||||
@@ -156,7 +146,7 @@ occurred you should do that with a new ``EntityManager``.
|
||||
Locking Support
|
||||
---------------
|
||||
|
||||
Doctrine ORM offers support for Pessimistic- and Optimistic-locking
|
||||
Doctrine 2 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.
|
||||
@@ -191,25 +181,14 @@ example we'll use an integer.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
#[Version, Column(type: 'integer')]
|
||||
private int $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="integer") */
|
||||
private int $version;
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -221,39 +200,19 @@ example we'll use an integer.
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
fields:
|
||||
version:
|
||||
type: integer
|
||||
version: true
|
||||
|
||||
Alternatively a datetime type can be used (which maps to a SQL
|
||||
timestamp or datetime):
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: attribute
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
#[Version, Column(type: 'datetime')]
|
||||
private DateTime $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: annotation
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="datetime") */
|
||||
private DateTime $version;
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -265,15 +224,6 @@ timestamp or datetime):
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
User:
|
||||
type: entity
|
||||
fields:
|
||||
version:
|
||||
type: datetime
|
||||
version: true
|
||||
|
||||
Version numbers (not timestamps) should however be preferred as
|
||||
they can not potentially conflict in a highly concurrent
|
||||
environment, unlike timestamps where this is a possibility,
|
||||
@@ -391,7 +341,7 @@ And the change headline action (POST Request):
|
||||
Pessimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine ORM supports Pessimistic Locking at the database level. No
|
||||
Doctrine 2 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
|
||||
@@ -400,12 +350,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 ORM will throw an
|
||||
Transaction Demarcation" described above. Doctrine 2 will throw an
|
||||
Exception if you attempt to acquire an pessimistic lock and no
|
||||
transaction is running.
|
||||
|
||||
Doctrine ORM currently supports two pessimistic lock modes:
|
||||
|
||||
Doctrine 2 currently supports two pessimistic lock modes:
|
||||
|
||||
- Pessimistic Write
|
||||
(``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the
|
||||
@@ -414,8 +363,7 @@ Doctrine ORM 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 four different scenarios:
|
||||
|
||||
You can use pessimistic locks in three different scenarios:
|
||||
|
||||
1. Using
|
||||
``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)``
|
||||
@@ -426,10 +374,7 @@ You can use pessimistic locks in four 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)``
|
||||
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
Implementing a TypedFieldMapper
|
||||
===============================
|
||||
|
||||
.. versionadded:: 2.14
|
||||
|
||||
You can specify custom typed field mapping between PHP type and DBAL type using ``Doctrine\ORM\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;
|
||||
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
|
||||
|
||||
$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;
|
||||
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
|
||||
|
||||
$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;
|
||||
use Doctrine\ORM\Mapping\ChainTypedFieldMapper;
|
||||
use Doctrine\ORM\Mapping\DefaultTypedFieldMapper;
|
||||
|
||||
$configuration->setTypedFieldMapper(
|
||||
new ChainTypedFieldMapper(
|
||||
new 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 the ``DefaultTypedFieldMapper`` and does not modify the type once its set prior in the chain.
|
||||
@@ -39,7 +39,7 @@ side of the association and these 2 references both represent the
|
||||
same association but can change independently of one another. Of
|
||||
course, in a correct application the semantics of the bidirectional
|
||||
association are properly maintained by the application developer
|
||||
(that's their responsibility). Doctrine needs to know which of these
|
||||
(that's his responsibility). Doctrine needs to know which of these
|
||||
two in-memory references is the one that should be persisted and
|
||||
which not. This is what the owning/inverse concept is mainly used
|
||||
for.
|
||||
|
||||
@@ -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(): void
|
||||
public function testIdentityMap()
|
||||
{
|
||||
$objectA = $this->entityManager->find('EntityName', 1);
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
@@ -34,15 +34,17 @@ will still end up with the same reference:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function testIdentityMapReference(): void
|
||||
public function testIdentityMapReference()
|
||||
{
|
||||
$objectA = $this->entityManager->getReference('EntityName', 1);
|
||||
// check entity is not initialized
|
||||
$this->assertTrue($this->entityManager->isUninitializedObject($objectA));
|
||||
/** @var EntityName|\ProxyManager\Proxy\GhostObjectInterface $objectA */
|
||||
$objectA = $this->entityManager->getReference(EntityName::class, 1);
|
||||
|
||||
$objectB = $this->entityManager->find('EntityName', 1);
|
||||
self::assertInstanceOf(\ProxyManager\Proxy\GhostObjectInterface::class, $objectA);
|
||||
self::assertFalse($objectA->isProxyInitialized());
|
||||
|
||||
$this->assertSame($objectA, $objectB)
|
||||
$objectB = $this->entityManager->find(EntityName::class, 1);
|
||||
|
||||
self::assertSame($objectA, $objectB)
|
||||
}
|
||||
|
||||
The identity map being indexed by primary keys only allows shortcuts when you
|
||||
@@ -102,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
|
||||
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?".
|
||||
|
||||
@@ -129,16 +131,10 @@ optimize the performance of the Flush Operation:
|
||||
- Temporarily mark entities as read only. If you have a very large UnitOfWork
|
||||
but know that a large set of entities has not changed, just mark them as read
|
||||
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
|
||||
- Flush only a single entity with ``$entityManager->flush($entity)``.
|
||||
- Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
|
||||
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
|
||||
---------------
|
||||
|
||||
@@ -152,15 +148,15 @@ Hydration
|
||||
~~~~~~~~~
|
||||
|
||||
Responsible for creating a final result from a raw database statement and a
|
||||
result-set mapping object. The developer can choose which kind of result they
|
||||
wish to be hydrated. Default result-types include:
|
||||
result-set mapping object. The developer can choose which kind of result he
|
||||
wishes to be hydrated. Default result-types include:
|
||||
|
||||
- SQL to Entities
|
||||
- SQL to structured Arrays
|
||||
- SQL to simple scalar result arrays
|
||||
- SQL to a single result variable
|
||||
|
||||
Hydration to entities and arrays is one of the most complex parts of Doctrine
|
||||
Hydration to entities and arrays is one of most complex parts of Doctrine
|
||||
algorithm-wise. It can build results with for example:
|
||||
|
||||
- Single table selects
|
||||
|
||||
@@ -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]
|
||||
private int|null $id = null;
|
||||
/** @Id @GeneratedValue @Column(type="string") */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
|
||||
*
|
||||
* @var Collection<int, Comment>
|
||||
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
|
||||
* @JoinTable(name="user_favorite_comments")
|
||||
*/
|
||||
#[ManyToMany(targetEntity: Comment::class, inversedBy: 'userFavorites')]
|
||||
#[JoinTable(name: 'user_favorite_comments')]
|
||||
private Collection $favorites;
|
||||
private $favorites;
|
||||
|
||||
/**
|
||||
* Unidirectional - Many users have marked many comments as read
|
||||
*
|
||||
* @var Collection<int, Comment>
|
||||
* @ManyToMany(targetEntity="Comment")
|
||||
* @JoinTable(name="user_read_comments")
|
||||
*/
|
||||
#[ManyToMany(targetEntity: Comment::class)]
|
||||
#[JoinTable(name: 'user_read_comments')]
|
||||
private Collection $commentsRead;
|
||||
private $commentsRead;
|
||||
|
||||
/**
|
||||
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||
*
|
||||
* @var Collection<int, Comment>
|
||||
* @OneToMany(targetEntity="Comment", mappedBy="author")
|
||||
*/
|
||||
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author')]
|
||||
private Collection $commentsAuthored;
|
||||
private $commentsAuthored;
|
||||
|
||||
/** Unidirectional - Many-To-One */
|
||||
#[ManyToOne(targetEntity: Comment::class)]
|
||||
private Comment|null $firstComment = null;
|
||||
/**
|
||||
* Unidirectional - Many-To-One
|
||||
*
|
||||
* @ManyToOne(targetEntity="Comment")
|
||||
*/
|
||||
private $firstComment;
|
||||
}
|
||||
|
||||
#[Entity]
|
||||
/** @Entity */
|
||||
class Comment
|
||||
{
|
||||
#[Id, GeneratedValue, Column]
|
||||
private string $id;
|
||||
/** @Id @GeneratedValue @Column(type="string") */
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
|
||||
*
|
||||
* @var Collection<int, User>
|
||||
* @ManyToMany(targetEntity="User", mappedBy="favorites")
|
||||
*/
|
||||
#[ManyToMany(targetEntity: User::class, mappedBy: 'favorites')]
|
||||
private Collection $userFavorites;
|
||||
private $userFavorites;
|
||||
|
||||
/**
|
||||
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
|
||||
*
|
||||
* @ManyToOne(targetEntity="User", inversedBy="commentsAuthored")
|
||||
*/
|
||||
#[ManyToOne(targetEntity: User::class, inversedBy: 'commentsAuthored')]
|
||||
private User|null $author = null;
|
||||
private $author;
|
||||
}
|
||||
|
||||
This two entities generate the following MySQL Schema (Foreign Key
|
||||
@@ -132,12 +132,11 @@ relations of the ``User``:
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @return Collection<int, Comment> */
|
||||
public function getReadComments(): Collection {
|
||||
public function getReadComments() {
|
||||
return $this->commentsRead;
|
||||
}
|
||||
|
||||
public function setFirstComment(Comment $c): void {
|
||||
public function setFirstComment(Comment $c) {
|
||||
$this->firstComment = $c;
|
||||
}
|
||||
}
|
||||
@@ -173,13 +172,11 @@ fields on both sides:
|
||||
{
|
||||
// ..
|
||||
|
||||
/** @return Collection<int, Comment> */
|
||||
public function getAuthoredComments(): Collection {
|
||||
public function getAuthoredComments() {
|
||||
return $this->commentsAuthored;
|
||||
}
|
||||
|
||||
/** @return Collection<int, Comment> */
|
||||
public function getFavoriteComments(): Collection {
|
||||
public function getFavoriteComments() {
|
||||
return $this->favorites;
|
||||
}
|
||||
}
|
||||
@@ -188,12 +185,11 @@ fields on both sides:
|
||||
{
|
||||
// ...
|
||||
|
||||
/** @return Collection<int, User> */
|
||||
public function getUserFavorites(): Collection {
|
||||
public function getUserFavorites() {
|
||||
return $this->userFavorites;
|
||||
}
|
||||
|
||||
public function setAuthor(User|null $author = null): void {
|
||||
public function setAuthor(User $author = null) {
|
||||
$this->author = $author;
|
||||
}
|
||||
}
|
||||
@@ -266,7 +262,6 @@ where n is the size of the map.
|
||||
can often be used to improve performance by avoiding the loading of
|
||||
the inverse collection.
|
||||
|
||||
|
||||
You can also clear the contents of a whole collection using the
|
||||
``Collections::clear()`` method. You should be aware that using
|
||||
this method can lead to a straight and optimized database delete or
|
||||
@@ -296,12 +291,12 @@ example that encapsulate much of the association management code:
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
public function markCommentRead(Comment $comment): void {
|
||||
public function markCommentRead(Comment $comment) {
|
||||
// Collections implement ArrayAccess
|
||||
$this->commentsRead[] = $comment;
|
||||
}
|
||||
|
||||
public function addComment(Comment $comment): void {
|
||||
public function addComment(Comment $comment) {
|
||||
if (count($this->commentsAuthored) == 0) {
|
||||
$this->setFirstComment($comment);
|
||||
}
|
||||
@@ -309,16 +304,16 @@ example that encapsulate much of the association management code:
|
||||
$comment->setAuthor($this);
|
||||
}
|
||||
|
||||
private function setFirstComment(Comment $c): void {
|
||||
private function setFirstComment(Comment $c) {
|
||||
$this->firstComment = $c;
|
||||
}
|
||||
|
||||
public function addFavorite(Comment $comment): void {
|
||||
public function addFavorite(Comment $comment) {
|
||||
$this->favorites->add($comment);
|
||||
$comment->addUserFavorite($this);
|
||||
}
|
||||
|
||||
public function removeFavorite(Comment $comment): void {
|
||||
public function removeFavorite(Comment $comment) {
|
||||
$this->favorites->removeElement($comment);
|
||||
$comment->removeUserFavorite($this);
|
||||
}
|
||||
@@ -328,11 +323,11 @@ example that encapsulate much of the association management code:
|
||||
{
|
||||
// ..
|
||||
|
||||
public function addUserFavorite(User $user): void {
|
||||
public function addUserFavorite(User $user) {
|
||||
$this->userFavorites[] = $user;
|
||||
}
|
||||
|
||||
public function removeUserFavorite(User $user): void {
|
||||
public function removeUserFavorite(User $user) {
|
||||
$this->userFavorites->removeElement($user);
|
||||
}
|
||||
}
|
||||
@@ -355,13 +350,11 @@ the details inside the classes can be challenging.
|
||||
entity cannot circumvent the logic you implement on your entity for
|
||||
association management. For example:
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User {
|
||||
/** @return array<int, Comment> */
|
||||
public function getReadComments(): array {
|
||||
public function getReadComments() {
|
||||
return $this->commentsRead->toArray();
|
||||
}
|
||||
}
|
||||
@@ -398,7 +391,6 @@ can show the possible caveats you can encounter:
|
||||
|
||||
There are two approaches to handle this problem in your code:
|
||||
|
||||
|
||||
1. Ignore updating the inverse side of bidirectional collections,
|
||||
BUT never read from them in requests that changed their state. In
|
||||
the next request Doctrine hydrates the consistent collection state
|
||||
@@ -412,10 +404,10 @@ There are two approaches to handle this problem in your code:
|
||||
Transitive persistence / Cascade Operations
|
||||
-------------------------------------------
|
||||
|
||||
Doctrine ORM provides a mechanism for transitive persistence through cascading of certain operations.
|
||||
Doctrine 2 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``.
|
||||
``persist``, ``remove``, ``refresh`` or ``all``.
|
||||
|
||||
The main use case for ``cascade: persist`` is to avoid "exposing" associated entities to your PHP application.
|
||||
Continuing with the User-Comment example of this chapter, this is how the creation of a new user and a new
|
||||
@@ -442,10 +434,8 @@ only accessing it through the User entity:
|
||||
// User entity
|
||||
class User
|
||||
{
|
||||
private int $id;
|
||||
|
||||
/** @var Collection<int, Comment> */
|
||||
private Collection $comments;
|
||||
private $id;
|
||||
private $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -471,8 +461,11 @@ If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** Bidirectional - One-To-Many (INVERSE SIDE) */
|
||||
#[OneToMany(targetEntity: Comment::class, mappedBy: 'author', cascade: ['persist', 'remove'])]
|
||||
/**
|
||||
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||
*
|
||||
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
|
||||
*/
|
||||
private $commentsAuthored;
|
||||
// ...
|
||||
}
|
||||
@@ -525,8 +518,6 @@ 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -535,7 +526,6 @@ operation. During each ``flush()`` operation Doctrine detects if there
|
||||
are new entities in any collection and three possible cases can
|
||||
happen:
|
||||
|
||||
|
||||
1. New entities in a collection marked as ``cascade: persist`` will be
|
||||
directly persisted by Doctrine.
|
||||
2. New entities in a collection not marked as ``cascade: persist`` will
|
||||
@@ -563,13 +553,6 @@ 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:
|
||||
|
||||
@@ -581,30 +564,31 @@ and StandingData:
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
#[Entity]
|
||||
/**
|
||||
* @Entity
|
||||
*/
|
||||
class Contact
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
#[OneToOne(targetEntity: StandingData::class, cascade: ['persist'], orphanRemoval: true)]
|
||||
private StandingData|null $standingData = null;
|
||||
/** @OneToOne(targetEntity="StandingData", orphanRemoval=true) */
|
||||
private $standingData;
|
||||
|
||||
/** @var Collection<int, Address> */
|
||||
#[OneToMany(targetEntity: Address::class, mappedBy: 'contact', cascade: ['persist'], orphanRemoval: true)]
|
||||
private Collection $addresses;
|
||||
/** @OneToMany(targetEntity="Address", mappedBy="contact", orphanRemoval=true) */
|
||||
private $addresses;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addresses = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function newStandingData(StandingData $sd): void
|
||||
public function newStandingData(StandingData $sd)
|
||||
{
|
||||
$this->standingData = $sd;
|
||||
}
|
||||
|
||||
public function removeAddress(int $pos): void
|
||||
public function removeAddress($pos)
|
||||
{
|
||||
unset($this->addresses[$pos]);
|
||||
}
|
||||
@@ -718,7 +702,6 @@ methods:
|
||||
|
||||
* ``andX($arg1, $arg2, ...)``
|
||||
* ``orX($arg1, $arg2, ...)``
|
||||
* ``not($expression)``
|
||||
* ``eq($field, $value)``
|
||||
* ``gt($field, $value)``
|
||||
* ``lt($field, $value)``
|
||||
@@ -729,42 +712,11 @@ methods:
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
* ``contains($field, $value)``
|
||||
* ``memberOf($value, $field)``
|
||||
* ``startsWith($field, $value)``
|
||||
* ``endsWith($field, $value)``
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Depending on whether the collection has already been loaded from the
|
||||
database or not, criteria matching may happen at the database/SQL level
|
||||
or on objects in memory. This may lead to different results and come
|
||||
surprising, for example when a code change in one place leads to a collection
|
||||
becoming initialized and, as a side effect, returning a different result
|
||||
or even breaking a ``matching()`` call somewhere else. Also, collection
|
||||
initialization state in practical use cases may differ from the one covered
|
||||
in unit tests.
|
||||
|
||||
Database level comparisons are based on scalar representations of the values
|
||||
stored in entity properties. The field names passed to expressions correspond
|
||||
to property names. Comparison and sorting may be affected by
|
||||
database-specific behavior. For example, MySQL enum types sort by index position,
|
||||
not lexicographically by value.
|
||||
|
||||
In-memory handling is based on the ``Selectable`` API of `Doctrine Collections <https://www.doctrine-project.org/projects/doctrine-collections/en/stable/index.html#matching>`.
|
||||
In this case, field names passed to expressions are being used to derive accessor
|
||||
method names. Strict type comparisons are used for equal and not-equal checks,
|
||||
and generally PHP language rules are being used for other comparison operators
|
||||
or sorting.
|
||||
|
||||
As a general guidance, for consistent results use the Criteria API with scalar
|
||||
values only. Note that ``DateTime`` and ``DateTimeImmutable`` are two predominant
|
||||
examples of value objects that are *not* scalars.
|
||||
|
||||
Refrain from using special database-level column types or custom Doctrine Types
|
||||
that may lead to database-specific comparison or sorting rules being applied, or
|
||||
to database-level values being different from object field values.
|
||||
|
||||
Provide accessor methods for all entity fields used in criteria expressions,
|
||||
and implement those methods in a way that their return value is the
|
||||
same as the database-level value.
|
||||
There is a limitation on the compatibility of Criteria comparisons.
|
||||
You have to use scalar values only as the value in a comparison or
|
||||
the behaviour between different backends is not the same.
|
||||
|
||||
@@ -27,7 +27,7 @@ Work that have not yet been persisted are lost.
|
||||
|
||||
.. note::
|
||||
|
||||
Doctrine NEVER touches the public API of methods in your entity
|
||||
Doctrine does NEVER touch 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,
|
||||
@@ -53,7 +53,7 @@ headline "Hello World" with the ID 1234:
|
||||
echo $article2->getHeadline();
|
||||
|
||||
In this case the Article is accessed from the entity manager twice,
|
||||
but modified in between. Doctrine ORM realizes this and will only
|
||||
but modified in between. Doctrine 2 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,29 +95,28 @@ from newly opened EntityManager.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
#[Entity]
|
||||
/** @Entity */
|
||||
class Article
|
||||
{
|
||||
#[Id, Column(type: 'integer'), GeneratedValue]
|
||||
private int|null $id = null;
|
||||
/** @Id @Column(type="integer") @GeneratedValue */
|
||||
private $id;
|
||||
|
||||
#[Column(type: 'string')]
|
||||
private string $headline;
|
||||
/** @Column(type="string") */
|
||||
private $headline;
|
||||
|
||||
#[ManyToOne(targetEntity: User::class)]
|
||||
private User|null $author = null;
|
||||
/** @ManyToOne(targetEntity="User") */
|
||||
private $author;
|
||||
|
||||
/** @var Collection<int, Comment> */
|
||||
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
|
||||
private Collection $comments;
|
||||
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
|
||||
private $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getAuthor(): User|null { return $this->author; }
|
||||
public function getComments(): Collection { return $this->comments; }
|
||||
public function getAuthor() { return $this->author; }
|
||||
public function getComments() { return $this->comments; }
|
||||
}
|
||||
|
||||
$article = $em->find('Article', 1);
|
||||
@@ -162,6 +161,31 @@ your code. See the following code:
|
||||
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 non-identity
|
||||
non-transient object state at instantiation time in order to
|
||||
enable lazy-loading mechanisms:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class UserProxyHASH extends User implements GhostObjectInterface
|
||||
{
|
||||
// ... generated code
|
||||
|
||||
public static function staticProxyConstructor($initializer)
|
||||
{
|
||||
// ... generated code
|
||||
}
|
||||
|
||||
private function callInitializerHASH($methodName, array $parameters)
|
||||
{
|
||||
// ... generated code
|
||||
}
|
||||
|
||||
// ... generated code
|
||||
}
|
||||
|
||||
.. warning::
|
||||
|
||||
Traversing the object graph for parts that are lazy-loaded will
|
||||
@@ -169,7 +193,6 @@ your code. See the following code:
|
||||
to heavily. Make sure to use DQL to fetch-join all the parts of the
|
||||
object-graph that you need as efficiently as possible.
|
||||
|
||||
|
||||
Persisting entities
|
||||
-------------------
|
||||
|
||||
@@ -192,12 +215,6 @@ be properly synchronized with the database when
|
||||
database in the most efficient way and a single, short transaction,
|
||||
taking care of maintaining referential integrity.
|
||||
|
||||
.. note::
|
||||
|
||||
Do not make any assumptions in your code about the number of queries
|
||||
it takes to flush changes, about the ordering of ``INSERT``, ``UPDATE``
|
||||
and ``DELETE`` queries or the order in which entities will be processed.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -218,11 +235,9 @@ Example:
|
||||
generated identifier being not available after a failed flush
|
||||
operation.
|
||||
|
||||
|
||||
The semantics of the persist operation, applied on an entity X, are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a new entity, it becomes managed. The entity X will be
|
||||
entered into the database as a result of the flush operation.
|
||||
- If X is a preexisting managed entity, it is ignored by the
|
||||
@@ -234,12 +249,6 @@ as follows:
|
||||
- If X is a detached entity, an exception will be thrown on
|
||||
flush.
|
||||
|
||||
.. caution::
|
||||
|
||||
Do not pass detached entities to the persist operation. The persist operation always
|
||||
considers entities that are not yet known to the ``EntityManager`` as new entities
|
||||
(refer to the ``STATE_NEW`` constant inside the ``UnitOfWork``).
|
||||
|
||||
Removing entities
|
||||
-----------------
|
||||
|
||||
@@ -260,7 +269,6 @@ which means that its persistent state will be deleted once
|
||||
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
|
||||
for more information.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -272,7 +280,6 @@ Example:
|
||||
The semantics of the remove operation, applied to an entity X are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a new entity, it is ignored by the remove operation.
|
||||
However, the remove operation is cascaded to entities referenced by
|
||||
X, if the relationship from X to these other entities is mapped
|
||||
@@ -288,61 +295,23 @@ 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.
|
||||
|
||||
During the ``EntityManager#flush()`` operation, the removed entity
|
||||
will also be removed from all collections in entities currently
|
||||
loaded into memory.
|
||||
|
||||
.. _remove_object_many_to_many_join_tables:
|
||||
|
||||
Join-table management when removing from many-to-many collections
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Regarding existing rows in many-to-many join tables that refer to
|
||||
an entity being removed, the following applies.
|
||||
|
||||
When the entity being removed does not declare the many-to-many association
|
||||
itself (that is, the many-to-many association is unidirectional and
|
||||
the entity is on the inverse side), the ORM has no reasonable way to
|
||||
detect associations targeting the entity's class. Thus, no ORM-level handling
|
||||
of join-table rows is attempted and database-level constraints apply.
|
||||
In case of database-level ``ON DELETE RESTRICT`` constraints, the
|
||||
``EntityManager#flush()`` operation may abort and a ``ConstraintViolationException``
|
||||
may be thrown. No in-memory collections will be modified in this case.
|
||||
With ``ON DELETE CASCADE``, the RDBMS will take care of removing rows
|
||||
from join tables.
|
||||
|
||||
When the entity being removed is part of bi-directional many-to-many
|
||||
association, either as the owning or inverse side, the ORM will
|
||||
delete rows from join tables before removing the entity itself. That means
|
||||
database-level ``ON DELETE RESTRICT`` constraints on join tables are not
|
||||
effective, since the join table rows are removed first. Removal of join table
|
||||
rows happens through specialized methods in entity and collection persister
|
||||
classes and take one query per entity and join table. In case the association
|
||||
uses a ``@JoinColumn`` configuration with ``onDelete="CASCADE"``, instead
|
||||
of using a dedicated ``DELETE`` query the database-level operation will be
|
||||
relied upon.
|
||||
|
||||
.. note::
|
||||
|
||||
In case you rely on database-level ``ON DELETE RESTRICT`` constraints,
|
||||
be aware that by making many-to-many associations bidirectional the
|
||||
assumed protection may be lost.
|
||||
|
||||
|
||||
Performance of different deletion strategies
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Removing an entity will also automatically delete any existing
|
||||
records in many-to-many join tables that link this entity. The
|
||||
action taken depends on the value of the ``@joinColumn`` mapping
|
||||
attribute "onDelete". Either Doctrine issues a dedicated ``DELETE``
|
||||
statement for records of each join table or it depends on the
|
||||
foreign key semantics of onDelete="CASCADE".
|
||||
|
||||
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 ORM will
|
||||
fetch this association. If it's 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()``.
|
||||
1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2
|
||||
will fetch this association. If it is 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()\`.
|
||||
In both cases the cascade remove semantics are applied recursively.
|
||||
For large object graphs this removal strategy can be very costly.
|
||||
2. Using a DQL ``DELETE`` statement allows you to delete multiple
|
||||
@@ -357,135 +326,43 @@ in multiple ways with very different performance impacts.
|
||||
because Doctrine will fetch and remove all associated entities
|
||||
explicitly nevertheless.
|
||||
|
||||
.. note::
|
||||
|
||||
Calling ``remove`` on an entity will remove the object from the identity
|
||||
map and therefore detach it. Querying the same entity again, for example
|
||||
via a lazy loaded relation, will return a new object.
|
||||
|
||||
|
||||
Detaching entities
|
||||
------------------
|
||||
|
||||
An entity is detached from an EntityManager and thus no longer
|
||||
managed by invoking the ``EntityManager#detach($entity)`` method on
|
||||
it or by cascading the detach operation to it. Changes made to the
|
||||
detached entity, if any (including removal of the entity), will not
|
||||
be synchronized to the database after the entity has been
|
||||
All entities are detached from an EntityManager and thus no longer
|
||||
managed by it after invoking the ``EntityManager#clear()`` method.
|
||||
Changes made to the detached entities, if any (including their removal),
|
||||
will not be synchronized to the database after they have been
|
||||
detached.
|
||||
|
||||
Doctrine will not hold on to any references to a detached entity.
|
||||
Doctrine will not hold on to any references to detached entities.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->detach($entity);
|
||||
$em->clear();
|
||||
|
||||
The semantics of the detach operation, applied to an entity X are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a managed entity, the detach operation causes it to
|
||||
become detached. The detach operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
":ref:`transitive-persistence`"). Entities which previously referenced X
|
||||
- If X is a managed entity, the ``clear`` operation causes it to
|
||||
become detached. Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
- If X is a new or detached entity, it is ignored by the detach
|
||||
operation.
|
||||
- If X is a removed entity, the detach operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities is mapped with cascade=DETACH or cascade=ALL (see
|
||||
":ref:`transitive-persistence`"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
- If X is a removed entity, it will become detached, and therefore
|
||||
no longer scheduled to be removed. Entities which previously
|
||||
referenced X will continue to reference X.
|
||||
|
||||
There are several situations in which an entity is detached
|
||||
automatically without invoking the ``detach`` method:
|
||||
automatically:
|
||||
|
||||
|
||||
- When ``EntityManager#clear()`` is invoked, all entities that are
|
||||
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
|
||||
unserialization will be detached (this is the case for all entities
|
||||
that are serialized and stored in some cache).
|
||||
|
||||
The ``detach`` operation is usually not as frequently needed and
|
||||
used as ``persist`` and ``remove``.
|
||||
|
||||
Merging entities
|
||||
----------------
|
||||
|
||||
Merging entities refers to the merging of (usually detached)
|
||||
entities into the context of an EntityManager so that they become
|
||||
managed again. To merge the state of an entity into an
|
||||
EntityManager use the ``EntityManager#merge($entity)`` method. The
|
||||
state of the passed entity will be merged into a managed copy of
|
||||
this entity and this copy will subsequently be returned.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$detachedEntity = unserialize($serializedEntity); // some detached entity
|
||||
$entity = $em->merge($detachedEntity);
|
||||
// $entity now refers to the fully managed copy returned by the merge operation.
|
||||
// The EntityManager $em now manages the persistence of $entity as usual.
|
||||
|
||||
|
||||
The semantics of the merge operation, applied to an entity X, are
|
||||
as follows:
|
||||
|
||||
|
||||
- If X is a detached entity, the state of X is copied onto a
|
||||
pre-existing managed entity instance X' of the same identity.
|
||||
- If X is a new entity instance, a new managed copy X' will be
|
||||
created and the state of X is copied onto this managed instance.
|
||||
- If X is a removed entity instance, an InvalidArgumentException
|
||||
will be thrown.
|
||||
- If X is a managed entity, it is ignored by the merge operation,
|
||||
however, the merge operation is cascaded to entities referenced by
|
||||
relationships from X if these relationships have been mapped with
|
||||
the cascade element value MERGE or ALL (see ":ref:`transitive-persistence`").
|
||||
- For all entities Y referenced by relationships from X having the
|
||||
cascade element value MERGE or ALL, Y is merged recursively as Y'.
|
||||
For all such Y referenced by X, X' is set to reference Y'. (Note
|
||||
that if X is managed then X is the same object as X'.)
|
||||
- If X is an entity merged to X', with a reference to another
|
||||
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
|
||||
navigation of the same association from X' yields a reference to a
|
||||
managed object Y' with the same persistent identity as Y.
|
||||
|
||||
The ``merge`` operation will throw an ``OptimisticLockException``
|
||||
if the entity being merged uses optimistic locking through a
|
||||
version field and the versions of the entity being merged and the
|
||||
managed copy don't match. This usually means that the entity has
|
||||
been modified while being detached.
|
||||
|
||||
The ``merge`` operation is usually not as frequently needed and
|
||||
used as ``persist`` and ``remove``. The most common scenario for
|
||||
the ``merge`` operation is to reattach entities to an EntityManager
|
||||
that come from some cache (and are therefore detached) and you want
|
||||
to modify and persist such an entity.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you need to perform multiple merges of entities that share certain subparts
|
||||
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
|
||||
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
|
||||
multiple copies of the "same" object in the database, however with different ids.
|
||||
|
||||
.. note::
|
||||
|
||||
If you load some detached entities from a cache and you do
|
||||
not need to persist or delete them or otherwise make use of them
|
||||
without the need for persistence services there is no need to use
|
||||
``merge``. I.e. you can simply pass detached objects from a cache
|
||||
directly to the view.
|
||||
|
||||
|
||||
Synchronization with the Database
|
||||
---------------------------------
|
||||
|
||||
@@ -527,7 +404,6 @@ Synchronizing New and Managed Entities
|
||||
The flush operation applies to a managed entity with the following
|
||||
semantics:
|
||||
|
||||
|
||||
- The entity itself is synchronized to the database using a SQL
|
||||
UPDATE statement, only if at least one persistent field has
|
||||
changed.
|
||||
@@ -536,14 +412,12 @@ semantics:
|
||||
The flush operation applies to a new entity with the following
|
||||
semantics:
|
||||
|
||||
|
||||
- The entity itself is synchronized to the database using a SQL
|
||||
INSERT statement.
|
||||
|
||||
For all (initialized) relationships of the new or managed entity
|
||||
the following semantics apply to each associated entity X:
|
||||
|
||||
|
||||
- If X is new and persist operations are configured to cascade on
|
||||
the relationship, X will be persisted.
|
||||
- If X is new and no persist operations are configured to cascade
|
||||
@@ -575,7 +449,6 @@ The cost of flushing
|
||||
|
||||
How costly a flush operation is, mainly depends on two factors:
|
||||
|
||||
|
||||
- The size of the EntityManager's current UnitOfWork.
|
||||
- The configured change tracking policies
|
||||
|
||||
@@ -595,14 +468,13 @@ during development.
|
||||
.. note::
|
||||
|
||||
Do not invoke ``flush`` after every change to an entity
|
||||
or every single invocation of persist/remove/merge/... This is an
|
||||
or every single invocation of persist/remove/refresh/... This is an
|
||||
anti-pattern and unnecessarily reduces the performance of your
|
||||
application. Instead, form units of work that operate on your
|
||||
objects and call ``flush`` when you are done. While serving a
|
||||
single HTTP request there should be usually no need for invoking
|
||||
``flush`` more than 0-2 times.
|
||||
|
||||
|
||||
Direct access to a Unit of Work
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -622,7 +494,6 @@ instance the EntityManager is currently using.
|
||||
marked as INTERNAL by not using them and carefully read the API
|
||||
documentation.
|
||||
|
||||
|
||||
Entity State
|
||||
~~~~~~~~~~~~
|
||||
|
||||
@@ -658,14 +529,14 @@ An entity is in DETACHED state if it has persistent state and
|
||||
identity but is currently not associated with an
|
||||
``EntityManager``.
|
||||
|
||||
An entity is in NEW state if has no persistent state and identity
|
||||
An entity is in NEW state if it has no persistent state and identity
|
||||
and is not associated with an ``EntityManager`` (for example those
|
||||
just created via the "new" operator).
|
||||
|
||||
Querying
|
||||
--------
|
||||
|
||||
Doctrine ORM provides the following ways, in increasing level of
|
||||
Doctrine 2 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.
|
||||
|
||||
@@ -769,8 +640,10 @@ Additionally, you can just count the result of the provided conditions when you
|
||||
By Criteria
|
||||
~~~~~~~~~~~
|
||||
|
||||
The Repository implement the ``Doctrine\Common\Collections\Selectable``
|
||||
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
|
||||
.. versionadded:: 2.3
|
||||
|
||||
The Repository implements the ``Doctrine\Common\Collections\Selectable``
|
||||
interface. It means you can build ``Doctrine\Common\Collections\Criteria``
|
||||
and pass them to the ``matching($criteria)`` method.
|
||||
|
||||
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
|
||||
@@ -780,26 +653,9 @@ By Eager Loading
|
||||
|
||||
Whenever you query for an entity that has persistent associations
|
||||
and these associations are mapped as EAGER, they will automatically
|
||||
be loaded together with the entity being queried and is thus
|
||||
be loaded together with the entity being queried and are thus
|
||||
immediately available to your application.
|
||||
|
||||
Eager Loading can also be configured at runtime through
|
||||
``AbstractQuery::setFetchMode`` in DQL or Native Queries.
|
||||
|
||||
Eager loading for many-to-one and one-to-one associations is using either a
|
||||
LEFT JOIN or a second query for fetching the related entity eagerly.
|
||||
|
||||
Eager loading for many-to-one associations uses a second query to load
|
||||
the collections for several entities at the same time.
|
||||
|
||||
When many-to-many, one-to-one or one-to-many associations are eagerly loaded,
|
||||
then the global batch size configuration is used to avoid IN(?) queries with
|
||||
too many arguments. The default batch size is 100 and can be changed with
|
||||
``Configuration::setEagerFetchBatchSize()``.
|
||||
|
||||
For eagerly loaded Many-To-Many associations one query has to be made for each
|
||||
collection.
|
||||
|
||||
By Lazy Loading
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -840,9 +696,7 @@ DQL and its syntax as well as the Doctrine class can be found in
|
||||
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
|
||||
For programmatically building up queries based on conditions that
|
||||
are only known at runtime, Doctrine provides the special
|
||||
``Doctrine\ORM\QueryBuilder`` class. While this a powerful tool,
|
||||
it also brings more complexity to your code compared to plain DQL,
|
||||
so you should only use it when you need it. More information on
|
||||
``Doctrine\ORM\QueryBuilder`` class. More information on
|
||||
constructing queries with a QueryBuilder can be found
|
||||
:doc:`in Query Builder chapter <query-builder>`.
|
||||
|
||||
@@ -863,7 +717,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 Attribute, Annotation, XML or YAML metadata. In large
|
||||
Repository in the Annotation or XML 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.
|
||||
@@ -873,11 +727,12 @@ 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: UserRepository::class)]
|
||||
/**
|
||||
* @ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
|
||||
*/
|
||||
class User
|
||||
{
|
||||
|
||||
@@ -885,10 +740,9 @@ in a central location.
|
||||
|
||||
class UserRepository extends EntityRepository
|
||||
{
|
||||
/** @return Collection<User> */
|
||||
public function getAllAdminUsers(): Collection
|
||||
public function getAllAdminUsers()
|
||||
{
|
||||
return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
|
||||
return $this->em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"')
|
||||
->getResult();
|
||||
}
|
||||
}
|
||||
@@ -901,3 +755,4 @@ You can access your repository now by calling:
|
||||
// $em instanceof EntityManager
|
||||
|
||||
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ 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>`_.
|
||||
The most convenient way to work with
|
||||
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
|
||||
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
|
||||
@@ -29,7 +31,6 @@ The XML mapping document of a class is loaded on-demand the first
|
||||
time it is requested and subsequently stored in the metadata cache.
|
||||
In order to work, this requires certain conventions:
|
||||
|
||||
|
||||
- Each entity/mapped superclass must get its own dedicated XML
|
||||
mapping document.
|
||||
- The name of the mapping document must consist of the fully
|
||||
@@ -192,12 +193,10 @@ specified as the ``<entity />`` element as a direct child of the
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The fully qualified class-name of the entity.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **table** - The Table-Name to be used for this entity. Otherwise the
|
||||
Unqualified Class-Name is used by default.
|
||||
- **repository-class** - The fully qualified class-name of an
|
||||
@@ -206,10 +205,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** - Specifies that this entity is marked as read only and not
|
||||
- **read-only** - (>= 2.1) 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** - The schema the table lies in, for platforms that support schemas
|
||||
- **schema** - (>= 2.5) The schema the table lies in, for platforms that support schemas
|
||||
|
||||
Defining Fields
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -239,13 +238,11 @@ entity. For the ID mapping you have to use the ``<id />`` element.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The name of the Property/Field on the given Entity PHP
|
||||
class.
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to
|
||||
"string"
|
||||
- column - Name of the column in the database, defaults to the
|
||||
@@ -256,11 +253,6 @@ 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.
|
||||
@@ -296,7 +288,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 ORM. The Id
|
||||
surrogate keys are recommended for use with Doctrine 2. The Id
|
||||
field allows to define properties of the identifier and allows a
|
||||
subset of the ``<field />`` element attributes:
|
||||
|
||||
@@ -308,7 +300,6 @@ subset of the ``<field />`` element attributes:
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The name of the Property/Field on the given Entity PHP
|
||||
class.
|
||||
- type - The ``Doctrine\DBAL\Types\Type`` name, preferably
|
||||
@@ -316,7 +307,6 @@ Required attributes:
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- column - Name of the column in the database, defaults to the
|
||||
field name.
|
||||
|
||||
@@ -342,7 +332,6 @@ have to use the ``NONE`` strategy.
|
||||
The following values are allowed for the ``<generator />`` strategy
|
||||
attribute:
|
||||
|
||||
|
||||
- AUTO - Automatic detection of the identifier strategy based on
|
||||
the preferred solution of the database vendor.
|
||||
- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs
|
||||
@@ -365,12 +354,10 @@ element to describe the sequence:
|
||||
|
||||
Required attributes for ``<sequence-generator />``:
|
||||
|
||||
|
||||
- sequence-name - The name of the sequence
|
||||
|
||||
Optional attributes for ``<sequence-generator />``:
|
||||
|
||||
|
||||
- allocation-size - By how much steps should the sequence be
|
||||
incremented when a value is retrieved. Defaults to 1
|
||||
- initial-value - What should the initial value of the sequence
|
||||
@@ -383,7 +370,6 @@ Optional attributes for ``<sequence-generator />``:
|
||||
element, if Doctrine chooses the sequence strategy for a
|
||||
platform.
|
||||
|
||||
|
||||
Defining a Mapped Superclass
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -403,7 +389,6 @@ can define it in XML using the ``<mapped-superclass />`` tag.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - Class name of the mapped superclass.
|
||||
|
||||
You can nest any number of ``<field />`` and unidirectional
|
||||
@@ -443,7 +428,6 @@ The allowed values for inheritance-type attribute are ``JOINED`` or
|
||||
All inheritance related definitions have to be defined on the root
|
||||
entity of the hierarchy.
|
||||
|
||||
|
||||
Defining Lifecycle Callbacks
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -476,7 +460,6 @@ For the inverse side the mapping is as simple as:
|
||||
|
||||
Required attributes for inverse One-To-One:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
@@ -494,7 +477,6 @@ For the owning side this mapping would look like:
|
||||
|
||||
Required attributes for owning One-to-One:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
@@ -502,7 +484,6 @@ Required attributes for owning One-to-One:
|
||||
|
||||
Optional attributes for owning One-to-One:
|
||||
|
||||
|
||||
- inversed-by - If the association is bidirectional the
|
||||
inversed-by attribute has to be specified with the name of the
|
||||
field on the inverse entity that contains the back-reference.
|
||||
@@ -545,7 +526,6 @@ like:
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
@@ -553,7 +533,6 @@ Required attributes:
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- inversed-by - If the association is bidirectional the
|
||||
inversed-by attribute has to be specified with the name of the
|
||||
field on the inverse entity that contains the back-reference.
|
||||
@@ -596,7 +575,6 @@ exists for bi-directional associations.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
@@ -606,7 +584,6 @@ Required attributes:
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY.
|
||||
- index-by: Index the collection by a field on the target entity.
|
||||
|
||||
@@ -625,7 +602,6 @@ definitions and rely on their implicit values.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- field - Name of the property/field on the entity's PHP class.
|
||||
- target-entity - Name of the entity associated entity class. If
|
||||
this is not qualified the namespace of the current class is
|
||||
@@ -633,7 +609,6 @@ Required attributes:
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- mapped-by - Name of the field on the owning side that contains
|
||||
the owning side association if the defined many-to-many association
|
||||
is on the inverse side.
|
||||
@@ -689,12 +664,9 @@ tags.
|
||||
Besides ``<cascade-all />`` the following operations can be
|
||||
specified by their respective tags:
|
||||
|
||||
|
||||
- ``<cascade-persist />``
|
||||
- ``<cascade-merge />``
|
||||
- ``<cascade-remove />``
|
||||
- ``<cascade-refresh />``
|
||||
- ``<cascade-detach />``
|
||||
|
||||
Join Column Element
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
@@ -705,14 +677,12 @@ key names are called that are used for joining two entities.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
- name - The column name of the foreign key.
|
||||
- referenced-column-name - The column name of the associated
|
||||
entities primary key
|
||||
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- unique - If the join column should contain a UNIQUE constraint.
|
||||
This makes sense for Many-To-Many join-columns only to simulate a
|
||||
one-to-many unidirectional using a join-table.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user