mirror of
https://github.com/doctrine/orm.git
synced 2026-03-30 02:42:18 +02:00
Compare commits
1489 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
434820973c | ||
|
|
41ff526921 | ||
|
|
0be52b0087 | ||
|
|
ee8dc496d9 | ||
|
|
f80656cddf | ||
|
|
72121c01ec | ||
|
|
ac505390dd | ||
|
|
728e6e15c5 | ||
|
|
d21305378c | ||
|
|
0552749059 | ||
|
|
fbd3fe95e4 | ||
|
|
c6d02daee0 | ||
|
|
5208035003 | ||
|
|
d93956eff0 | ||
|
|
b3b06d3e7d | ||
|
|
427f815975 | ||
|
|
bf601ce268 | ||
|
|
8bfb363fcc | ||
|
|
ebf2630a66 | ||
|
|
9018955e1f | ||
|
|
88d58ae0a3 | ||
|
|
2fc99afd44 | ||
|
|
fa0885e25d | ||
|
|
0e4a0108d2 | ||
|
|
58370256c0 | ||
|
|
d5364231c2 | ||
|
|
4df3a4d436 | ||
|
|
812989490c | ||
|
|
892ef9edb7 | ||
|
|
982782f8c9 | ||
|
|
7319f524a3 | ||
|
|
1d71fbf77b | ||
|
|
7eacfec2c3 | ||
|
|
46f2a41cf7 | ||
|
|
fd2baf6f65 | ||
|
|
c8bf06d549 | ||
|
|
3acfa50214 | ||
|
|
3dbe205498 | ||
|
|
899cce8094 | ||
|
|
7400d51444 | ||
|
|
96c344d22b | ||
|
|
f48d71ecd0 | ||
|
|
d3acbbf79b | ||
|
|
cb9ec8234b | ||
|
|
47c72e583e | ||
|
|
568c2d308c | ||
|
|
11a7f359d1 | ||
|
|
145f1f5198 | ||
|
|
ff1df41485 | ||
|
|
d36aec8fb7 | ||
|
|
2779b5ee91 | ||
|
|
32efbd3edd | ||
|
|
68718eac1b | ||
|
|
7b64b4a207 | ||
|
|
f1143f591f | ||
|
|
07fc401d25 | ||
|
|
96f166a7e9 | ||
|
|
f4b775323d | ||
|
|
43d308116d | ||
|
|
d2b4dd71d2 | ||
|
|
36e6a73d5b | ||
|
|
e26158a45e | ||
|
|
3cfcd6a856 | ||
|
|
ff68806bfa | ||
|
|
4192c3abf4 | ||
|
|
ac1e1c7d23 | ||
|
|
9ab999618c | ||
|
|
f2666a472f | ||
|
|
ceda5d3bc7 | ||
|
|
6d81d519b6 | ||
|
|
88d1d79516 | ||
|
|
cfc6cfd1a3 | ||
|
|
6b7d67b427 | ||
|
|
b6d08b15c0 | ||
|
|
01f89a8cdc | ||
|
|
efd7a5dca6 | ||
|
|
7ba0290643 | ||
|
|
8ceb47178b | ||
|
|
2560d4f419 | ||
|
|
87ee409783 | ||
|
|
d47c1f3e9b | ||
|
|
b952dac339 | ||
|
|
ffb7d4c79c | ||
|
|
e68717b725 | ||
|
|
30a063ef9d | ||
|
|
35c3669ebc | ||
|
|
23f4f03575 | ||
|
|
a912fc09be | ||
|
|
a736a3713b | ||
|
|
f2da5bc93e | ||
|
|
2905b435db | ||
|
|
48ca6dbcec | ||
|
|
15a4302902 | ||
|
|
1f82a20312 | ||
|
|
fc943b70f6 | ||
|
|
f36470941c | ||
|
|
ae6d80daab | ||
|
|
44e82e2720 | ||
|
|
e94467d6da | ||
|
|
794c7708e8 | ||
|
|
8e73926359 | ||
|
|
8fc1d74820 | ||
|
|
496c6a9f03 | ||
|
|
7873f700b0 | ||
|
|
46c0861f45 | ||
|
|
5149c0ff25 | ||
|
|
cf99d62472 | ||
|
|
5878797eae | ||
|
|
8c2d090dc8 | ||
|
|
3f772eac32 | ||
|
|
62c952d258 | ||
|
|
c2f698e56e | ||
|
|
40f2a3efba | ||
|
|
333b9c0b99 | ||
|
|
90d19b4131 | ||
|
|
374e7ace49 | ||
|
|
d752cafb47 | ||
|
|
0e44271a40 | ||
|
|
4c89498359 | ||
|
|
b6aa4bab15 | ||
|
|
27c42d418b | ||
|
|
bb8970286d | ||
|
|
b210c1e364 | ||
|
|
a16dc65cd2 | ||
|
|
0345f7b836 | ||
|
|
349724f05b | ||
|
|
e1825e37ef | ||
|
|
cfa1dfbfe6 | ||
|
|
8e3c3f0bae | ||
|
|
ad3c3f4114 | ||
|
|
8ca7db8852 | ||
|
|
ca42879f9e | ||
|
|
28b6ca3a16 | ||
|
|
c4a5e5c3f8 | ||
|
|
c308986a90 | ||
|
|
739f518ebe | ||
|
|
a46e794b60 | ||
|
|
d3ff823f97 | ||
|
|
32c125def1 | ||
|
|
0837493a7c | ||
|
|
260c2e899a | ||
|
|
74ce8913fc | ||
|
|
80a94727ee | ||
|
|
d6212dd09e | ||
|
|
122e9c3aa3 | ||
|
|
8e4f624f90 | ||
|
|
441c5d138c | ||
|
|
b3654f95d0 | ||
|
|
aacea65519 | ||
|
|
d0d802309c | ||
|
|
a352c214a0 | ||
|
|
cd00ccae69 | ||
|
|
e149f89cfe | ||
|
|
b79b26aa8b | ||
|
|
04d9bc40e4 | ||
|
|
31816f6e2f | ||
|
|
d32a8634aa | ||
|
|
f13f7ebe54 | ||
|
|
79e1be8c3d | ||
|
|
92dd27fe3f | ||
|
|
ad0a8c53fa | ||
|
|
be18256a93 | ||
|
|
bc7aeb9d11 | ||
|
|
6e095f7c3b | ||
|
|
c0a505366f | ||
|
|
b211dd4db7 | ||
|
|
3ca65e28fc | ||
|
|
b3331b2237 | ||
|
|
dfc31bc855 | ||
|
|
7c28a932ae | ||
|
|
15c145f3b3 | ||
|
|
dda42f6c09 | ||
|
|
99b2e57606 | ||
|
|
d3759a2447 | ||
|
|
b7cace86a0 | ||
|
|
b8fd708139 | ||
|
|
6cb5a9c50a | ||
|
|
4bbb1067ac | ||
|
|
b47a39be64 | ||
|
|
6b5eb11458 | ||
|
|
43a88d539d | ||
|
|
8a893068ce | ||
|
|
214dc9896b | ||
|
|
ba32237e2b | ||
|
|
984327d782 | ||
|
|
2be1b7d0b8 | ||
|
|
13197123c5 | ||
|
|
24408b42d3 | ||
|
|
8918bd3b8a | ||
|
|
4ab9413675 | ||
|
|
91408a3a54 | ||
|
|
bccc46dc12 | ||
|
|
a82f6c5725 | ||
|
|
59792654c0 | ||
|
|
f277eef6ea | ||
|
|
ed86ee2567 | ||
|
|
302c3a6640 | ||
|
|
0497f50ba6 | ||
|
|
2129801ac1 | ||
|
|
620319f206 | ||
|
|
5f3afa4c4f | ||
|
|
99db207a9f | ||
|
|
0bea6881da | ||
|
|
e279dfaa91 | ||
|
|
87a6d0b77e | ||
|
|
ffd3d34f34 | ||
|
|
57e9feffb2 | ||
|
|
6b5c97055c | ||
|
|
30fccf8e83 | ||
|
|
2a865177b9 | ||
|
|
d560449661 | ||
|
|
436b15e873 | ||
|
|
aea970722e | ||
|
|
b1466e6d3e | ||
|
|
8e16748ff8 | ||
|
|
9e9e5628f5 | ||
|
|
f2dc9a8f92 | ||
|
|
66f903a38f | ||
|
|
3dd7eb5888 | ||
|
|
dd12ba88ee | ||
|
|
83e00d5010 | ||
|
|
d831f4fd9f | ||
|
|
6ba2d1c317 | ||
|
|
5f99cad669 | ||
|
|
fb7a96caf9 | ||
|
|
13f838f8be | ||
|
|
2d88e45240 | ||
|
|
2ade863bca | ||
|
|
19dcd629c0 | ||
|
|
4fdbdabae4 | ||
|
|
53245e8a73 | ||
|
|
a7e13f89cc | ||
|
|
1d5c87ee4b | ||
|
|
f96dc3ba91 | ||
|
|
3d3ecc77bd | ||
|
|
df1250ee4b | ||
|
|
d995203ee1 | ||
|
|
8ecddc4fc1 | ||
|
|
059cfd86df | ||
|
|
0c4f9a8866 | ||
|
|
6a86175617 | ||
|
|
bf8e27b422 | ||
|
|
80f7824b3d | ||
|
|
828bfdf4ae | ||
|
|
c7d6d62393 | ||
|
|
c429f1c38b | ||
|
|
8b81691e77 | ||
|
|
d0d1e557d1 | ||
|
|
fd9f96c766 | ||
|
|
4bc29d1049 | ||
|
|
c016e2d434 | ||
|
|
12043cd845 | ||
|
|
a1c93bfd48 | ||
|
|
874a5e3547 | ||
|
|
68706034e5 | ||
|
|
bfc76adce0 | ||
|
|
8c7052c99c | ||
|
|
e1e7485e58 | ||
|
|
7a0385634f | ||
|
|
922c55f5dd | ||
|
|
61404e2d6d | ||
|
|
660f164568 | ||
|
|
acfc890dbd | ||
|
|
a708d4076e | ||
|
|
aac0204611 | ||
|
|
ed047520c3 | ||
|
|
caa008b61d | ||
|
|
07b397f341 | ||
|
|
c9d1f852de | ||
|
|
c29a1e96b7 | ||
|
|
a2f4053a81 | ||
|
|
3f09e20955 | ||
|
|
220dc79ebf | ||
|
|
6f6e88cfb6 | ||
|
|
dac1ce4172 | ||
|
|
ca39abcd71 | ||
|
|
4e0b76ce69 | ||
|
|
805ba041ef | ||
|
|
3842ad8ea1 | ||
|
|
c7281f6ade | ||
|
|
a8453dda89 | ||
|
|
309b286ed3 | ||
|
|
8ad3dfd3bd | ||
|
|
645cccf2db | ||
|
|
2be32f249c | ||
|
|
4a007c76f5 | ||
|
|
a754eae0f0 | ||
|
|
89fbb6a060 | ||
|
|
2751c0fff2 | ||
|
|
87e8bccb11 | ||
|
|
f39614136f | ||
|
|
a3208f8d08 | ||
|
|
e21b29c264 | ||
|
|
b456cffa2d | ||
|
|
17b996da8c | ||
|
|
92dc39bfb9 | ||
|
|
997000352a | ||
|
|
6f8a80be79 | ||
|
|
c4465abaa0 | ||
|
|
ddccd42bb1 | ||
|
|
9eaf23a5e0 | ||
|
|
a30d8d85ea | ||
|
|
44f2e22f14 | ||
|
|
4cbcdb761a | ||
|
|
b66643d52e | ||
|
|
42d9162bd5 | ||
|
|
8af68614fc | ||
|
|
764ab59882 | ||
|
|
468496be1a | ||
|
|
e18fb6607d | ||
|
|
2ab363ab82 | ||
|
|
0882b10213 | ||
|
|
328467c226 | ||
|
|
f6ce69fe29 | ||
|
|
aaad25a061 | ||
|
|
c503b81421 | ||
|
|
7e7921e32f | ||
|
|
8cc29e84a0 | ||
|
|
db9c12f1af | ||
|
|
82db643b4f | ||
|
|
4c7286f57b | ||
|
|
0072054020 | ||
|
|
0b8e468f06 | ||
|
|
bf06b7dbbc | ||
|
|
ad3b9de4b8 | ||
|
|
6d40859228 | ||
|
|
aac523d155 | ||
|
|
671fd50725 | ||
|
|
1a0bb82e1d | ||
|
|
a426808a16 | ||
|
|
492fb50744 | ||
|
|
c799c6da8b | ||
|
|
eb762dea23 | ||
|
|
19bc4991ae | ||
|
|
9864a5a9b9 | ||
|
|
5224a89549 | ||
|
|
d4db126bb0 | ||
|
|
60e29b40a0 | ||
|
|
496c22db0e | ||
|
|
1bf8465f43 | ||
|
|
ff15a2bc79 | ||
|
|
9d7be0f927 | ||
|
|
2921f068b7 | ||
|
|
9707701d10 | ||
|
|
df1577db0c | ||
|
|
866a424963 | ||
|
|
74c83f3cec | ||
|
|
5770459bfc | ||
|
|
94640aca88 | ||
|
|
fda770700a | ||
|
|
da109c36b7 | ||
|
|
40515472c1 | ||
|
|
bb994b9e70 | ||
|
|
76e2155fb3 | ||
|
|
894490c2a0 | ||
|
|
e1acba3ae4 | ||
|
|
9005c5afdd | ||
|
|
71218b66b9 | ||
|
|
0bc91f8733 | ||
|
|
de6d932e8c | ||
|
|
9e837dc53c | ||
|
|
57a9509dec | ||
|
|
ff3c89d8b1 | ||
|
|
7024926f10 | ||
|
|
e4006e533c | ||
|
|
b1e091b0e1 | ||
|
|
599865528e | ||
|
|
fe88422e68 | ||
|
|
741819f060 | ||
|
|
ec3eed68ca | ||
|
|
b7ae5b4afb | ||
|
|
f0ed4e87b5 | ||
|
|
a0c0d3bf2a | ||
|
|
2a58645cb5 | ||
|
|
1ede3c514f | ||
|
|
166c5816b6 | ||
|
|
25829ea450 | ||
|
|
ebd521c56e | ||
|
|
b352cd3e22 | ||
|
|
da3cd04993 | ||
|
|
db2530d6fd | ||
|
|
16a14f2238 | ||
|
|
6d428c90e2 | ||
|
|
9ad91ddc1c | ||
|
|
91a5091612 | ||
|
|
025ed1147b | ||
|
|
99fdbf550d | ||
|
|
55882ca7a6 | ||
|
|
633c8461c8 | ||
|
|
352b3ba6f6 | ||
|
|
c2dd274c42 | ||
|
|
004ac51869 | ||
|
|
3579997531 | ||
|
|
a2ca6bbfaf | ||
|
|
1174ec6e8a | ||
|
|
b064fe3d86 | ||
|
|
93c4064679 | ||
|
|
04a5b122b0 | ||
|
|
345cf1acf8 | ||
|
|
031e79e726 | ||
|
|
d44e6e1a9e | ||
|
|
1fc7f81741 | ||
|
|
5e2257db04 | ||
|
|
d2c9b22397 | ||
|
|
8c4b5a4b71 | ||
|
|
80f12ed490 | ||
|
|
a4e547b691 | ||
|
|
eca1d6b3ea | ||
|
|
c195064ba4 | ||
|
|
625f792290 | ||
|
|
112a402016 | ||
|
|
0a1a84163e | ||
|
|
594e60d3f7 | ||
|
|
874d60d8c7 | ||
|
|
bbe005837e | ||
|
|
b960170fe1 | ||
|
|
27300bf4af | ||
|
|
aa13e49fdf | ||
|
|
c2167664fc | ||
|
|
8952176c73 | ||
|
|
2b8acb9907 | ||
|
|
30088fe529 | ||
|
|
6744b48bc2 | ||
|
|
a54a258866 | ||
|
|
97914c0f3e | ||
|
|
40ee7af9c8 | ||
|
|
0c30bab776 | ||
|
|
5d8dc1757f | ||
|
|
8149a1dd83 | ||
|
|
dbb0cdea51 | ||
|
|
03d33ec900 | ||
|
|
84079572f7 | ||
|
|
e07c90df44 | ||
|
|
cfa9d787fe | ||
|
|
f1bc1bbf12 | ||
|
|
d8140d700a | ||
|
|
d658364b59 | ||
|
|
77ce354f18 | ||
|
|
b1bbad3b15 | ||
|
|
7515dd20f2 | ||
|
|
8ec186f095 | ||
|
|
52402917a0 | ||
|
|
c5a636ebfb | ||
|
|
2692435705 | ||
|
|
29f55eaef9 | ||
|
|
5401cb5329 | ||
|
|
d51235f200 | ||
|
|
cbb0c1bd04 | ||
|
|
ef0edc8929 | ||
|
|
fd27b22ad1 | ||
|
|
93dc028194 | ||
|
|
608b3ed6a4 | ||
|
|
131aac531a | ||
|
|
1e16cb83f8 | ||
|
|
97735cdb68 | ||
|
|
f23359c1f3 | ||
|
|
0fdcc71887 | ||
|
|
8774b02c88 | ||
|
|
d8ddc47f83 | ||
|
|
88ea1d33fa | ||
|
|
f95c81b210 | ||
|
|
94adf97550 | ||
|
|
37cb2c0722 | ||
|
|
25669c51b6 | ||
|
|
c8a41598c9 | ||
|
|
9826d9c29a | ||
|
|
6bf9f6f72f | ||
|
|
eba8fec1fb | ||
|
|
60a2628f9d | ||
|
|
82c87081b4 | ||
|
|
f6907b9503 | ||
|
|
4ef0a238bf | ||
|
|
af3591fbca | ||
|
|
1d8c7f9bac | ||
|
|
5109322f7d | ||
|
|
d4b94e097a | ||
|
|
2b824ea9df | ||
|
|
2fb3cfdba2 | ||
|
|
5389ad7261 | ||
|
|
3d5acd607b | ||
|
|
4a736f48f8 | ||
|
|
60601a9323 | ||
|
|
854ff17ab9 | ||
|
|
624af3df22 | ||
|
|
80573038ed | ||
|
|
668ad4cc29 | ||
|
|
fb3ec7648d | ||
|
|
39572a8b6e | ||
|
|
247fb6ef0d | ||
|
|
17892bb327 | ||
|
|
c47cf1de34 | ||
|
|
63519be69c | ||
|
|
34c25a3ee3 | ||
|
|
45e1817f6f | ||
|
|
260cc6e3e0 | ||
|
|
e3ecec36ad | ||
|
|
c678577f8f | ||
|
|
c32ba8f5d1 | ||
|
|
74c48c201d | ||
|
|
1231861b09 | ||
|
|
c7ef9085fb | ||
|
|
e91dcf8fb4 | ||
|
|
b1f7c59ed5 | ||
|
|
2fd8752818 | ||
|
|
d2f7514248 | ||
|
|
167dfde00f | ||
|
|
5181eae8d6 | ||
|
|
31d2d84160 | ||
|
|
bd47ec95a1 | ||
|
|
0e88f1b654 | ||
|
|
aa233f8e57 | ||
|
|
04acde667a | ||
|
|
7d98135084 | ||
|
|
388afb46d0 | ||
|
|
5d98247178 | ||
|
|
b66dd0b6fa | ||
|
|
807f1422a3 | ||
|
|
995054d884 | ||
|
|
0b5d877d5f | ||
|
|
067e01e0d7 | ||
|
|
aba486ea2d | ||
|
|
9eb2d6139e | ||
|
|
41bcdb3268 | ||
|
|
95b30c1d40 | ||
|
|
21ad7a1913 | ||
|
|
d4cdc6e1fe | ||
|
|
af1ea1ae1d | ||
|
|
379acd9dfd | ||
|
|
e8332a45de | ||
|
|
3df6b7316b | ||
|
|
fc609271e0 | ||
|
|
ba69cc8f7a | ||
|
|
8b9c29738d | ||
|
|
11c54b7715 | ||
|
|
30256e7a08 | ||
|
|
b925cce6c4 | ||
|
|
b446afd937 | ||
|
|
9cfdf1ef81 | ||
|
|
9b9128ae77 | ||
|
|
b21cb3e2a0 | ||
|
|
633a442046 | ||
|
|
33cee11e6f | ||
|
|
98aa25b0ea | ||
|
|
6e9c1d8a4b | ||
|
|
d13327eca2 | ||
|
|
fe8259a094 | ||
|
|
01d226aff0 | ||
|
|
29fe76cced | ||
|
|
2ce40a6aeb | ||
|
|
f0e403211b | ||
|
|
a9cc522e8a | ||
|
|
1afa8a915d | ||
|
|
87e9879edd | ||
|
|
652358a4de | ||
|
|
07a9b10f36 | ||
|
|
a97c2659fc | ||
|
|
462481ebbe | ||
|
|
d2be4a2b48 | ||
|
|
571115cf18 | ||
|
|
71b040c849 | ||
|
|
edffb4d449 | ||
|
|
60b6073643 | ||
|
|
1e3bf6562e | ||
|
|
3260d220e8 | ||
|
|
a7dcdd8d48 | ||
|
|
1dbc67cce1 | ||
|
|
5b6d766961 | ||
|
|
f5d4db7d9c | ||
|
|
7523513be5 | ||
|
|
aa9f34b600 | ||
|
|
1bdc61f932 | ||
|
|
93f617536b | ||
|
|
256091282e | ||
|
|
747c1857d6 | ||
|
|
05758f4564 | ||
|
|
49e4b1004c | ||
|
|
3e3751cfd9 | ||
|
|
966f9a84c2 | ||
|
|
e4847534a4 | ||
|
|
866418e40f | ||
|
|
e4ff7a35a8 | ||
|
|
914c400a7d | ||
|
|
e80cd74c3e | ||
|
|
ad5014decc | ||
|
|
9fa456b9f0 | ||
|
|
e123f16ec9 | ||
|
|
99dc8aa2a9 | ||
|
|
177c48107d | ||
|
|
8f9f41ea89 | ||
|
|
5a2a771173 | ||
|
|
fc67b398a1 | ||
|
|
5c02e0c1e8 | ||
|
|
a9c711ad7e | ||
|
|
ee5f222c58 | ||
|
|
8dccd27b52 | ||
|
|
d8663cd9ee | ||
|
|
8796e2d938 | ||
|
|
19fc91482e | ||
|
|
99df158fc8 | ||
|
|
1bf884970f | ||
|
|
8d144daf01 | ||
|
|
043ca69f0b | ||
|
|
3dc0f471fe | ||
|
|
c0c08d92ba | ||
|
|
799190d5e4 | ||
|
|
351b6972a4 | ||
|
|
7a63e81c94 | ||
|
|
43009682a4 | ||
|
|
049ad1e079 | ||
|
|
b42c36f472 | ||
|
|
9f13557d14 | ||
|
|
633b821e18 | ||
|
|
a59367423a | ||
|
|
1559db7ac1 | ||
|
|
971c400025 | ||
|
|
2359360149 | ||
|
|
22ecc2d58c | ||
|
|
49bb345533 | ||
|
|
fcfaa13df9 | ||
|
|
92476b5953 | ||
|
|
205ee72e33 | ||
|
|
f9062d9931 | ||
|
|
f18e178960 | ||
|
|
2a239be45e | ||
|
|
db528a44b1 | ||
|
|
4bf2e890fb | ||
|
|
76f0fe45af | ||
|
|
f304685c68 | ||
|
|
6ea30f0354 | ||
|
|
00aba32c67 | ||
|
|
46cebfb33d | ||
|
|
89bf6152a6 | ||
|
|
75821e75f5 | ||
|
|
7bfd172156 | ||
|
|
ee066cc5de | ||
|
|
7ce2381bdd | ||
|
|
334b7e68a7 | ||
|
|
95c60219d5 | ||
|
|
73770ac090 | ||
|
|
0a801b895e | ||
|
|
e9b54de488 | ||
|
|
fac7e8facb | ||
|
|
c222c1d17a | ||
|
|
2695f5e3a5 | ||
|
|
885c431bd9 | ||
|
|
5c1908d82f | ||
|
|
7bb02d0dbd | ||
|
|
96bcee4fa9 | ||
|
|
21e12ef4a9 | ||
|
|
11c84c7b20 | ||
|
|
3219fe5316 | ||
|
|
e71272e2b4 | ||
|
|
85a52d781e | ||
|
|
2c1ebc4ef1 | ||
|
|
77ee69f1a8 | ||
|
|
0a10f347d3 | ||
|
|
6c0654c144 | ||
|
|
a360da5a7e | ||
|
|
0aef63b350 | ||
|
|
4ebf27de35 | ||
|
|
942bb6cb1f | ||
|
|
7c6c5d87c8 | ||
|
|
ccaa4b8ce1 | ||
|
|
a557c97a93 | ||
|
|
38bfcc6a7a | ||
|
|
03972c9c3a | ||
|
|
692a1afa86 | ||
|
|
4eb4465169 | ||
|
|
318e9a5596 | ||
|
|
e44a83fc7b | ||
|
|
b88480212a | ||
|
|
6b6c300319 | ||
|
|
7c5b27da2c | ||
|
|
7e50a965fa | ||
|
|
e798bfe34a | ||
|
|
0865181702 | ||
|
|
b43325760d | ||
|
|
f537eb2915 | ||
|
|
2c5e76c961 | ||
|
|
03b4397557 | ||
|
|
3d7a7346f7 | ||
|
|
5bd7bd8d48 | ||
|
|
904f4d5021 | ||
|
|
7aab261c24 | ||
|
|
f5b4e8c823 | ||
|
|
60cc11461d | ||
|
|
f2edf36248 | ||
|
|
d6049f8631 | ||
|
|
5507e0be29 | ||
|
|
1aa02f9afc | ||
|
|
bd1efaf528 | ||
|
|
504e701020 | ||
|
|
e4704beaf9 | ||
|
|
dcc80af7d9 | ||
|
|
10935dd843 | ||
|
|
260c8d0113 | ||
|
|
23ae83e351 | ||
|
|
64ab53a243 | ||
|
|
e42d0bdda5 | ||
|
|
f3df000d29 | ||
|
|
5989ea1752 | ||
|
|
888f1be7c9 | ||
|
|
63bd6c359c | ||
|
|
773af5b306 | ||
|
|
9c76c3766f | ||
|
|
c816d375e8 | ||
|
|
b181228d69 | ||
|
|
57f560401b | ||
|
|
119aa4e46c | ||
|
|
1f53afa9cd | ||
|
|
4f28aaa206 | ||
|
|
bb943afabe | ||
|
|
247b085fce | ||
|
|
5968b9e62f | ||
|
|
a831bb82ce | ||
|
|
41a377948b | ||
|
|
09cbb9ff48 | ||
|
|
730db5fd2e | ||
|
|
3d7ddc89b4 | ||
|
|
109ac5f827 | ||
|
|
d137ffe0a4 | ||
|
|
31c40f8342 | ||
|
|
5208187f1a | ||
|
|
9e35d9712d | ||
|
|
4fccf84c82 | ||
|
|
38636e7db4 | ||
|
|
63e4eea9e2 | ||
|
|
df5f480b4c | ||
|
|
4e304df495 | ||
|
|
eaedc37d7b | ||
|
|
5a562b3571 | ||
|
|
4e573038be | ||
|
|
617ec9219e | ||
|
|
ce4abdea55 | ||
|
|
7c168c2047 | ||
|
|
145c44630e | ||
|
|
eaeecc3857 | ||
|
|
f570eb5922 | ||
|
|
a1839048dd | ||
|
|
c86865aa71 | ||
|
|
8b499a4791 | ||
|
|
06691e0150 | ||
|
|
fc8ede844d | ||
|
|
443902f9f9 | ||
|
|
563c79bd80 | ||
|
|
d0c3f961ef | ||
|
|
a8d7b327ef | ||
|
|
c24a89f4a3 | ||
|
|
3ce833fb62 | ||
|
|
f3909ae885 | ||
|
|
22b5fb1ad4 | ||
|
|
f64d543d0c | ||
|
|
eecf4382b0 | ||
|
|
a8a3a8c9e7 | ||
|
|
1a8eeacfba | ||
|
|
cd1a5fcadc | ||
|
|
21a5d8ca1b | ||
|
|
cfd595b699 | ||
|
|
018a5db08f | ||
|
|
f39f1a2e11 | ||
|
|
262d13a047 | ||
|
|
f4595d3a2f | ||
|
|
30cd2d172b | ||
|
|
9582ffc982 | ||
|
|
00c67ba2db | ||
|
|
0c2edcd08a | ||
|
|
a22f165026 | ||
|
|
ab0e854830 | ||
|
|
85e2dc8f22 | ||
|
|
3ce262a61a | ||
|
|
81d44d4d6e | ||
|
|
754f36ef65 | ||
|
|
1be226cf63 | ||
|
|
7f4de25a26 | ||
|
|
493d39f5df | ||
|
|
d1c8d378cf | ||
|
|
879d4e7df0 | ||
|
|
29062fb42e | ||
|
|
a06f8d4759 | ||
|
|
6aa81d1d36 | ||
|
|
6e6be3fdd9 | ||
|
|
24377156b4 | ||
|
|
ba9fecc43f | ||
|
|
62d122bd54 | ||
|
|
fda6fdd9fb | ||
|
|
512aa8a3c7 | ||
|
|
234989d069 | ||
|
|
c609072ce1 | ||
|
|
74c8a08828 | ||
|
|
1d5e16e9d9 | ||
|
|
521588f498 | ||
|
|
6af1d2843f | ||
|
|
555e8ae641 | ||
|
|
dc3b166811 | ||
|
|
44a6141235 | ||
|
|
53c5824a6b | ||
|
|
1d7397caf0 | ||
|
|
92274124f9 | ||
|
|
49333867f8 | ||
|
|
9894dcb4b0 | ||
|
|
fdb2af07e7 | ||
|
|
dffd765b1e | ||
|
|
c1038096e0 | ||
|
|
8f77afdc34 | ||
|
|
754e1f5d42 | ||
|
|
c97799f151 | ||
|
|
56598596a4 | ||
|
|
6ad9c9ea04 | ||
|
|
c4d41fe56a | ||
|
|
6b1d64d484 | ||
|
|
be4aafd4f6 | ||
|
|
2a7d21ad18 | ||
|
|
4a87f00fab | ||
|
|
77a338e0fd | ||
|
|
cff5c07014 | ||
|
|
73ea0ba8f3 | ||
|
|
e736d19677 | ||
|
|
0a86c324ad | ||
|
|
52badf1cdd | ||
|
|
4c59ec9282 | ||
|
|
dc7c6ed72c | ||
|
|
28025b8230 | ||
|
|
0ed18fb062 | ||
|
|
5d477cdbbc | ||
|
|
b2f5da19a4 | ||
|
|
401300b295 | ||
|
|
12465e08ad | ||
|
|
89a00860e4 | ||
|
|
f8002ca27e | ||
|
|
a4f76bda34 | ||
|
|
1d2baedfd5 | ||
|
|
3341781f52 | ||
|
|
1dfadef221 | ||
|
|
ad6469b64a | ||
|
|
087d081601 | ||
|
|
d3c604567b | ||
|
|
c148059593 | ||
|
|
20190605a0 | ||
|
|
9acf170292 | ||
|
|
fa09a95023 | ||
|
|
b06dcb89b3 | ||
|
|
f11697361d | ||
|
|
a9c2778f30 | ||
|
|
be1fd130f1 | ||
|
|
a9230b8546 | ||
|
|
3cd7b8c951 | ||
|
|
24da9061b9 | ||
|
|
e2b198112e | ||
|
|
e7f2e35383 | ||
|
|
979fede80c | ||
|
|
8c38f5775d | ||
|
|
899393d3bb | ||
|
|
bfa9a31ad7 | ||
|
|
6344fd34cb | ||
|
|
7789df39c5 | ||
|
|
1b39cd87ad | ||
|
|
86fd0c5aa9 | ||
|
|
39dcf3e4c6 | ||
|
|
317e86802d | ||
|
|
95d9c64aec | ||
|
|
53cd9c4ca8 | ||
|
|
e7856f90d8 | ||
|
|
d2643eeb8b | ||
|
|
e37041aa94 | ||
|
|
f1bf045af3 | ||
|
|
8d433cdb39 | ||
|
|
f9a605f6ca | ||
|
|
7bf206adb4 | ||
|
|
8580f02c6a | ||
|
|
cdf4af5f27 | ||
|
|
05e77868ab | ||
|
|
db375a22cc | ||
|
|
0bf3d7f84c | ||
|
|
155672af40 | ||
|
|
bd58e4de4f | ||
|
|
3e98fdb082 | ||
|
|
3b6309318b | ||
|
|
60a967bb1d | ||
|
|
b8b5c2d686 | ||
|
|
11c2d815ef | ||
|
|
56741a3fee | ||
|
|
c8df209409 | ||
|
|
fb95116a26 | ||
|
|
83da3d4b04 | ||
|
|
5c6ecdcf1b | ||
|
|
dc07fc609e | ||
|
|
dbe843fc4b | ||
|
|
b8a18cd0a1 | ||
|
|
ca1a9473d3 | ||
|
|
e4050edb4e | ||
|
|
81fefb40db | ||
|
|
26cf90e1c5 | ||
|
|
cf1ba3183d | ||
|
|
3861cbf317 | ||
|
|
cdad5a82c5 | ||
|
|
d6eddab94f | ||
|
|
ade3f3a7f3 | ||
|
|
d3c2c40452 | ||
|
|
a6e44d9305 | ||
|
|
42c4938a8b | ||
|
|
fb1136cc9a | ||
|
|
03da85e19e | ||
|
|
a4379cc9e2 | ||
|
|
a353cb81a3 | ||
|
|
277833b487 | ||
|
|
47cb963731 | ||
|
|
fc81760b54 | ||
|
|
96b3797ad6 | ||
|
|
974a9f4b9e | ||
|
|
2ee56a595b | ||
|
|
05db15f7ee | ||
|
|
cd11723e63 | ||
|
|
3fca33bdc4 | ||
|
|
67e205b36a | ||
|
|
22105058fa | ||
|
|
503b211a22 | ||
|
|
f8436b2165 | ||
|
|
75bf197e11 | ||
|
|
99d704ff45 | ||
|
|
29f51b4a26 | ||
|
|
1f521d26f3 | ||
|
|
fda2cd7d0e | ||
|
|
a879811b6c | ||
|
|
e52ca954f0 | ||
|
|
d7a0ed0611 | ||
|
|
44af69c5d2 | ||
|
|
b8c7d871be | ||
|
|
01d51bfca3 | ||
|
|
48dcbe9d35 | ||
|
|
3e3bfbf6d8 | ||
|
|
60346e0046 | ||
|
|
81f4d3b0fb | ||
|
|
e43b9e9e3a | ||
|
|
e7e142ea4a | ||
|
|
5353137617 | ||
|
|
009e94720b | ||
|
|
d27cffa8e6 | ||
|
|
dd476094af | ||
|
|
b431332cef | ||
|
|
b49026b657 | ||
|
|
16a3a2a132 | ||
|
|
5eebdcf630 | ||
|
|
43d22984ae | ||
|
|
d2cbd5e872 | ||
|
|
00bbf4f523 | ||
|
|
95546d68c5 | ||
|
|
12b5e79ff2 | ||
|
|
8a87fa2d01 | ||
|
|
c5c56a9dad | ||
|
|
36e9904082 | ||
|
|
e2cba87662 | ||
|
|
7bf4a65c92 | ||
|
|
de4c854ac9 | ||
|
|
5e51a985b7 | ||
|
|
6f79a378d5 | ||
|
|
61f6b667c0 | ||
|
|
591bae0855 | ||
|
|
bea3c653bc | ||
|
|
a90035e81a | ||
|
|
35341769ea | ||
|
|
2814d6e2fa | ||
|
|
50f321f2e9 | ||
|
|
140960ebb1 | ||
|
|
74ec055d57 | ||
|
|
c6ea8b1129 | ||
|
|
3bec698fed | ||
|
|
a90f23dfc7 | ||
|
|
0da6669fac | ||
|
|
75f44008d6 | ||
|
|
eb62ae5933 | ||
|
|
31a0c02b06 | ||
|
|
f18d279710 | ||
|
|
718ee42e8e | ||
|
|
20fb340375 | ||
|
|
184f7d3285 | ||
|
|
b785a8dc02 | ||
|
|
2b47670831 | ||
|
|
da43aa2d49 | ||
|
|
44feacd327 | ||
|
|
355d2c3d19 | ||
|
|
d330da898f | ||
|
|
5d12593e70 | ||
|
|
dbcdc1d42a | ||
|
|
0d82128b2e | ||
|
|
7544934158 | ||
|
|
1cb8d790b6 | ||
|
|
2829174267 | ||
|
|
5b8b548bd4 | ||
|
|
9abccba109 | ||
|
|
da7582d329 | ||
|
|
aa1fda6d5f | ||
|
|
29d9f344e8 | ||
|
|
549bfe127c | ||
|
|
12789ee6ca | ||
|
|
5761d07c46 | ||
|
|
cd36407f28 | ||
|
|
1880cbd8b6 | ||
|
|
27e9b49215 | ||
|
|
bcc7983934 | ||
|
|
ab4b761110 | ||
|
|
f7c16ab364 | ||
|
|
99b1eaaabb | ||
|
|
1dba0b8545 | ||
|
|
0e8491a474 | ||
|
|
511b27517a | ||
|
|
a81458a0aa | ||
|
|
b108a2af52 | ||
|
|
f7317d700c | ||
|
|
2301fb3ff2 | ||
|
|
eccec87796 | ||
|
|
c0fc4f1158 | ||
|
|
52b2d9022a | ||
|
|
a7c4ca82fd | ||
|
|
6bf6bae219 | ||
|
|
c6675b0ce3 | ||
|
|
db6c593463 | ||
|
|
347d1625bc | ||
|
|
16cddd4693 | ||
|
|
ffd1465af2 | ||
|
|
8eef0beacb | ||
|
|
015ec444c5 | ||
|
|
32ea9112fa | ||
|
|
b55ef58025 | ||
|
|
a3e9529c02 | ||
|
|
2ab752bfc3 | ||
|
|
7ef3e3a60c | ||
|
|
04b48ae12b | ||
|
|
58d8b86bd5 | ||
|
|
8237760c1b | ||
|
|
76badc296a | ||
|
|
1162440d55 | ||
|
|
8c49ba6128 | ||
|
|
56a6505294 | ||
|
|
3dfc180720 | ||
|
|
18e3cb4440 | ||
|
|
aa8cf7bae9 | ||
|
|
27f3bc1e2c | ||
|
|
b59b966cc2 | ||
|
|
a9bca86d4d | ||
|
|
d00069e38b | ||
|
|
e409c10209 | ||
|
|
1610d916a4 | ||
|
|
765e102d01 | ||
|
|
ed7f658437 | ||
|
|
659f6a3864 | ||
|
|
9da83cfae8 | ||
|
|
163dac4a91 | ||
|
|
c834ccf3fa | ||
|
|
be090e2f75 | ||
|
|
288e3191ce | ||
|
|
d3f6c5ec70 | ||
|
|
6ac7480df4 | ||
|
|
81fe6a82b3 | ||
|
|
ea788fb734 | ||
|
|
7e4106d47c | ||
|
|
9b902263d5 | ||
|
|
650d49ee81 | ||
|
|
68b0060595 | ||
|
|
649ff94b38 | ||
|
|
fa7799cec1 | ||
|
|
c0a87597fa | ||
|
|
0b5b7190d7 | ||
|
|
fadd0a338f | ||
|
|
741da7806c | ||
|
|
3bc61d5f5e | ||
|
|
68c5d761a8 | ||
|
|
b9b952ce8a | ||
|
|
20d86c5b27 | ||
|
|
14e0800293 | ||
|
|
34d8e00df7 | ||
|
|
313e4a33e5 | ||
|
|
beb2641492 | ||
|
|
4d48781e2b | ||
|
|
3df494ddc8 | ||
|
|
70603ee3db | ||
|
|
90b7450747 | ||
|
|
2c1818d513 | ||
|
|
1c2b7c9685 | ||
|
|
a6eb7f7c96 | ||
|
|
fafb8166f4 | ||
|
|
b3aa8254e4 | ||
|
|
2f60d6a1f8 | ||
|
|
964d510357 | ||
|
|
b173763bbb | ||
|
|
bf322b903d | ||
|
|
56daa67f94 | ||
|
|
c5283eea87 | ||
|
|
59a0410951 | ||
|
|
165722cf05 | ||
|
|
76a5229fac | ||
|
|
6c2463b905 | ||
|
|
2f2236a1d6 | ||
|
|
60e2224e6b | ||
|
|
b3df49b2f3 | ||
|
|
01e53ba44a | ||
|
|
f06f383a38 | ||
|
|
c1943624ab | ||
|
|
82588c0af9 | ||
|
|
4b45183dbd | ||
|
|
6ed05a9670 | ||
|
|
4c2ced8fee | ||
|
|
aef8f63dd0 | ||
|
|
9c320ca64f | ||
|
|
1fa71f15d9 | ||
|
|
38efda33e1 | ||
|
|
13473e8b4e | ||
|
|
9b4c50e81e | ||
|
|
b784a04cf7 | ||
|
|
86cde3a9df | ||
|
|
d814ad7234 | ||
|
|
9461839d42 | ||
|
|
e8296e8e7d | ||
|
|
cbde629bf0 | ||
|
|
02eaf6a17a | ||
|
|
34dbefaf22 | ||
|
|
dd64161ece | ||
|
|
cc7ef71a13 | ||
|
|
1e660abeb8 | ||
|
|
3ed64dcec2 | ||
|
|
dd3f67d862 | ||
|
|
0feaf92348 | ||
|
|
ae785757a1 | ||
|
|
0adeade045 | ||
|
|
f0accca99d | ||
|
|
cfeda903e3 | ||
|
|
468fe315ba | ||
|
|
375b0369ae | ||
|
|
788143dc03 | ||
|
|
a1ca73d1e1 | ||
|
|
1e97cf21e8 | ||
|
|
2c70f4edf7 | ||
|
|
a4d84e0cd8 | ||
|
|
b491e75d64 | ||
|
|
5535690b75 | ||
|
|
744c308337 | ||
|
|
55d4f515af | ||
|
|
d7e7baf2a2 | ||
|
|
196ea2d0c5 | ||
|
|
f9217cf6f2 | ||
|
|
1697293591 | ||
|
|
22b9574a0f | ||
|
|
5bd476209d | ||
|
|
ebe11c7bcf | ||
|
|
8bde0c8a90 | ||
|
|
6963fd8f20 | ||
|
|
d97fad26a5 | ||
|
|
7f16e5bc51 | ||
|
|
50d7ed98ca | ||
|
|
b792e959cd | ||
|
|
30eb4646f7 | ||
|
|
b22cdab1bb | ||
|
|
43787447d6 | ||
|
|
75957ee3e7 | ||
|
|
3e87a1c4b8 | ||
|
|
e37e1818c6 | ||
|
|
86e13f9c5d | ||
|
|
5849dec16f | ||
|
|
3ca6828544 | ||
|
|
5e53e92fc5 | ||
|
|
e0148f14e2 | ||
|
|
6e6d3e27ab | ||
|
|
eb62e4d6c3 | ||
|
|
7057d3607e | ||
|
|
3260291aef | ||
|
|
f9715ac24a | ||
|
|
24e4215ab3 | ||
|
|
156075682f | ||
|
|
6099e45eef | ||
|
|
0c6b510c2a | ||
|
|
e390dbd5ba | ||
|
|
b341d5c453 | ||
|
|
044b202379 | ||
|
|
b2a6f8aa0e | ||
|
|
276a22fb6f | ||
|
|
6dcb97a20e | ||
|
|
0a1fa8920e | ||
|
|
3971e8c55b | ||
|
|
996db42029 | ||
|
|
cf3a54ab95 | ||
|
|
16baa8d60f | ||
|
|
41235f61de | ||
|
|
21a250fc02 | ||
|
|
9ac7c3bf70 | ||
|
|
342ab2f7f1 | ||
|
|
84f2bf7204 | ||
|
|
8c086d1a6e | ||
|
|
df129635cf | ||
|
|
a130ff96ba | ||
|
|
5b22e59383 | ||
|
|
624fcb4486 | ||
|
|
00718f6bf7 | ||
|
|
9b4dadade0 | ||
|
|
5687347d60 | ||
|
|
15aafaa11d | ||
|
|
8d62aadf55 | ||
|
|
b1bf57db48 | ||
|
|
9e68a5adc7 | ||
|
|
a6ddac0a96 | ||
|
|
bd94931201 | ||
|
|
4df7699ced | ||
|
|
fb4d02c69b | ||
|
|
da4a29c0e2 | ||
|
|
d2cbdd1866 | ||
|
|
f840f0d464 | ||
|
|
11f9b00d53 | ||
|
|
33476d8c34 | ||
|
|
c337f8f5c7 | ||
|
|
3c3b7364ba | ||
|
|
9b77ba2c1a | ||
|
|
3c7d92e4cc | ||
|
|
e8f40f58a1 | ||
|
|
8ea62b95b8 | ||
|
|
832adae6b4 | ||
|
|
599f99471e | ||
|
|
b48fca543c | ||
|
|
57b22bd646 | ||
|
|
e3627f1886 | ||
|
|
b980e98ba3 | ||
|
|
6d8778fd69 | ||
|
|
f8ba3a3434 | ||
|
|
3cff0f88bd | ||
|
|
e0cb90d426 | ||
|
|
9c5cea3e95 | ||
|
|
22e76e8624 | ||
|
|
86c81da7ce | ||
|
|
6c96e23a47 | ||
|
|
0b45d96c74 | ||
|
|
001d5b7ed3 | ||
|
|
427642aaba | ||
|
|
84eb175dbf | ||
|
|
50ee47cc70 | ||
|
|
04789dfec7 | ||
|
|
7fc36b4110 | ||
|
|
cdfcca2ff2 | ||
|
|
9f93999758 | ||
|
|
e8acfb5891 | ||
|
|
5ec9a62e2b | ||
|
|
96398ba30f | ||
|
|
c561223d1b | ||
|
|
8192d5bb3e | ||
|
|
91ce78815f | ||
|
|
71bb27a62f | ||
|
|
e64f44ec9b | ||
|
|
802cc82c8d | ||
|
|
50d87bd111 | ||
|
|
3a44a3dada | ||
|
|
3452f5ccc3 | ||
|
|
d49cd4aa1e | ||
|
|
58992ad523 | ||
|
|
089a0ac60b | ||
|
|
d57ecc12c0 | ||
|
|
1187c51a89 | ||
|
|
46dec5478a | ||
|
|
49bb687aaf | ||
|
|
664925d7f4 | ||
|
|
370dbf12de | ||
|
|
3e0529db53 | ||
|
|
bad0f17c10 | ||
|
|
443259f629 | ||
|
|
b7bd42638d | ||
|
|
ada97d55ce | ||
|
|
6dfafad33c | ||
|
|
c8d2ac40f4 | ||
|
|
d7a3154954 | ||
|
|
d88cf97bef | ||
|
|
28cebeca1c | ||
|
|
e7685c89da | ||
|
|
378f6546b4 | ||
|
|
843966ac50 | ||
|
|
9e085ca0b3 | ||
|
|
2fa289edee | ||
|
|
ff28507b88 | ||
|
|
2383ccfba5 | ||
|
|
f097478437 | ||
|
|
a7ded16204 | ||
|
|
1587aac4ff | ||
|
|
06c194310a | ||
|
|
d39a73cce6 | ||
|
|
5481e0fb8b | ||
|
|
43b2419a3c | ||
|
|
7de2e04ccd | ||
|
|
96687e04a6 | ||
|
|
b1451cda52 | ||
|
|
b15e8b79dc | ||
|
|
47441666df | ||
|
|
60b80c95cc | ||
|
|
290dc8989d | ||
|
|
52817cedfc | ||
|
|
e354ebb162 | ||
|
|
8535e01115 | ||
|
|
e682f48422 | ||
|
|
e91bce5f2a | ||
|
|
c86ab7d239 | ||
|
|
88f43a947d | ||
|
|
dd2068777c | ||
|
|
1ddb63c88a | ||
|
|
3a7d2da2e5 | ||
|
|
5c6f0edb23 | ||
|
|
b5799ae569 | ||
|
|
7d4016a757 | ||
|
|
61ad1bd137 | ||
|
|
d02b059269 | ||
|
|
4b87f5827f | ||
|
|
60beca3760 | ||
|
|
03523c67d5 | ||
|
|
3e26330c53 | ||
|
|
b055d78ea1 | ||
|
|
2242a4c652 | ||
|
|
42a500a73d | ||
|
|
84f51b68af | ||
|
|
ae243643e5 | ||
|
|
4b7d8511f8 | ||
|
|
a18ff95fc4 | ||
|
|
8b79fe9d9b | ||
|
|
c03996d3b3 | ||
|
|
16172534bb | ||
|
|
1dbacec0e4 | ||
|
|
1b09bb75b0 | ||
|
|
2e2921690a | ||
|
|
a0a0c731bb | ||
|
|
d1b24dfe12 | ||
|
|
5c8cca2f44 | ||
|
|
8aa0cdd0b9 | ||
|
|
2d00a9bcbf | ||
|
|
29c2b807ce | ||
|
|
41ee7e0f1a | ||
|
|
b062ffdead | ||
|
|
3e5bf819e3 | ||
|
|
233ebb0ba1 | ||
|
|
df461601f7 | ||
|
|
2b3648c725 | ||
|
|
93806a8036 | ||
|
|
dfa874642a | ||
|
|
df820a5695 | ||
|
|
f88896cc9d | ||
|
|
d4d9a2ba3c | ||
|
|
5393bc9956 | ||
|
|
dbd3fa4501 | ||
|
|
a3b1f85fd4 | ||
|
|
1d6adcaf4a | ||
|
|
71b1dde096 | ||
|
|
8a9f6849a2 | ||
|
|
f10f4b7c52 | ||
|
|
bb8f1c77cc | ||
|
|
57ca849848 | ||
|
|
26c295b06b | ||
|
|
f5e79fa0c0 | ||
|
|
e87c94052d | ||
|
|
1370424fed | ||
|
|
ad056ebdc6 | ||
|
|
506df640b5 | ||
|
|
3a7b2991e8 | ||
|
|
61488d955e | ||
|
|
72d848901b | ||
|
|
8503c1247f | ||
|
|
6bf6db3533 | ||
|
|
70a22b6a73 | ||
|
|
06a00cf073 | ||
|
|
eb5d87cd94 | ||
|
|
12183b7e6c | ||
|
|
74883e535f | ||
|
|
cbfd71cec6 | ||
|
|
977f0a5b5c | ||
|
|
67cda0cd75 | ||
|
|
b3af5590f9 | ||
|
|
34eb4fdec5 | ||
|
|
56cedc5d4f | ||
|
|
77d03ec745 | ||
|
|
6f1107c4ee | ||
|
|
1659fab44f | ||
|
|
d0892b21fb | ||
|
|
6d6a6a7fbd | ||
|
|
e8854b68bb | ||
|
|
cddbbf00b4 | ||
|
|
4b1851467b | ||
|
|
cedff7715e | ||
|
|
97cc49033e | ||
|
|
44557a5afa | ||
|
|
4a39754e76 | ||
|
|
e0872c55a9 | ||
|
|
726383cafb | ||
|
|
de424be9f7 | ||
|
|
33c2ae465d | ||
|
|
abf6b8bee5 | ||
|
|
96b065b282 | ||
|
|
5780f3209c | ||
|
|
a2a400b8fb | ||
|
|
793fe9d2d1 | ||
|
|
9715f3dfcc | ||
|
|
af2f6a6fff | ||
|
|
6fa20062f3 | ||
|
|
32f0fefec7 | ||
|
|
4da0ee9db8 | ||
|
|
5f2922b3a7 | ||
|
|
012367a371 | ||
|
|
acbda4bc0e | ||
|
|
7d64be915c | ||
|
|
f4f32a5213 | ||
|
|
11be4fae86 | ||
|
|
c685255fe3 | ||
|
|
6a428c6064 | ||
|
|
6b3056ff8c | ||
|
|
073f570c67 | ||
|
|
37a409aa1f | ||
|
|
63a543d6a0 | ||
|
|
f41e59258c | ||
|
|
ae5b8178e7 | ||
|
|
5ae40d571a | ||
|
|
8d1be42924 | ||
|
|
6423a33a23 | ||
|
|
545e448024 | ||
|
|
5aded88cf6 | ||
|
|
1e7e8f216a | ||
|
|
ac03c9d851 | ||
|
|
96dbecec24 | ||
|
|
14ff7f50cf | ||
|
|
b6d29c8b72 | ||
|
|
b33612f9e7 | ||
|
|
8625ca5187 | ||
|
|
d92785811b | ||
|
|
272e8aa6f1 | ||
|
|
6a98f0391d | ||
|
|
702b8b3309 | ||
|
|
8c6607532b | ||
|
|
900b55d16a | ||
|
|
f61526d3ff | ||
|
|
4d6dfb2b81 | ||
|
|
652f999e83 | ||
|
|
330f88e44b | ||
|
|
1b9e197ff3 | ||
|
|
af2295b73a | ||
|
|
961bff6a80 | ||
|
|
f91fadd00c | ||
|
|
3d0f44f596 | ||
|
|
71d134fcc0 | ||
|
|
b8ef6cfd2f | ||
|
|
2706812b37 | ||
|
|
5ac291d0f4 | ||
|
|
b3b0f6bd5b | ||
|
|
6fae618669 | ||
|
|
a71f2ba76f | ||
|
|
5f18618355 | ||
|
|
5f891435f1 | ||
|
|
34b6ce9259 | ||
|
|
dbc29d28d2 | ||
|
|
012f33524b | ||
|
|
c236a67096 | ||
|
|
74964e7d85 | ||
|
|
eef81b6eb9 | ||
|
|
3f84be7b7c | ||
|
|
e501137d1a | ||
|
|
192da14842 | ||
|
|
699a6e1783 | ||
|
|
2c90930068 | ||
|
|
6a41b73318 | ||
|
|
e7397ff62e | ||
|
|
a14517dc9d | ||
|
|
354ccdc58b | ||
|
|
ea824f39a9 | ||
|
|
f93f601715 | ||
|
|
19e24eeb23 | ||
|
|
63d86c2e0f | ||
|
|
c162b593be | ||
|
|
a661a7371d | ||
|
|
8f758dc932 | ||
|
|
6f033a2c47 | ||
|
|
c7c6822c56 | ||
|
|
257bf81a3b | ||
|
|
fb055b5eab | ||
|
|
fb96a65733 | ||
|
|
052b8e802f | ||
|
|
16a3d0d769 | ||
|
|
4a358284f6 | ||
|
|
08b66f09b1 | ||
|
|
2a81adc1fc | ||
|
|
f0d2e8d150 | ||
|
|
95b128ce8f | ||
|
|
d49c907934 | ||
|
|
e5efc23642 | ||
|
|
23fb24e2be | ||
|
|
ad5de6f67a | ||
|
|
8a4d1e2cf6 | ||
|
|
1993aecd4c | ||
|
|
6b5188fee8 | ||
|
|
670972d5c3 | ||
|
|
3bc3aeeb52 | ||
|
|
21b70577d3 | ||
|
|
6b94dc61c6 | ||
|
|
bad6593460 |
@@ -1,4 +0,0 @@
|
||||
# for php-coveralls
|
||||
service_name: travis-ci
|
||||
src_dir: lib
|
||||
coverage_clover: build/logs/clover*.xml
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -10,3 +10,4 @@ build.xml export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
phpunit.xml.dist export-ignore
|
||||
run-all.sh export-ignore
|
||||
phpcs.xml.dist export-ignore
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,5 +10,8 @@ lib/Doctrine/DBAL
|
||||
.buildpath
|
||||
.project
|
||||
.idea
|
||||
*.iml
|
||||
vendor/
|
||||
composer.lock
|
||||
/tests/Doctrine/Performance/history.db
|
||||
/.phpcs-cache
|
||||
|
||||
34
.scrutinizer.yml
Normal file
34
.scrutinizer.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
build:
|
||||
nodes:
|
||||
analysis:
|
||||
environment:
|
||||
php:
|
||||
version: 7.1
|
||||
cache:
|
||||
disabled: false
|
||||
directories:
|
||||
- ~/.composer/cache
|
||||
|
||||
project_setup:
|
||||
override: true
|
||||
tests:
|
||||
override:
|
||||
- php-scrutinizer-run
|
||||
|
||||
before_commands:
|
||||
- "composer install --no-dev --prefer-source"
|
||||
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 3600
|
||||
|
||||
filter:
|
||||
excluded_paths:
|
||||
- docs
|
||||
- tools
|
||||
|
||||
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
|
||||
- 'patches.label("Unused Use Statements").new.exists' # No new unused imports patches allowed
|
||||
113
.travis.yml
113
.travis.yml
@@ -1,38 +1,103 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7.0
|
||||
- hhvm
|
||||
- hhvm-nightly
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 7.3
|
||||
- nightly
|
||||
|
||||
env:
|
||||
- DB=sqlite
|
||||
- DB=mysql
|
||||
- DB=pgsql
|
||||
- DB=sqlite
|
||||
|
||||
before_script:
|
||||
- if [[ $TRAVIS_PHP_VERSION = '5.6' && $DB = 'sqlite' ]]; then PHPUNIT_FLAGS="--coverage-clover ./build/logs/clover.xml"; else PHPUNIT_FLAGS=""; fi
|
||||
- if [[ $TRAVIS_PHP_VERSION != '5.6' && $TRAVIS_PHP_VERSION != 'hhvm' && $TRAVIS_PHP_VERSION != 'hhvm-nightly' && $TRAVIS_PHP_VERSION != '7.0' ]]; then phpenv config-rm xdebug.ini; fi
|
||||
before_install:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
|
||||
- composer self-update
|
||||
- composer install --prefer-source --dev
|
||||
|
||||
install: travis_retry composer update --prefer-dist
|
||||
|
||||
script:
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml $PHPUNIT_FLAGS
|
||||
- if [[ "$DB" == "mysql" || "$DB" == "mariadb" ]]; then mysql -e "CREATE SCHEMA doctrine_tests; GRANT ALL PRIVILEGES ON doctrine_tests.* to travis@'%'"; fi
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml
|
||||
- ENABLE_SECOND_LEVEL_CACHE=1 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --exclude-group performance,non-cacheable,locking_functional
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- php: hhvm
|
||||
env: DB=pgsql # driver currently unsupported by HHVM
|
||||
- php: hhvm
|
||||
env: DB=mysqli # driver currently unsupported by HHVM
|
||||
- php: hhvm-nightly
|
||||
env: DB=pgsql # driver currently unsupported by HHVM
|
||||
- php: hhvm-nightly
|
||||
env: DB=mysqli # driver currently unsupported by HHVM
|
||||
jobs:
|
||||
include:
|
||||
- stage: Test
|
||||
env: DB=mariadb
|
||||
addons:
|
||||
mariadb: 10.1
|
||||
|
||||
- stage: Test
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.1
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: 7.2
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
env: DB=mysql MYSQL_VERSION=5.7
|
||||
php: nightly
|
||||
before_script:
|
||||
- ./tests/travis/install-mysql-$MYSQL_VERSION.sh
|
||||
sudo: required
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite DEPENDENCIES=low
|
||||
install: travis_retry composer update --prefer-dist --prefer-lowest
|
||||
|
||||
- stage: Test
|
||||
if: type = cron
|
||||
php: 7.3
|
||||
env: DB=sqlite DEV_DEPENDENCIES
|
||||
install:
|
||||
- composer config minimum-stability dev
|
||||
- travis_retry composer update --prefer-dist
|
||||
|
||||
- stage: Test
|
||||
env: DB=sqlite COVERAGE
|
||||
before_script:
|
||||
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
|
||||
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
|
||||
script:
|
||||
- ENABLE_SECOND_LEVEL_CACHE=0 ./vendor/bin/phpunit -v -c tests/travis/$DB.travis.xml --coverage-clover ./build/logs/clover.xml
|
||||
after_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 STATIC_ANALYSIS
|
||||
install: travis_retry composer update --prefer-dist --prefer-stable
|
||||
before_script:
|
||||
- echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
- echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
|
||||
- travis_retry composer require --dev --prefer-dist --prefer-stable phpstan/phpstan:^0.9
|
||||
script: vendor/bin/phpstan analyse -l 1 -c phpstan.neon lib
|
||||
|
||||
- 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 -l dots --report=default
|
||||
|
||||
- stage: Code Quality
|
||||
env: DB=none CODING_STANDARDS
|
||||
php: nightly
|
||||
script:
|
||||
- ./vendor/bin/phpcs
|
||||
|
||||
allow_failures:
|
||||
- php: 7.0
|
||||
- php: hhvm-nightly # hhvm-nightly currently chokes on composer installation
|
||||
- php: nightly
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
@@ -41,16 +41,29 @@ Please try to add a test for your pull-request.
|
||||
* 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 ``phpunit`` from the root of the project.
|
||||
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 doctrine2, and you
|
||||
will have to run a composer installation in the project:
|
||||
|
||||
```sh
|
||||
git clone git@github.com:doctrine/doctrine2.git
|
||||
cd doctrine2
|
||||
curl -sS https://getcomposer.org/installer | php --
|
||||
./composer.phar install
|
||||
```
|
||||
|
||||
To run the testsuite against another database, copy the ``phpunit.xml.dist``
|
||||
to for example ``mysql.phpunit.xml`` and edit the parameters. You can
|
||||
take a look at the ``tests/travis`` folder for some examples. Then run:
|
||||
|
||||
phpunit -c mysql.phpunit.xml
|
||||
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 unittests:
|
||||
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/doctrine2/tree/master/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC2306Test.php` for an
|
||||
@@ -62,13 +75,6 @@ We automatically run your pull request through [Travis CI](http://www.travis-ci.
|
||||
against SQLite, MySQL and PostgreSQL. If you break the tests, we cannot merge your code,
|
||||
so please make sure that your code is working before opening up a Pull-Request.
|
||||
|
||||
## DoctrineBot, Tickets and Jira
|
||||
|
||||
DoctrineBot will synchronize your Pull-Request into our [Jira](http://www.doctrine-project.org).
|
||||
Make sure to add any existing Jira ticket into the Pull-Request Title, for example:
|
||||
|
||||
"[DDC-123] My Pull Request"
|
||||
|
||||
## Getting merged
|
||||
|
||||
Please allow us time to review your pull requests. We will give our best to review
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2006-2012 Doctrine Project
|
||||
Copyright (c) 2006-2015 Doctrine Project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
| [Master][Master] | [2.4][2.4] | [2.3][2.3] | [2.2][2.2] | [2.1][2.1] |
|
||||
|:----------------:|:----------:|:----------:|:----------:|:----------:|
|
||||
| [![Build status][Master image]][Master] | [![Build status][2.4 image]][2.4] | [![Build status][2.3 image]][2.3] | [![Build status][2.2 image]][2.2] | [![Build status][2.1 image]][2.1] |
|
||||
| [![Coverage Status][Master coverage image]][Master coverage] |
|
||||
|
||||
Doctrine 2 is an object-relational mapper (ORM) for PHP 5.4+ 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.
|
||||
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org)
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DDC)
|
||||
* [Downloads](http://github.com/doctrine/doctrine2/downloads)
|
||||
|
||||
[Master image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=master
|
||||
[Master]: https://travis-ci.org/doctrine/doctrine2
|
||||
[Master coverage image]: https://coveralls.io/repos/doctrine/doctrine2/badge.png?branch=master
|
||||
[Master coverage]: https://coveralls.io/r/doctrine/doctrine2?branch=master
|
||||
[2.4 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.4
|
||||
[2.4]: https://github.com/doctrine/doctrine2/tree/2.4
|
||||
[2.3 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.3
|
||||
[2.3]: https://github.com/doctrine/doctrine2/tree/2.3
|
||||
[2.2 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.2
|
||||
[2.2]: https://github.com/doctrine/doctrine2/tree/2.2
|
||||
[2.1 image]: https://travis-ci.org/doctrine/doctrine2.svg?branch=2.1.x
|
||||
[2.1]: https://github.com/doctrine/doctrine2/tree/2.1.x
|
||||
26
README.md
Normal file
26
README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
| [Master][Master] | [2.5][2.5] |
|
||||
|:----------------:|:----------:|
|
||||
| [![Build status][Master image]][Master] | [![Build status][2.5 image]][2.5] |
|
||||
| [![Coverage Status][Master coverage image]][Master coverage] | [![Coverage Status][2.5 coverage image]][2.5 coverage] |
|
||||
|
||||
Doctrine 2 is an object-relational mapper (ORM) for PHP 7.1+ that provides transparent persistence
|
||||
for PHP objects. It sits on top of a powerful database abstraction layer (DBAL). One of its key features
|
||||
is the option to write database queries in a proprietary object oriented SQL dialect called Doctrine Query Language (DQL),
|
||||
inspired by Hibernate's HQL. This provides developers with a powerful alternative to SQL that maintains flexibility
|
||||
without requiring unnecessary code duplication.
|
||||
|
||||
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org)
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/index.html)
|
||||
|
||||
|
||||
[Master image]: https://img.shields.io/travis/doctrine/doctrine2/master.svg?style=flat-square
|
||||
[Master]: https://travis-ci.org/doctrine/doctrine2
|
||||
[Master coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/master.svg?style=flat-square
|
||||
[Master coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=master
|
||||
[2.5 image]: https://img.shields.io/travis/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5]: https://github.com/doctrine/doctrine2/tree/2.5
|
||||
[2.5 coverage image]: https://img.shields.io/scrutinizer/coverage/g/doctrine/doctrine2/2.5.svg?style=flat-square
|
||||
[2.5 coverage]: https://scrutinizer-ci.com/g/doctrine/doctrine2/?branch=2.5
|
||||
54
UPGRADE.md
54
UPGRADE.md
@@ -1,5 +1,59 @@
|
||||
# Upgrade to 2.6
|
||||
|
||||
## Added `Doctrine\ORM\EntityRepository::count()` method
|
||||
|
||||
`Doctrine\ORM\EntityRepository::count()` has been added. This new method has different
|
||||
signature than `Countable::count()` (required parameter) and therefore are not compatible.
|
||||
If your repository implemented the `Countable` interface, you will have to use
|
||||
`$repository->count([])` instead and not implement `Countable` interface anymore.
|
||||
|
||||
## Minor BC BREAK: `Doctrine\ORM\Tools\Console\ConsoleRunner` is now final
|
||||
|
||||
Since it's just an utilitarian class and should not be inherited.
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
|
||||
|
||||
Method `Doctrine\ORM\Query\QueryException::associationPathInverseSideNotSupported()`
|
||||
now has a required parameter `$pathExpr`.
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\Query\Parser#isInternalFunction()`
|
||||
|
||||
Method `Doctrine\ORM\Query\Parser#isInternalFunction()` was removed because
|
||||
the distinction between internal function and user defined DQL was removed.
|
||||
[#6500](https://github.com/doctrine/doctrine2/pull/6500)
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\ORMException#overwriteInternalDQLFunctionNotAllowed()`
|
||||
|
||||
Method `Doctrine\ORM\Query\Parser#overwriteInternalDQLFunctionNotAllowed()` was
|
||||
removed because of the choice to allow users to overwrite internal functions, ie
|
||||
`AVG`, `SUM`, `COUNT`, `MIN` and `MAX`. [#6500](https://github.com/doctrine/doctrine2/pull/6500)
|
||||
|
||||
## PHP 7.1 is now required
|
||||
|
||||
Doctrine 2.6 now requires PHP 7.1 or newer.
|
||||
|
||||
As a consequence, automatic cache setup in Doctrine\ORM\Tools\Setup::create*Configuration() was changed:
|
||||
- APCu extension (ext-apcu) will now be used instead of abandoned APC (ext-apc).
|
||||
- Memcached extension (ext-memcached) will be used instead of obsolete Memcache (ext-memcache).
|
||||
- XCache support was dropped as it doesn't work with PHP 7.
|
||||
|
||||
# Upgrade to 2.5
|
||||
|
||||
## Minor BC BREAK: removed `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()`
|
||||
|
||||
Method `Doctrine\ORM\Query\SqlWalker#walkCaseExpression()` was unused and part
|
||||
of the internal API of the ORM, so it was removed. [#5600](https://github.com/doctrine/doctrine2/pull/5600).
|
||||
|
||||
## Minor BC BREAK: removed $className parameter on `AbstractEntityInheritancePersister#getSelectJoinColumnSQL()`
|
||||
|
||||
As `$className` parameter was not used in the method, it was safely removed.
|
||||
|
||||
## Minor BC BREAK: query cache key time is now a float
|
||||
|
||||
As of 2.5.5, the `QueryCacheEntry#time` property will contain a float value
|
||||
instead of an integer in order to have more precision and also to be consistent
|
||||
with the `TimestampCacheEntry#time`.
|
||||
|
||||
## Minor BC BREAK: discriminator map must now include all non-transient classes
|
||||
|
||||
It is now required that you declare the root of an inheritance in the
|
||||
|
||||
@@ -31,7 +31,7 @@ $helperSet = null;
|
||||
if (file_exists($configFile)) {
|
||||
if ( ! is_readable($configFile)) {
|
||||
trigger_error(
|
||||
'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR
|
||||
'Configuration file [' . $configFile . '] does not have read permission.', E_USER_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
13
bin/doctrine.php
Executable file → Normal file
13
bin/doctrine.php
Executable file → Normal file
@@ -20,16 +20,19 @@
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||
|
||||
$autoloadFiles = array(__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/../../../autoload.php');
|
||||
$autoloadFiles = [
|
||||
__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/../../../autoload.php'
|
||||
];
|
||||
|
||||
foreach ($autoloadFiles as $autoloadFile) {
|
||||
if (file_exists($autoloadFile)) {
|
||||
require_once $autoloadFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$directories = array(getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config');
|
||||
$directories = [getcwd(), getcwd() . DIRECTORY_SEPARATOR . 'config'];
|
||||
|
||||
$configFile = null;
|
||||
foreach ($directories as $directory) {
|
||||
@@ -50,7 +53,7 @@ if ( ! is_readable($configFile)) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$commands = array();
|
||||
$commands = [];
|
||||
|
||||
$helperSet = require $configFile;
|
||||
|
||||
@@ -63,4 +66,4 @@ if ( ! ($helperSet instanceof HelperSet)) {
|
||||
}
|
||||
}
|
||||
|
||||
\Doctrine\ORM\Tools\Console\ConsoleRunner::run($helperSet, $commands);
|
||||
ConsoleRunner::run($helperSet, $commands);
|
||||
|
||||
@@ -9,39 +9,48 @@
|
||||
{"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": "Jonathan Wage", "email": "jonwage@gmail.com"},
|
||||
{"name": "Marco Pivetta", "email": "ocramius@gmail.com"}
|
||||
],
|
||||
"minimum-stability": "dev",
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4",
|
||||
"php": "^7.1",
|
||||
"ext-pdo": "*",
|
||||
"doctrine/collections": "~1.2",
|
||||
"doctrine/dbal": ">=2.5-dev,<2.6-dev",
|
||||
"doctrine/instantiator": "~1.0.1",
|
||||
"doctrine/common": ">=2.5-dev,<2.7-dev",
|
||||
"doctrine/cache": "~1.4",
|
||||
"symfony/console": "~2.5|~3.0"
|
||||
"doctrine/annotations": "~1.5",
|
||||
"doctrine/cache": "~1.6",
|
||||
"doctrine/collections": "^1.4",
|
||||
"doctrine/common": "^2.7.1",
|
||||
"doctrine/dbal": "^2.6",
|
||||
"doctrine/instantiator": "~1.1",
|
||||
"symfony/console": "~3.0|~4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/yaml": "~2.3|~3.0",
|
||||
"phpunit/phpunit": "~4.0"
|
||||
"doctrine/coding-standard": "^1.0",
|
||||
"phpunit/phpunit": "^6.5",
|
||||
"squizlabs/php_codesniffer": "^3.2",
|
||||
"symfony/yaml": "~3.4|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "If you want to use YAML Metadata Mapping Driver"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Doctrine\\ORM\\": "lib/" }
|
||||
"psr-4": { "Doctrine\\ORM\\": "lib/Doctrine/ORM" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-0": { "Doctrine\\Tests\\": "tests/" }
|
||||
"psr-4": {
|
||||
"Doctrine\\Tests\\": "tests/Doctrine/Tests",
|
||||
"Doctrine\\Performance\\": "tests/Doctrine/Performance"
|
||||
}
|
||||
},
|
||||
"bin": ["bin/doctrine", "bin/doctrine.php"],
|
||||
"bin": ["bin/doctrine"],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.6.x-dev"
|
||||
}
|
||||
},
|
||||
"archive": {
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp", "*coveralls.yml"]
|
||||
"exclude": ["!vendor", "tests", "*phpunit.xml", ".travis.yml", "build.xml", "build.properties", "composer.phar", "vendor/satooshi", "lib/vendor", "*.swp"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
# Doctrine ORM Documentation
|
||||
|
||||
## How to Generate
|
||||
## How to Generate:
|
||||
Using Ubuntu 14.04 LTS:
|
||||
|
||||
1. Run ./bin/install-dependencies.sh
|
||||
2. Run ./bin/generate-docs.sh
|
||||
|
||||
It will generate the documentation into the build directory of the checkout.
|
||||
It will generate the documentation into the build directory of the checkout.
|
||||
|
||||
|
||||
## Theme issues
|
||||
|
||||
If you get a "Theme error", check if the `en/_theme` subdirectory is empty,
|
||||
in which case you will need to run:
|
||||
|
||||
1. git submodule init
|
||||
2. git submodule update
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
#!/bin/bash
|
||||
sudo apt-get install python25 python25-dev texlive-full rubber
|
||||
sudo easy_install pygments
|
||||
sudo easy_install sphinx
|
||||
sudo apt-get update && sudo apt-get install -y python2.7 python-sphinx python-pygments
|
||||
Submodule docs/en/_theme updated: dc294be1db...6f1bc8bead
@@ -1,9 +1,9 @@
|
||||
What is new in Doctrine ORM 2.5?
|
||||
================================
|
||||
|
||||
This document describes changes between Doctrine ORM 2.4 and 2.5 (currently in
|
||||
Beta). It contains a description of all the new features and sections
|
||||
about behavioral changes and potential backwards compatibility breaks.
|
||||
This document describes changes between Doctrine ORM 2.4 and 2.5.
|
||||
It contains a description of all the new features and sections about
|
||||
behavioral changes and potential backwards compatibility breaks.
|
||||
Please review this document carefully when updating to Doctrine 2.5.
|
||||
|
||||
First note, that with the ORM 2.5 release we are dropping support
|
||||
@@ -21,7 +21,7 @@ defined then Doctrine would trigger listeners after the fields were
|
||||
loaded, but before assocations are available.
|
||||
|
||||
- `DDC-54 <http://doctrine-project.org/jira/browse/DDC-54>`_
|
||||
- `Commit <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
|
||||
- `Commit #a90629 <https://github.com/doctrine/doctrine2/commit/a906295c65f1516737458fbee2f6fa96254f27a5>`_
|
||||
|
||||
Events: Add API to programatically add event listeners to Entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -58,11 +58,11 @@ metadata generation:
|
||||
}
|
||||
}
|
||||
|
||||
Embeddedable Objects
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Embeddable Objects
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Doctrine now supports creating multiple PHP objects from one database table
|
||||
implementing a feature called "Embeddedable Objects". Next to an ``@Entity``
|
||||
implementing a feature called "Embeddable Objects". Next to an ``@Entity``
|
||||
class you can now define a class that is embeddable into a database table of an
|
||||
entity using the ``@Embeddable`` annotation. Embeddable objects can never be
|
||||
saved, updated or deleted on their own, only as part of an entity (called
|
||||
@@ -102,7 +102,7 @@ This feature was developed by external contributor `Johannes Schmitt
|
||||
<https://twitter.com/schmittjoh>`_
|
||||
|
||||
- `DDC-93 <http://doctrine-project.org/jira/browse/DDC-93>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/835>`_
|
||||
- `Pull Request #835 <https://github.com/doctrine/doctrine2/pull/835>`_
|
||||
|
||||
Second-Level-Cache
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
@@ -160,7 +160,7 @@ instead of the database.
|
||||
|
||||
- `Documentation
|
||||
<http://docs.doctrine-project.org/en/latest/reference/second-level-cache.html>`_
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/808>`_
|
||||
- `Pull Request #808 <https://github.com/doctrine/doctrine2/pull/808>`_
|
||||
|
||||
Criteria API: Support for ManyToMany assocations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -217,8 +217,8 @@ trigger a full load of the collection.
|
||||
|
||||
This feature was contributed by `Michaël Gallego <https://github.com/bakura10>`_.
|
||||
|
||||
- `Pull Request #1 <https://github.com/doctrine/doctrine2/pull/882>`_
|
||||
- `Pull Request #2 <https://github.com/doctrine/doctrine2/pull/1032>`_
|
||||
- `Pull Request #882 <https://github.com/doctrine/doctrine2/pull/882>`_
|
||||
- `Pull Request #1032 <https://github.com/doctrine/doctrine2/pull/1032>`_
|
||||
|
||||
Mapping: Allow configuring Index flags
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -241,7 +241,7 @@ only with a schema event listener before.
|
||||
|
||||
This feature was contributed by `Adrian Olek <https://github.com/adrianolek>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
- `Pull Request #973 <https://github.com/doctrine/doctrine2/pull/973>`_
|
||||
|
||||
SQLFilter API: Check if a parameter is set
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -272,7 +272,7 @@ Extending on the locale example of the documentation:
|
||||
|
||||
This feature was contributed by `Miroslav Demovic <https://github.com/mdemo>`_
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
- `Pull Request #963 <https://github.com/doctrine/doctrine2/pull/963>`_
|
||||
|
||||
|
||||
EXTRA_LAZY Improvements
|
||||
@@ -301,7 +301,7 @@ EXTRA_LAZY Improvements
|
||||
|
||||
This feature was contributed by `Asmir Mustafic <https://github.com/goetas>`_
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
- `Pull Request #937 <https://github.com/doctrine/doctrine2/pull/937>`_
|
||||
|
||||
2. Add EXTRA_LAZY Support for get() for owning and inverse many-to-many
|
||||
|
||||
@@ -415,11 +415,11 @@ object:
|
||||
This feature was contributed by `Michael Perrin
|
||||
<https://github.com/michaelperrin>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/590>`_
|
||||
- `Pull Request #590 <https://github.com/doctrine/doctrine2/pull/590>`_
|
||||
- `DDC-2319 <http://doctrine-project.org/jira/browse/DDC-2319>`_
|
||||
|
||||
Query API: Add suport for default Query Hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Query API: Add support for default Query Hints
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To configure multiple different features such as custom AST Walker, fetch modes,
|
||||
locking and other features affecting DQL generation we have had a feature
|
||||
@@ -439,7 +439,7 @@ It is now possible to add query hints that are always enabled for every Query:
|
||||
This feature was contributed by `Artur Eshenbrener
|
||||
<https://github.com/Strate>`_.
|
||||
|
||||
- `Pull Request <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
- `Pull Request #863 <https://github.com/doctrine/doctrine2/pull/863>`_
|
||||
|
||||
ResultSetMappingBuilder: Add support for Single-Table Inheritance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -486,8 +486,8 @@ EntityGenerator Command: Avoid backups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When calling the EntityGenerator for an existing entity, Doctrine would
|
||||
create a backup file every time to avoid loosing changes to the code.
|
||||
You can now skip generating the backup file by passing the ``--no-backup``
|
||||
create a backup file every time to avoid losing changes to the code. You
|
||||
can now skip generating the backup file by passing the ``--no-backup``
|
||||
flag:
|
||||
|
||||
::
|
||||
@@ -708,3 +708,4 @@ From now on, the resultset will look like this:
|
||||
),
|
||||
...
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import sys, os, datetime
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -38,7 +38,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Doctrine 2 ORM'
|
||||
copyright = u'2010-12, Doctrine Project Team'
|
||||
copyright = u'2010-%y, Doctrine Project Team'.format(datetime.date.today)
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
|
||||
@@ -322,7 +322,7 @@ The aggregate field ``Account::$balance`` is now -200, however the
|
||||
SUM over all entries amounts yields -400. A violation of our max
|
||||
credit rule.
|
||||
|
||||
You can use both optimistic or pessimistic locking to save-guard
|
||||
You can use both optimistic or pessimistic locking to safe-guard
|
||||
your aggregate fields against this kind of race-conditions. Reading
|
||||
Eric Evans DDD carefully he mentions that the "Aggregate Root"
|
||||
(Account in our example) needs a locking mechanism.
|
||||
|
||||
@@ -49,8 +49,6 @@ you wish. Here is an example skeleton of such a custom type class:
|
||||
|
||||
The following assumptions are applied to mapping types by the ORM:
|
||||
|
||||
- If the value of the field is *NULL* the method
|
||||
``convertToDatabaseValue()`` is not called.
|
||||
- The ``UnitOfWork`` never passes values to the database convert
|
||||
method that did not change in the request.
|
||||
- The ``UnitOfWork`` internally assumes that entity identifiers are
|
||||
|
||||
@@ -132,7 +132,7 @@ dql statement.
|
||||
|
||||
The ``ArithmeticPrimary`` method call is the most common
|
||||
denominator of valid EBNF tokens taken from the
|
||||
`DQL EBNF grammar <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
|
||||
`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
|
||||
|
||||
@@ -4,7 +4,7 @@ Implementing Wakeup or Clone
|
||||
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||
|
||||
As explained in the
|
||||
`restrictions for entity classes in the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
|
||||
`restrictions for entity classes in the manual <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/architecture.html#entities>`_,
|
||||
it is usually not allowed for an entity to implement ``__wakeup``
|
||||
or ``__clone``, because Doctrine makes special use of them.
|
||||
However, it is quite easy to make use of these methods in a safe
|
||||
|
||||
@@ -111,7 +111,7 @@ APC, get rid of EchoSqlLogger, and turn off
|
||||
autoGenerateProxyClasses.
|
||||
|
||||
For more details, consult the
|
||||
`Doctrine 2 Configuration documentation <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
|
||||
`Doctrine 2 Configuration documentation <http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/configuration.html>`_.
|
||||
|
||||
Now to use it
|
||||
-------------
|
||||
|
||||
@@ -98,7 +98,7 @@ For example for the previous enum type:
|
||||
|
||||
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||
{
|
||||
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
|
||||
return "ENUM('visible', 'invisible')";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
@@ -118,6 +118,11 @@ For example for the previous enum type:
|
||||
{
|
||||
return self::ENUM_VISIBILITY;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
|
||||
@@ -152,7 +157,7 @@ You can generalize this approach easily to create a base class for enums:
|
||||
{
|
||||
$values = array_map(function($val) { return "'".$val."'"; }, $this->values);
|
||||
|
||||
return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'";
|
||||
return "ENUM(".implode(", ", $values).")";
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
@@ -172,6 +177,11 @@ You can generalize this approach easily to create a base class for enums:
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function requiresSQLCommentHint(AbstractPlatform $platform)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
With this base class you can define an enum as easily as:
|
||||
|
||||
@@ -41,7 +41,9 @@ appropriate autoloaders.
|
||||
$classMetadata = $eventArgs->getClassMetadata();
|
||||
|
||||
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
|
||||
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
|
||||
$classMetadata->setPrimaryTable([
|
||||
'name' => $this->prefix . $classMetadata->getTableName()
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
|
||||
|
||||
@@ -89,7 +89,7 @@ events for one method, this will happen before Beta 1 though.
|
||||
Now validation is performed whenever you call
|
||||
``EntityManager#persist($order)`` or when you call
|
||||
``EntityManager#flush()`` and an order is about to be updated. Any
|
||||
Exception that happens in the lifecycle callbacks will be cached by
|
||||
Exception that happens in the lifecycle callbacks will be caught by
|
||||
the EntityManager and the current transaction is rolled back.
|
||||
|
||||
Of course you can do any type of primitive checks, not null,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Working with DateTime Instances
|
||||
===============================
|
||||
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner
|
||||
There are many nitty gritty details when working with PHPs DateTime instances. You have to know their inner
|
||||
workings pretty well not to make mistakes with date handling. This cookbook entry holds several
|
||||
interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2.
|
||||
|
||||
@@ -49,14 +49,15 @@ By default Doctrine assumes that you are working with a default timezone. Each D
|
||||
is created by Doctrine will be assigned the timezone that is currently the default, either through
|
||||
the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``.
|
||||
|
||||
This is very important to handle correctly if your application runs on different serves or is moved from one to another server
|
||||
This is very important to handle correctly if your application runs on different servers or is moved from one to another server
|
||||
(with different timezone settings). You have to make sure that the timezone is the correct one
|
||||
on all this systems.
|
||||
|
||||
Handling different Timezones with the DateTime Type
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you first come across the requirement to save different you are still optimistic to manage this mess,
|
||||
If you first come across the requirement to save different timezones you may be still optimistic about how
|
||||
to manage this mess,
|
||||
however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2)
|
||||
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
|
||||
@@ -85,43 +86,63 @@ the UTC time at the time of the booking and the timezone the event happened in.
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\ConversionException;
|
||||
use Doctrine\DBAL\Types\DateTimeType;
|
||||
|
||||
class UTCDateTimeType extends DateTimeType
|
||||
{
|
||||
static private $utc = null;
|
||||
static private $utc;
|
||||
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
if ($value instanceof \DateTime) {
|
||||
$value->setTimezone(self::getUtc());
|
||||
}
|
||||
|
||||
|
||||
return $value->format($platform->getDateTimeFormatString(),
|
||||
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
|
||||
);
|
||||
return parent::convertToDatabaseValue($value, $platform);
|
||||
}
|
||||
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
if (null === $value || $value instanceof \DateTime) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$val = \DateTime::createFromFormat(
|
||||
$converted = \DateTime::createFromFormat(
|
||||
$platform->getDateTimeFormatString(),
|
||||
$value,
|
||||
(self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC'))
|
||||
self::$utc ? self::$utc : self::$utc = new \DateTimeZone('UTC')
|
||||
);
|
||||
if (!$val) {
|
||||
throw ConversionException::conversionFailed($value, $this->getName());
|
||||
|
||||
if (! $converted) {
|
||||
throw ConversionException::conversionFailedFormat(
|
||||
$value,
|
||||
$this->getName(),
|
||||
$platform->getDateTimeFormatString()
|
||||
);
|
||||
}
|
||||
return $val;
|
||||
|
||||
return $converted;
|
||||
}
|
||||
}
|
||||
|
||||
This database type makes sure that every DateTime instance is always saved in UTC, relative
|
||||
to the current timezone that the passed DateTime instance has. To be able to transform these values
|
||||
to the current timezone that the passed DateTime instance has.
|
||||
|
||||
To actually use this new type instead of the default ``datetime`` type, you need to run following
|
||||
code before bootstrapping the ORM:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use DoctrineExtensions\DBAL\Types\UTCDateTimeType;
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -152,6 +152,7 @@ The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\ApcuCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
@@ -183,6 +184,7 @@ The recommended implementations for production are:
|
||||
|
||||
|
||||
- ``Doctrine\Common\Cache\ApcCache``
|
||||
- ``Doctrine\Common\Cache\ApcuCache``
|
||||
- ``Doctrine\Common\Cache\MemcacheCache``
|
||||
- ``Doctrine\Common\Cache\XcacheCache``
|
||||
- ``Doctrine\Common\Cache\RedisCache``
|
||||
@@ -394,7 +396,7 @@ means that you have to register a special autoloader for these classes:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\ORM\Proxy\Autoloader;
|
||||
use Doctrine\Common\Proxy\Autoloader;
|
||||
|
||||
$proxyDir = "/path/to/proxies";
|
||||
$proxyNamespace = "MyProxies";
|
||||
|
||||
@@ -37,8 +37,11 @@ Index
|
||||
- :ref:`@ColumnResult <annref_column_result>`
|
||||
- :ref:`@Cache <annref_cache>`
|
||||
- :ref:`@ChangeTrackingPolicy <annref_changetrackingpolicy>`
|
||||
- :ref:`@CustomIdGenerator <annref_customidgenerator>`
|
||||
- :ref:`@DiscriminatorColumn <annref_discriminatorcolumn>`
|
||||
- :ref:`@DiscriminatorMap <annref_discriminatormap>`
|
||||
- :ref:`@Embeddable <annref_embeddable>`
|
||||
- :ref:`@Embedded <annref_embedded>`
|
||||
- :ref:`@Entity <annref_entity>`
|
||||
- :ref:`@EntityResult <annref_entity_result>`
|
||||
- :ref:`@FieldResult <annref_field_result>`
|
||||
@@ -110,7 +113,7 @@ Optional attributes:
|
||||
- **unique**: Boolean value to determine if the value of the column
|
||||
should be unique across all rows of the underlying entities table.
|
||||
|
||||
- **nullable**: Determines if NULL values allowed for this column.
|
||||
- **nullable**: Determines if NULL values allowed for this column. If not specified, default value is false.
|
||||
|
||||
- **options**: Array of additional options:
|
||||
|
||||
@@ -131,6 +134,9 @@ Optional attributes:
|
||||
|
||||
- ``collation``: The collation of the column (only supported by Drizzle, Mysql, PostgreSQL>=9.1, Sqlite and SQLServer).
|
||||
|
||||
- ``check``: Adds a check constraint type to the column (might not
|
||||
be supported by all vendors).
|
||||
|
||||
- **columnDefinition**: DDL SQL snippet that starts after the column
|
||||
name and specifies the complete (non-portable!) column definition.
|
||||
This attribute allows to make use of advanced RMDBS features.
|
||||
@@ -231,16 +237,43 @@ Example:
|
||||
*/
|
||||
class User {}
|
||||
|
||||
.. _annref_customidgenerator:
|
||||
|
||||
@CustomIdGenerator
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This annotations allows you to specify a user-provided class to generate identifiers. This annotation only works when both :ref:`@Id <annref_id>` and :ref:`@GeneratedValue(strategy="CUSTOM") <annref_generatedvalue>` are specified.
|
||||
|
||||
Required attributes:
|
||||
|
||||
- **class**: name of the class which should extend Doctrine\ORM\Id\AbstractIdGenerator
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue(strategy="CUSTOM")
|
||||
* @CustomIdGenerator(class="My\Namespace\MyIdGenerator")
|
||||
*/
|
||||
public $id;
|
||||
|
||||
.. _annref_discriminatorcolumn:
|
||||
|
||||
@DiscriminatorColumn
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This annotation is a required annotation for the topmost/super
|
||||
This annotation is an optional annotation for the topmost/super
|
||||
class of an inheritance hierarchy. It specifies the details of the
|
||||
column which saves the name of the class, which the entity is
|
||||
actually instantiated as.
|
||||
|
||||
If this annotation is not specified, the discriminator column defaults
|
||||
to a string column of length 255 called ``dtype``.
|
||||
|
||||
Required attributes:
|
||||
|
||||
|
||||
@@ -279,6 +312,67 @@ depending on whether the classes are in the namespace or not.
|
||||
// ...
|
||||
}
|
||||
|
||||
|
||||
.. _annref_embeddable:
|
||||
|
||||
@Embeddable
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The embeddable annotation is required on a class, in order to make it
|
||||
embeddable inside an entity. It works together with the :ref:`@Embedded <annref_embedded>`
|
||||
annotation to establish the relationship between the two classes.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @Embeddable
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Embedded(class = "Address")
|
||||
*/
|
||||
private $address;
|
||||
|
||||
|
||||
.. _annref_embedded:
|
||||
|
||||
@Embedded
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The embedded annotation is required on an entity's member variable,
|
||||
in order to specify that it is an embedded class.
|
||||
|
||||
Required attributes:
|
||||
|
||||
- **class**: The embeddable class
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
|
||||
// ...
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* @Embedded(class = "Address")
|
||||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* @Embeddable
|
||||
*/
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
|
||||
|
||||
.. _annref_entity:
|
||||
|
||||
@Entity
|
||||
@@ -357,11 +451,12 @@ conjunction with @Id.
|
||||
If this annotation is not specified with @Id the NONE strategy is
|
||||
used as default.
|
||||
|
||||
Required attributes:
|
||||
Optional attributes:
|
||||
|
||||
|
||||
- **strategy**: Set the name of the identifier generation strategy.
|
||||
Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE.
|
||||
If not specified, default value is AUTO.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -1211,12 +1306,13 @@ Example with partial indexes:
|
||||
.. _annref_version:
|
||||
|
||||
@Version
|
||||
~~~~~~~~~~~~~~
|
||||
~~~~~~~~
|
||||
|
||||
Marker annotation that defines a specified column as version
|
||||
attribute used in an optimistic locking scenario. It only works on
|
||||
:ref:`@Column <annref_column>` annotations that have the type integer or
|
||||
datetime. Combining @Version with :ref:`@Id <annref_id>` is not supported.
|
||||
Marker annotation that defines a specified column as version attribute used in
|
||||
an :ref:`optimistic locking <transactions-and-concurrency_optimistic-locking>`
|
||||
scenario. It only works on :ref:`@Column <annref_column>` annotations that have
|
||||
the type ``integer`` or ``datetime``. Combining ``@Version`` with
|
||||
:ref:`@Id <annref_id>` is not supported.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -16,20 +16,31 @@ This chapter is split into three different sections.
|
||||
- :ref:`association_mapping_defaults` are explained that simplify the use-case examples.
|
||||
- :ref:`collections` are introduced that contain entities in associations.
|
||||
|
||||
One tip for working with relations is to read the relation from left to right, where the left word refers to the current Entity. For example:
|
||||
|
||||
- OneToMany - One instance of the current Entity has Many instances (references) to the refered Entity.
|
||||
- ManyToOne - Many instances of the current Entity refer to One instance of the refered Entity.
|
||||
- OneToOne - One instance of the current Entity refers to One instance of the refered Entity.
|
||||
|
||||
See below for all the possible relations.
|
||||
|
||||
An association is considered to be unidirectional if only one side of the association has
|
||||
a property referring to the other side.
|
||||
|
||||
To gain a full understanding of associations you should also read about :doc:`owning and
|
||||
inverse sides of associations <unitofwork-associations>`
|
||||
|
||||
Many-To-One, Unidirectional
|
||||
---------------------------
|
||||
|
||||
A many-to-one association is the most common association between objects.
|
||||
A many-to-one association is the most common association between objects. Example: Many Users have One Address:
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
@@ -37,11 +48,11 @@ A many-to-one association is the most common association between objects.
|
||||
/**
|
||||
* @ManyToOne(targetEntity="Address")
|
||||
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||
**/
|
||||
*/
|
||||
private $address;
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Address
|
||||
{
|
||||
// ...
|
||||
@@ -96,31 +107,30 @@ One-To-One, Unidirectional
|
||||
--------------------------
|
||||
|
||||
Here is an example of a one-to-one association with a ``Product`` entity that
|
||||
references one ``Shipping`` entity. The ``Shipping`` does not reference back to
|
||||
the ``Product`` so that the reference is said to be unidirectional, in one
|
||||
direction only.
|
||||
references one ``Shipment`` entity.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* @OneToOne(targetEntity="Shipping")
|
||||
* @JoinColumn(name="shipping_id", referencedColumnName="id")
|
||||
**/
|
||||
private $shipping;
|
||||
* One Product has One Shipment.
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private $shipment;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
class Shipping
|
||||
/** @Entity */
|
||||
class Shipment
|
||||
{
|
||||
// ...
|
||||
}
|
||||
@@ -129,8 +139,8 @@ direction only.
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipping" target-entity="Shipping">
|
||||
<join-column name="shipping_id" referenced-column-name="id" />
|
||||
<one-to-one field="shipment" target-entity="Shipment">
|
||||
<join-column name="shipment_id" referenced-column-name="id" />
|
||||
</one-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -140,10 +150,10 @@ direction only.
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipping:
|
||||
targetEntity: Shipping
|
||||
shipment:
|
||||
targetEntity: Shipment
|
||||
joinColumn:
|
||||
name: shipping_id
|
||||
name: shipment_id
|
||||
referencedColumnName: id
|
||||
|
||||
Note that the @JoinColumn is not really necessary in this example,
|
||||
@@ -155,15 +165,15 @@ Generated MySQL Schema:
|
||||
|
||||
CREATE TABLE Product (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
shipping_id INT DEFAULT NULL,
|
||||
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipping_id),
|
||||
shipment_id INT DEFAULT NULL,
|
||||
UNIQUE INDEX UNIQ_6FBC94267FE4B2B (shipment_id),
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
CREATE TABLE Shipping (
|
||||
CREATE TABLE Shipment (
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id);
|
||||
ALTER TABLE Product ADD FOREIGN KEY (shipment_id) REFERENCES Shipment(id);
|
||||
|
||||
One-To-One, Bidirectional
|
||||
-------------------------
|
||||
@@ -172,33 +182,39 @@ Here is a one-to-one relationship between a ``Customer`` and a
|
||||
``Cart``. The ``Cart`` has a reference back to the ``Customer`` so
|
||||
it is bidirectional.
|
||||
|
||||
Here we see the ``mappedBy`` and ``inversedBy`` annotations for the first time.
|
||||
They are used to tell Doctrine which property on the other side refers to the
|
||||
object.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Customer
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Customer has One Cart.
|
||||
* @OneToOne(targetEntity="Cart", mappedBy="customer")
|
||||
**/
|
||||
*/
|
||||
private $cart;
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Cart
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Cart has One Customer.
|
||||
* @OneToOne(targetEntity="Customer", inversedBy="cart")
|
||||
* @JoinColumn(name="customer_id", referencedColumnName="id")
|
||||
**/
|
||||
*/
|
||||
private $customer;
|
||||
|
||||
// ...
|
||||
@@ -251,8 +267,9 @@ Generated MySQL Schema:
|
||||
) ENGINE = InnoDB;
|
||||
ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id);
|
||||
|
||||
See how the foreign key is defined on the owning side of the
|
||||
relation, the table ``Cart``.
|
||||
We had a choice of sides on which to place the ``inversedBy`` attribute. Because it
|
||||
is on the ``Cart``, that is the owning side of the relation, and thus holds the
|
||||
foreign key.
|
||||
|
||||
One-To-One, Self-referencing
|
||||
----------------------------
|
||||
@@ -263,15 +280,16 @@ below.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Student
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* One Student has One Student.
|
||||
* @OneToOne(targetEntity="Student")
|
||||
* @JoinColumn(name="mentor_id", referencedColumnName="id")
|
||||
**/
|
||||
*/
|
||||
private $mentor;
|
||||
|
||||
// ...
|
||||
@@ -294,15 +312,16 @@ With the generated MySQL Schema:
|
||||
One-To-Many, Bidirectional
|
||||
--------------------------
|
||||
|
||||
A one-to-many association has to be bidirectional, unless you are using an
|
||||
additional join-table. This is necessary, because of the foreign key
|
||||
in a one-to-many association being defined on the "many" side. Doctrine
|
||||
needs a many-to-one association that defines the mapping of this
|
||||
foreign key.
|
||||
A one-to-many association has to be bidirectional, unless you are using a
|
||||
join table. This is because the "many" side in a one-to-many association holds
|
||||
the foreign key, making it the owning side. Doctrine needs the "many" side
|
||||
defined in order to understand the association.
|
||||
|
||||
This bidirectional mapping requires the ``mappedBy`` attribute on the
|
||||
``OneToMany`` association and the ``inversedBy`` attribute on the ``ManyToOne``
|
||||
association.
|
||||
"one" side and the ``inversedBy`` attribute on the "many" side.
|
||||
|
||||
This means there is no difference between a bidirectional one-to-many and a
|
||||
bidirectional many-to-one.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -311,13 +330,14 @@ association.
|
||||
<?php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Product
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* One product has many features. This is the inverse side.
|
||||
* @OneToMany(targetEntity="Feature", mappedBy="product")
|
||||
**/
|
||||
*/
|
||||
private $features;
|
||||
// ...
|
||||
|
||||
@@ -326,14 +346,15 @@ association.
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Feature
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many features have one product. This is the owning side.
|
||||
* @ManyToOne(targetEntity="Product", inversedBy="features")
|
||||
* @JoinColumn(name="product_id", referencedColumnName="id")
|
||||
**/
|
||||
*/
|
||||
private $product;
|
||||
// ...
|
||||
}
|
||||
@@ -402,18 +423,19 @@ The following example sets up such a unidirectional one-to-many association:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many User have Many Phonenumbers.
|
||||
* @ManyToMany(targetEntity="Phonenumber")
|
||||
* @JoinTable(name="users_phonenumbers",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="phonenumber_id", referencedColumnName="id", unique=true)}
|
||||
* )
|
||||
**/
|
||||
*/
|
||||
private $phonenumbers;
|
||||
|
||||
public function __construct()
|
||||
@@ -424,7 +446,7 @@ The following example sets up such a unidirectional one-to-many association:
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Phonenumber
|
||||
{
|
||||
// ...
|
||||
@@ -503,19 +525,21 @@ database perspective is known as an adjacency list approach.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Category
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* One Category has Many Categories.
|
||||
* @OneToMany(targetEntity="Category", mappedBy="parent")
|
||||
**/
|
||||
*/
|
||||
private $children;
|
||||
|
||||
/**
|
||||
* Many Categories have One Category.
|
||||
* @ManyToOne(targetEntity="Category", inversedBy="children")
|
||||
* @JoinColumn(name="parent_id", referencedColumnName="id")
|
||||
**/
|
||||
*/
|
||||
private $parent;
|
||||
// ...
|
||||
|
||||
@@ -572,18 +596,19 @@ entities:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @JoinTable(name="users_groups",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
|
||||
* )
|
||||
**/
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
// ...
|
||||
@@ -593,7 +618,7 @@ entities:
|
||||
}
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Group
|
||||
{
|
||||
// ...
|
||||
@@ -672,15 +697,16 @@ one is bidirectional.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group", inversedBy="users")
|
||||
* @JoinTable(name="users_groups")
|
||||
**/
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
public function __construct() {
|
||||
@@ -690,13 +716,14 @@ one is bidirectional.
|
||||
// ...
|
||||
}
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class Group
|
||||
{
|
||||
// ...
|
||||
/**
|
||||
* Many Groups have Many Users.
|
||||
* @ManyToMany(targetEntity="User", mappedBy="groups")
|
||||
**/
|
||||
*/
|
||||
private $users;
|
||||
|
||||
public function __construct() {
|
||||
@@ -754,22 +781,22 @@ one is bidirectional.
|
||||
The MySQL schema is exactly the same as for the Many-To-Many
|
||||
uni-directional case above.
|
||||
|
||||
Owning and Inverse Side on a ManyToMany association
|
||||
Owning and Inverse Side on a ManyToMany Association
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For Many-To-Many associations you can chose which entity is the
|
||||
owning and which the inverse side. There is a very simple semantic
|
||||
rule to decide which side is more suitable to be the owning side
|
||||
from a developers perspective. You only have to ask yourself, which
|
||||
entity is responsible for the connection management and pick that
|
||||
from a developers perspective. You only have to ask yourself which
|
||||
entity is responsible for the connection management, and pick that
|
||||
as the owning side.
|
||||
|
||||
Take an example of two entities ``Article`` and ``Tag``. Whenever
|
||||
you want to connect an Article to a Tag and vice-versa, it is
|
||||
mostly the Article that is responsible for this relation. Whenever
|
||||
you add a new article, you want to connect it with existing or new
|
||||
tags. Your create Article form will probably support this notion
|
||||
and allow to specify the tags directly. This is why you should pick
|
||||
tags. Your "Create Article" form will probably support this notion
|
||||
and allow specifying the tags directly. This is why you should pick
|
||||
the Article as owning side, as it makes the code more
|
||||
understandable:
|
||||
|
||||
@@ -819,23 +846,25 @@ field named ``$friendsWithMe`` and ``$myFriends``.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
|
||||
/**
|
||||
* Many Users have Many Users.
|
||||
* @ManyToMany(targetEntity="User", mappedBy="myFriends")
|
||||
**/
|
||||
*/
|
||||
private $friendsWithMe;
|
||||
|
||||
/**
|
||||
* Many Users have many Users.
|
||||
* @ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
|
||||
* @JoinTable(name="friends",
|
||||
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="friend_user_id", referencedColumnName="id")}
|
||||
* )
|
||||
**/
|
||||
*/
|
||||
private $myFriends;
|
||||
|
||||
public function __construct() {
|
||||
@@ -883,14 +912,14 @@ As an example, consider this mapping:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/** @OneToOne(targetEntity="Shipping") **/
|
||||
private $shipping;
|
||||
/** @OneToOne(targetEntity="Shipment") */
|
||||
private $shipment;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipping" target-entity="Shipping" />
|
||||
<one-to-one field="shipment" target-entity="Shipment" />
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
@@ -899,8 +928,8 @@ As an example, consider this mapping:
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipping:
|
||||
targetEntity: Shipping
|
||||
shipment:
|
||||
targetEntity: Shipment
|
||||
|
||||
This is essentially the same as the following, more verbose,
|
||||
mapping:
|
||||
@@ -911,17 +940,18 @@ mapping:
|
||||
|
||||
<?php
|
||||
/**
|
||||
* @OneToOne(targetEntity="Shipping")
|
||||
* @JoinColumn(name="shipping_id", referencedColumnName="id")
|
||||
**/
|
||||
private $shipping;
|
||||
* One Product has One Shipment.
|
||||
* @OneToOne(targetEntity="Shipment")
|
||||
* @JoinColumn(name="shipment_id", referencedColumnName="id")
|
||||
*/
|
||||
private $shipment;
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity class="Product">
|
||||
<one-to-one field="shipping" target-entity="Shipping">
|
||||
<join-column name="shipping_id" referenced-column-name="id" />
|
||||
<one-to-one field="shipment" target-entity="Shipment">
|
||||
<join-column name="shipment_id" referenced-column-name="id" />
|
||||
</one-to-one>
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
@@ -931,10 +961,10 @@ mapping:
|
||||
Product:
|
||||
type: entity
|
||||
oneToOne:
|
||||
shipping:
|
||||
targetEntity: Shipping
|
||||
shipment:
|
||||
targetEntity: Shipment
|
||||
joinColumn:
|
||||
name: shipping_id
|
||||
name: shipment_id
|
||||
referencedColumnName: id
|
||||
|
||||
The @JoinTable definition used for many-to-many mappings has
|
||||
@@ -948,7 +978,7 @@ similar defaults. As an example, consider this mapping:
|
||||
class User
|
||||
{
|
||||
//...
|
||||
/** @ManyToMany(targetEntity="Group") **/
|
||||
/** @ManyToMany(targetEntity="Group") */
|
||||
private $groups;
|
||||
//...
|
||||
}
|
||||
@@ -980,12 +1010,13 @@ This is essentially the same as the following, more verbose, mapping:
|
||||
{
|
||||
//...
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
* @JoinTable(name="User_Group",
|
||||
* joinColumns={@JoinColumn(name="User_id", referencedColumnName="id")},
|
||||
* inverseJoinColumns={@JoinColumn(name="Group_id", referencedColumnName="id")}
|
||||
* )
|
||||
**/
|
||||
*/
|
||||
private $groups;
|
||||
//...
|
||||
}
|
||||
@@ -1064,12 +1095,17 @@ and ``@ManyToMany`` associations in the constructor of your entities:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/** @Entity **/
|
||||
/** @Entity */
|
||||
class User
|
||||
{
|
||||
/** @ManyToMany(targetEntity="Group") **/
|
||||
/**
|
||||
* Many Users have Many Groups.
|
||||
* @var Collection
|
||||
* @ManyToMany(targetEntity="Group")
|
||||
*/
|
||||
private $groups;
|
||||
|
||||
public function __construct()
|
||||
|
||||
@@ -295,7 +295,8 @@ annotation.
|
||||
class Message
|
||||
{
|
||||
/**
|
||||
* @Id @Column(type="integer")
|
||||
* @Id
|
||||
* @Column(type="integer")
|
||||
* @GeneratedValue
|
||||
*/
|
||||
private $id;
|
||||
@@ -354,6 +355,8 @@ Here is the list of possible generation strategies:
|
||||
strategy does currently not provide full portability and is
|
||||
supported by the following platforms: MySQL/SQLite/SQL Anywhere
|
||||
(AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL).
|
||||
- ``UUID``: Tells Doctrine to use the built-in Universally Unique Identifier
|
||||
generator. This strategy provides full portability.
|
||||
- ``TABLE``: Tells Doctrine to use a separate table for ID
|
||||
generation. This strategy provides full portability.
|
||||
***This strategy is not yet implemented!***
|
||||
@@ -361,6 +364,8 @@ Here is the list of possible generation strategies:
|
||||
thus generated) by your code. The assignment must take place before
|
||||
a new entity is passed to ``EntityManager#persist``. NONE is the
|
||||
same as leaving off the @GeneratedValue entirely.
|
||||
- ``CUSTOM``: With this option, you can use the ``@CustomIdGenerator`` annotation.
|
||||
It will allow you to pass a :doc:`class of your own to generate the identifiers.<_annref_customidgenerator>`
|
||||
|
||||
Sequence Generator
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
@@ -446,7 +451,7 @@ need to access the sequence once to generate the identifiers for
|
||||
Composite Keys
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
with Doctrine 2 you can use composite primary keys, using ``@Id`` on more then
|
||||
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
|
||||
|
||||
@@ -100,7 +100,7 @@ with the batching strategy that was already used for bulk inserts:
|
||||
|
||||
Results may be fully buffered by the database client/ connection allocating
|
||||
additional memory not visible to the PHP process. For large sets this
|
||||
may easily kill the process for no apparant reason.
|
||||
may easily kill the process for no apparent reason.
|
||||
|
||||
|
||||
Bulk Deletes
|
||||
|
||||
@@ -54,7 +54,7 @@ Don't use special characters
|
||||
|
||||
Avoid using any non-ASCII characters in class, field, table or
|
||||
column names. Doctrine itself is not unicode-safe in many places
|
||||
and will not be until PHP itself is fully unicode-aware (PHP6).
|
||||
and will not be until PHP itself is fully unicode-aware.
|
||||
|
||||
Don't use identifier quoting
|
||||
----------------------------
|
||||
|
||||
@@ -13,7 +13,7 @@ Cache Drivers
|
||||
|
||||
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\AbstractCache`` which implements
|
||||
base class ``Doctrine\Common\Cache\CacheProvider`` which implements
|
||||
this interface.
|
||||
|
||||
The interface defines the following public methods for you to implement:
|
||||
@@ -21,10 +21,10 @@ 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
|
||||
- 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 ``AbstractCache`` class which defines a few
|
||||
Each driver extends the ``CacheProvider`` class which defines a few
|
||||
abstract protected methods that each of the drivers must
|
||||
implement:
|
||||
|
||||
@@ -38,9 +38,13 @@ 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 ``AbstractCache`` can build custom functionality on top of
|
||||
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
|
||||
~~~
|
||||
|
||||
@@ -59,6 +63,24 @@ by itself.
|
||||
$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 <http://us2.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
|
||||
~~~~~~~~
|
||||
|
||||
@@ -82,7 +104,7 @@ driver by itself.
|
||||
$cacheDriver->save('cache_id', 'my_data');
|
||||
|
||||
Memcached
|
||||
~~~~~~~~
|
||||
~~~~~~~~~
|
||||
|
||||
Memcached is a more recent and complete alternative extension to
|
||||
Memcache.
|
||||
@@ -283,7 +305,7 @@ use on your ORM configuration.
|
||||
|
||||
<?php
|
||||
$config = new \Doctrine\ORM\Configuration();
|
||||
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
$config->setQueryCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
Result Cache
|
||||
~~~~~~~~~~~~
|
||||
@@ -296,7 +318,7 @@ cache implementation.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
$config->setResultCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
Now when you're executing DQL queries you can configure them to use
|
||||
the result cache.
|
||||
@@ -313,7 +335,7 @@ result cache driver.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache());
|
||||
$query->setResultCacheDriver(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -365,7 +387,7 @@ first.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache());
|
||||
$config->setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcuCache());
|
||||
|
||||
Now the metadata information will only be parsed once and stored in
|
||||
the cache driver.
|
||||
@@ -401,6 +423,39 @@ 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.
|
||||
|
||||
Cache Chaining
|
||||
--------------
|
||||
|
||||
A common pattern is to use a static cache to store data that is
|
||||
requested many times in a single PHP request. Even though this data
|
||||
may be stored in a fast memory cache, often that cache is over a
|
||||
network link leading to sizable network traffic.
|
||||
|
||||
The ChainCache class allows multiple caches to be registered at once.
|
||||
For example, a per-request ArrayCache can be used first, followed by
|
||||
a (relatively) slower MemcacheCache if the ArrayCache misses.
|
||||
ChainCache automatically handles pushing data up to faster caches in
|
||||
the chain and clearing data in the entire stack when it is deleted.
|
||||
|
||||
A ChainCache takes a simple array of CacheProviders in the order that
|
||||
they should be used.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$arrayCache = new \Doctrine\Common\Cache\ArrayCache();
|
||||
$memcache = new Memcache();
|
||||
$memcache->connect('memcache_host', 11211);
|
||||
$chainCache = new \Doctrine\Common\Cache\ChainCache([
|
||||
$arrayCache,
|
||||
$memcache,
|
||||
]);
|
||||
|
||||
ChainCache itself extends the CacheProvider interface, so it is
|
||||
possible to create chains of chains. While this may seem like an easy
|
||||
way to build a simple high-availability cache, ChainCache does not
|
||||
implement any exception handling so using it as a high-availability
|
||||
mechanism is not recommended.
|
||||
|
||||
Cache Slams
|
||||
-----------
|
||||
|
||||
|
||||
@@ -79,6 +79,13 @@ Or if you prefer YAML:
|
||||
$config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
|
||||
.. note::
|
||||
If you want to use yml mapping you should add yaml dependency to your `composer.json`:
|
||||
|
||||
::
|
||||
|
||||
"symfony/yaml": "*"
|
||||
|
||||
Inside the ``Setup`` methods several assumptions are made:
|
||||
|
||||
- If `$isDevMode` is true caching is done in memory with the ``ArrayCache``. Proxy objects are recreated on every request.
|
||||
|
||||
@@ -2,7 +2,7 @@ Doctrine Query Language
|
||||
===========================
|
||||
|
||||
DQL stands for Doctrine Query Language and is an Object
|
||||
Query Language derivate that is very similar to the Hibernate
|
||||
Query Language derivative that is very similar to the Hibernate
|
||||
Query Language (HQL) or the Java Persistence Query Language (JPQL).
|
||||
|
||||
In essence, DQL provides powerful querying capabilities over your
|
||||
@@ -34,9 +34,9 @@ object model.
|
||||
|
||||
DQL SELECT statements are a very powerful way of retrieving parts
|
||||
of your domain model that are not accessible via associations.
|
||||
Additionally they allow to retrieve entities and their associations
|
||||
Additionally they allow you to retrieve entities and their associations
|
||||
in one single SQL select statement which can make a huge difference
|
||||
in performance in contrast to using several queries.
|
||||
in performance compared to using several queries.
|
||||
|
||||
DQL UPDATE and DELETE statements offer a way to execute bulk
|
||||
changes on the entities of your domain model. This is often
|
||||
@@ -49,10 +49,6 @@ SELECT queries
|
||||
DQL SELECT clause
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The select clause of a DQL query specifies what appears in the
|
||||
query result. The composition of all the expressions in the select
|
||||
clause also influences the nature of the query result.
|
||||
|
||||
Here is an example that selects all users with an age > 20:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -83,14 +79,58 @@ Lets examine the query:
|
||||
The result of this query would be a list of User objects where all
|
||||
users are older than 20.
|
||||
|
||||
The SELECT clause allows to specify both class identification
|
||||
variables that signal the hydration of a complete entity class or
|
||||
just fields of the entity using the syntax ``u.name``. Combinations
|
||||
of both are also allowed and it is possible to wrap both fields and
|
||||
identification values into aggregation and DQL functions. Numerical
|
||||
fields can be part of computations using mathematical operations.
|
||||
See the sub-section on `Functions, Operators, Aggregates`_ for
|
||||
more information.
|
||||
Result format
|
||||
~~~~~~~~~~~~~
|
||||
The composition of the expressions in the SELECT clause also
|
||||
influences the nature of the query result. There are three
|
||||
cases:
|
||||
|
||||
**All objects**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u, p, n FROM Users u...
|
||||
|
||||
In this case, the result will be an array of User objects because of
|
||||
the FROM clause, with children ``p`` and ``n`` hydrated because of
|
||||
their inclusion in the SELECT clause.
|
||||
|
||||
**All scalars**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
SELECT u.name, u.address FROM Users u...
|
||||
|
||||
In this case, the result will be an array of arrays. In the example
|
||||
above, each element of the result array would be an array of the
|
||||
scalar name and address values.
|
||||
|
||||
You can select scalars from any entity in the query.
|
||||
|
||||
**Mixed**
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
``SELECT u, p.quantity FROM Users u...``
|
||||
|
||||
Here, the result will again be an array of arrays, with each element
|
||||
being an array made up of a User object and the scalar value
|
||||
``p.quantity``.
|
||||
|
||||
Multiple FROM clauses are allowed, which would cause the result
|
||||
array elements to cycle through the classes included in the
|
||||
multiple FROM clauses.
|
||||
|
||||
.. note::
|
||||
|
||||
You cannot select other entities unless you also select the
|
||||
root of the selection (which is the first entity in FROM).
|
||||
|
||||
For example, ``SELECT p,n FROM Users u...`` would be wrong because
|
||||
``u`` is not part of the SELECT
|
||||
|
||||
Doctrine throws an exception if you violate this constraint.
|
||||
|
||||
|
||||
Joins
|
||||
~~~~~
|
||||
@@ -319,7 +359,8 @@ article-ids:
|
||||
$query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a');
|
||||
$results = $query->getResult(); // array of user ids and every article_id for each user
|
||||
|
||||
Restricting a JOIN clause by additional conditions:
|
||||
Restricting a JOIN clause by additional conditions specified by
|
||||
WITH:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -452,6 +493,18 @@ Joins between entities without associations were not possible until version
|
||||
<?php
|
||||
$query = $em->createQuery('SELECT u FROM User u JOIN Blacklist b WITH u.email = b.email');
|
||||
|
||||
.. note::
|
||||
The differences between WHERE, WITH and HAVING clauses may be
|
||||
confusing.
|
||||
|
||||
- WHERE is applied to the results of an entire query
|
||||
- WITH is applied to a join as an additional condition. For
|
||||
arbitrary joins (SELECT f, b FROM Foo f, Bar b WITH f.id = b.id)
|
||||
the WITH is required, even if it is 1 = 1
|
||||
- HAVING is applied to the results of a query after
|
||||
aggregation (GROUP BY)
|
||||
|
||||
|
||||
Partial Object Syntax
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -486,9 +539,9 @@ You use the partial syntax when joining as well:
|
||||
Using the ``NEW`` operator you can construct Data Transfer Objects (DTOs) directly from DQL queries.
|
||||
|
||||
- When using ``SELECT NEW`` you don't need to specify a mapped entity.
|
||||
- You can specify any PHP class, it's only require that the constructor of this class matches the ``NEW`` statement.
|
||||
- You can specify any PHP class, it only requires that the constructor of this class matches the ``NEW`` statement.
|
||||
- This approach involves determining exactly which columns you really need,
|
||||
and instantiating data-transfer object that containing a constructor with those arguments.
|
||||
and instantiating a data-transfer object that contains a constructor with those arguments.
|
||||
|
||||
If you want to select data-transfer objects you should create a class:
|
||||
|
||||
@@ -601,12 +654,15 @@ The same restrictions apply for the reference of related entities.
|
||||
DQL DELETE statements are ported directly into a
|
||||
Database DELETE statement and therefore bypass any events and checks for the
|
||||
version column if they are not explicitly added to the WHERE clause
|
||||
of the query. Additionally Deletes of specifies entities are *NOT*
|
||||
of the query. Additionally Deletes of specified entities are *NOT*
|
||||
cascaded to related entities even if specified in the metadata.
|
||||
|
||||
|
||||
Functions, Operators, Aggregates
|
||||
--------------------------------
|
||||
It is possible to wrap both fields and identification values into
|
||||
aggregation and DQL functions. Numerical fields can be part of
|
||||
computations using mathematical operations.
|
||||
|
||||
DQL Functions
|
||||
~~~~~~~~~~~~~
|
||||
@@ -719,8 +775,6 @@ classes have to implement the base class :
|
||||
|
||||
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||
{
|
||||
$lexer = $parser->getLexer();
|
||||
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
@@ -911,7 +965,7 @@ An instance of the ``Doctrine\ORM\Query`` class represents a DQL
|
||||
query. You create a Query instance be calling
|
||||
``EntityManager#createQuery($dql)``, passing the DQL query string.
|
||||
Alternatively you can create an empty ``Query`` instance and invoke
|
||||
``Query#setDql($dql)`` afterwards. Here are some examples:
|
||||
``Query#setDQL($dql)`` afterwards. Here are some examples:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -921,9 +975,9 @@ Alternatively you can create an empty ``Query`` instance and invoke
|
||||
// example1: passing a DQL string
|
||||
$q = $em->createQuery('select u from MyProject\Model\User u');
|
||||
|
||||
// example2: using setDql
|
||||
// example2: using setDQL
|
||||
$q = $em->createQuery();
|
||||
$q->setDql('select u from MyProject\Model\User u');
|
||||
$q->setDQL('select u from MyProject\Model\User u');
|
||||
|
||||
Query Result Formats
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -1006,7 +1060,7 @@ structure:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
$dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id";
|
||||
$dql = "SELECT u, 'some scalar string', count(g.id) AS num FROM User u JOIN u.groups g GROUP BY u.id";
|
||||
|
||||
array
|
||||
[0]
|
||||
@@ -1106,6 +1160,22 @@ Object hydration hydrates the result set into the object graph:
|
||||
$query = $em->createQuery('SELECT u FROM CmsUser u');
|
||||
$users = $query->getResult(Query::HYDRATE_OBJECT);
|
||||
|
||||
Sometimes the behavior in the object hydrator can be confusing, which is
|
||||
why we are listing as many of the assumptions here for reference:
|
||||
|
||||
- Objects fetched in a FROM clause are returned as a Set, that means every
|
||||
object is only ever included in the resulting array once. This is the case
|
||||
even when using JOIN or GROUP BY in ways that return the same row for an
|
||||
object multiple times. If the hydrator sees the same object multiple times,
|
||||
then it makes sure it is only returned once.
|
||||
|
||||
- If an object is already in memory from a previous query of any kind, then
|
||||
then the previous object is used, even if the database may contain more
|
||||
recent data. Data from the database is discarded. This even happens if the
|
||||
previous object is still an unloaded proxy.
|
||||
|
||||
This list might be incomplete.
|
||||
|
||||
Array Hydration
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -1382,7 +1452,13 @@ Given that there are 10 users and corresponding addresses in the database the ex
|
||||
SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
|
||||
.. note::
|
||||
Changing the fetch mode during a query is only possible for one-to-one and many-to-one relations.
|
||||
Changing the fetch mode during a query mostly makes sense for one-to-one and many-to-one relations. In that case,
|
||||
all the necessary IDs are available after the root entity (``user`` in the above example) has been loaded. So, one
|
||||
query per association can be executed to fetch all the referred-to entities (``address``).
|
||||
|
||||
For one-to-many relations, changing the fetch mode to eager will cause to execute one query **for every root entity
|
||||
loaded**. This gives no improvement over the ``lazy`` fetch mode which will also initialize the associations on
|
||||
a one-by-one basis once they are accessed.
|
||||
|
||||
|
||||
EBNF
|
||||
@@ -1411,7 +1487,9 @@ Terminals
|
||||
~~~~~~~~~
|
||||
|
||||
|
||||
- identifier (name, email, ...)
|
||||
- identifier (name, email, ...) must match ``[a-z_][a-z0-9_]*``
|
||||
- fully_qualified_name (Doctrine\Tests\Models\CMS\CmsUser) matches PHP's fully qualified class names
|
||||
- aliased_name (CMS:CmsUser) uses two identifiers, one for the namespace alias and one for the class inside it
|
||||
- string ('foo', 'bar''s house', '%ninja%', ...)
|
||||
- char ('/', '\\', ' ', ...)
|
||||
- integer (-1, 0, 1, 34, ...)
|
||||
@@ -1445,8 +1523,8 @@ Identifiers
|
||||
/* Alias Identification declaration (the "u" of "FROM User u") */
|
||||
AliasIdentificationVariable :: = identifier
|
||||
|
||||
/* identifier that must be a class name (the "User" of "FROM User u") */
|
||||
AbstractSchemaName ::= identifier
|
||||
/* identifier that must be a class name (the "User" of "FROM User u"), possibly as a fully qualified class name or namespace-aliased */
|
||||
AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
|
||||
|
||||
/* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */
|
||||
AliasResultVariable = identifier
|
||||
@@ -1545,7 +1623,7 @@ Select Expressions
|
||||
SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable]
|
||||
PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
|
||||
PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
|
||||
NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
|
||||
NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
|
||||
NewObjectArg ::= ScalarExpression | "(" Subselect ")"
|
||||
|
||||
Conditional Expressions
|
||||
|
||||
@@ -164,7 +164,8 @@ the life-time of their registered entities.
|
||||
database insert operations. Generated primary key values are
|
||||
available in the postPersist event.
|
||||
- preUpdate - The preUpdate event occurs before the database
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement.
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement
|
||||
nor when the computed changeset is empty.
|
||||
- postUpdate - The postUpdate event occurs after the database
|
||||
update operations to entity data. It is not called for a DQL UPDATE statement.
|
||||
- postLoad - The postLoad event occurs for an entity after the
|
||||
@@ -178,7 +179,7 @@ the life-time of their registered entities.
|
||||
allows providing fallback metadata even when no actual metadata exists
|
||||
or could be found. This event is not a lifecycle callback.
|
||||
- preFlush - The preFlush event occurs at the very beginning of a flush
|
||||
operation. This event is not a lifecycle callback.
|
||||
operation.
|
||||
- onFlush - The onFlush event occurs after the change-sets of all
|
||||
managed entities are computed. This event is not a lifecycle
|
||||
callback.
|
||||
@@ -322,7 +323,7 @@ XML would look something like this:
|
||||
<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
|
||||
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User">
|
||||
|
||||
@@ -405,8 +406,8 @@ behaviors across different entity classes.
|
||||
|
||||
Note that they require much more detailed knowledge about the inner
|
||||
workings of the EntityManager and UnitOfWork. Please read the
|
||||
*Implementing Event Listeners* section carefully if you are trying
|
||||
to write your own listener.
|
||||
:ref:`reference-events-implementing-listeners` section carefully if you
|
||||
are trying to write your own listener.
|
||||
|
||||
For event subscribers, there are no surprises. They declare the
|
||||
lifecycle events in their ``getSubscribedEvents`` method and provide
|
||||
@@ -433,7 +434,7 @@ A lifecycle event listener looks like the following:
|
||||
}
|
||||
}
|
||||
|
||||
A lifecycle event subscriber may looks like this:
|
||||
A lifecycle event subscriber may look like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -652,7 +653,8 @@ preUpdate
|
||||
|
||||
PreUpdate is the most restrictive to use event, since it is called
|
||||
right before an update statement is called for an entity inside the
|
||||
``EntityManager#flush()`` method.
|
||||
``EntityManager#flush()`` method. Note that this event is not
|
||||
triggered when the computed changeset is empty.
|
||||
|
||||
Changes to associations of the updated entity are never allowed in
|
||||
this event, since Doctrine cannot guarantee to correctly handle
|
||||
@@ -738,7 +740,7 @@ The three post events are called inside ``EntityManager#flush()``.
|
||||
Changes in here are not relevant to the persistence in the
|
||||
database, but you can use these events to alter non-persistable items,
|
||||
like non-mapped fields, logging or even associated classes that are
|
||||
directly mapped by Doctrine.
|
||||
not directly mapped by Doctrine.
|
||||
|
||||
postLoad
|
||||
~~~~~~~~
|
||||
@@ -886,6 +888,9 @@ you need to map the listener method using the event type mapping:
|
||||
preRemove: [preRemoveHandler]
|
||||
# ....
|
||||
|
||||
.. note::
|
||||
|
||||
The order of execution of multiple methods for the same event (e.g. multiple @PrePersist) is not guaranteed.
|
||||
|
||||
|
||||
Entity listeners resolver
|
||||
|
||||
@@ -21,12 +21,6 @@ created database tables and columns.
|
||||
Entity Classes
|
||||
--------------
|
||||
|
||||
I access a variable and its null, what is wrong?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If this variable is a public variable then you are violating one of the criteria for entities.
|
||||
All properties have to be protected or private for the proxy object pattern to work.
|
||||
|
||||
How can I add default values to a column?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -125,9 +125,8 @@ Example:
|
||||
Things to note:
|
||||
|
||||
|
||||
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
|
||||
must be specified on the topmost class that is part of the mapped
|
||||
entity hierarchy.
|
||||
- The @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
|
||||
@@ -160,7 +159,7 @@ 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,
|
||||
relationships involving types that employ this mapping strategy are
|
||||
very performant.
|
||||
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
|
||||
@@ -455,6 +454,8 @@ Things to note:
|
||||
- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany)
|
||||
- The association type *CANNOT* be changed.
|
||||
- The override could redefine the joinTables or joinColumns depending on the association type.
|
||||
- The override could redefine inversedBy to reference more than one extended entity.
|
||||
- The override could redefine fetch to modify the fetch strategy of the extended entity.
|
||||
|
||||
Attribute Override
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -492,7 +493,7 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
* column=@Column(
|
||||
* name = "guest_id",
|
||||
* type = "integer",
|
||||
length = 140
|
||||
* length = 140
|
||||
* )
|
||||
* ),
|
||||
* @AttributeOverride(name="name",
|
||||
@@ -500,7 +501,7 @@ Could be used by an entity that extends a mapped superclass to override a field
|
||||
* name = "guest_name",
|
||||
* nullable = false,
|
||||
* unique = true,
|
||||
length = 240
|
||||
* length = 240
|
||||
* )
|
||||
* )
|
||||
* })
|
||||
|
||||
@@ -63,7 +63,7 @@ Where the ``attribute_name`` column contains the key and
|
||||
``$attributes``.
|
||||
|
||||
The feature request for persistence of primitive value arrays
|
||||
`is described in the DDC-298 ticket <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
|
||||
`is described in the DDC-298 ticket <https://github.com/doctrine/doctrine2/issues/3743>`_.
|
||||
|
||||
Cascade Merge with Bi-directional Associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -71,8 +71,8 @@ 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 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <http://www.doctrine-project.org/jira/browse/DDC-763>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
- `DDC-875 <https://github.com/doctrine/doctrine2/issues/5398>`_ Merge can sometimes add the same entity twice into a collection
|
||||
- `DDC-763 <https://github.com/doctrine/doctrine2/issues/5277>`_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"
|
||||
|
||||
Custom Persisters
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@@ -83,10 +83,8 @@ Currently there is no way to overwrite the persister implementation
|
||||
for a given entity, however there are several use-cases that can
|
||||
benefit from custom persister implementations:
|
||||
|
||||
|
||||
- `Add Upsert Support <http://www.doctrine-project.org/jira/browse/DDC-668>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
|
||||
- The previous Filter Rules Feature Request
|
||||
- `Add Upsert Support <https://github.com/doctrine/doctrine2/issues/5178>`_
|
||||
- `Evaluate possible ways in which stored-procedures can be used <https://github.com/doctrine/doctrine2/issues/4946>`_
|
||||
|
||||
Persist Keys of Collections
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -96,7 +94,7 @@ PHP Arrays are ordered hash-maps and so should be the
|
||||
evaluate a feature that optionally persists and hydrates the keys
|
||||
of a Collection instance.
|
||||
|
||||
`Ticket DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
`Ticket DDC-213 <https://github.com/doctrine/doctrine2/issues/2817>`_
|
||||
|
||||
Mapping many tables to one entity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -114,10 +112,10 @@ in the core library. We don't think behaviors add more value than
|
||||
they cost pain and debugging hell. Please see the many different
|
||||
blog posts we have written on this topics:
|
||||
|
||||
- `Doctrine2 "Behaviors" in a Nutshell <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
|
||||
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
|
||||
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
|
||||
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
|
||||
- `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>`_
|
||||
- `Doctrator <https://github.com/pablodip/doctrator`>_
|
||||
|
||||
Doctrine 2 has enough hooks and extension points so that **you** can
|
||||
@@ -146,9 +144,8 @@ backwards compatibility issues or where no simple fix exists (yet).
|
||||
We don't plan to add every bug in the tracker there, just those
|
||||
issues that can potentially cause nightmares or pain of any sort.
|
||||
|
||||
See the Open Bugs on Jira for more details on `bugs, improvement and feature
|
||||
requests
|
||||
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
|
||||
See bugs, improvement and feature requests on `Github issues
|
||||
<https://github.com/doctrine/doctrine2/issues>`_.
|
||||
|
||||
Identifier Quoting and Legacy Databases
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -182,3 +179,27 @@ MySQL with MyISAM tables
|
||||
Doctrine cannot provide atomic operations when calling ``EntityManager#flush()`` if one
|
||||
of the tables involved uses the storage engine MyISAM. You must use InnoDB or
|
||||
other storage engines that support transactions if you need integrity.
|
||||
|
||||
Entities, Proxies and Reflection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Using methods for Reflection on entities can be prone to error, when the entity
|
||||
is actually a proxy the following methods will not work correctly:
|
||||
|
||||
- ``new ReflectionClass``
|
||||
- ``new ReflectionObject``
|
||||
- ``get_class()``
|
||||
- ``get_parent_class()``
|
||||
|
||||
This is why ``Doctrine\Common\Util\ClassUtils`` class exists that has similar
|
||||
methods, which resolve the proxy problem beforehand.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
$bookProxy = $entityManager->getReference('Acme\Book');
|
||||
|
||||
$reflection = ClassUtils::newReflectionClass($bookProxy);
|
||||
$class = ClassUtils::getClass($bookProxy)¸
|
||||
|
||||
@@ -35,7 +35,7 @@ an entity.
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$em->getConfiguration()->setMetadataCacheImpl(new ApcCache());
|
||||
$em->getConfiguration()->setMetadataCacheImpl(new ApcuCache());
|
||||
|
||||
|
||||
If you want to use one of the included core metadata drivers you
|
||||
|
||||
@@ -3,48 +3,43 @@ Implementing a NamingStrategy
|
||||
|
||||
.. versionadded:: 2.3
|
||||
|
||||
Using a naming strategy you can provide rules for automatically generating
|
||||
database identifiers, columns and tables names
|
||||
when the table/column name is not given.
|
||||
This feature helps reduce the verbosity of the mapping document,
|
||||
eliminating repetitive noise (eg: ``TABLE_``).
|
||||
|
||||
Using a naming strategy you can provide rules for generating database identifiers,
|
||||
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_``).
|
||||
|
||||
Configuring a naming strategy
|
||||
-----------------------------
|
||||
The default strategy used by Doctrine is quite minimal.
|
||||
|
||||
By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy``
|
||||
uses the simple class name and the attributes names to generate tables and columns
|
||||
uses the simple class name and the attribute names to generate tables and columns.
|
||||
|
||||
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :
|
||||
You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new MyNamingStrategy();
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
$configuration->setNamingStrategy($namingStrategy);
|
||||
|
||||
Underscore naming strategy
|
||||
---------------------------
|
||||
|
||||
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy
|
||||
that might be a useful if you want to use a underlying convention.
|
||||
``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(CASE_UPPER);
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
|
||||
Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER
|
||||
or some_entity_name using CASE_LOWER is given.
|
||||
$configuration->setNamingStrategy($namingStrategy);
|
||||
|
||||
For SomeEntityName the strategy will generate the table SOME_ENTITY_NAME with the
|
||||
``CASE_UPPER`` option, or some_entity_name with the ``CASE_LOWER`` option.
|
||||
|
||||
Naming strategy interface
|
||||
-------------------------
|
||||
The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify
|
||||
a "naming standard" for database tables and columns.
|
||||
a naming strategy for database tables and columns.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -101,10 +96,11 @@ a "naming standard" for database tables and columns.
|
||||
|
||||
Implementing a naming strategy
|
||||
-------------------------------
|
||||
If you have database naming standards like all tables names should be prefixed
|
||||
by the application prefix, all column names should be upper case,
|
||||
you can easily achieve such standards by implementing a naming strategy.
|
||||
You need to implements NamingStrategy first. Following is an example
|
||||
If you have database naming standards, like all table names should be prefixed
|
||||
by the application prefix, all column names should be lower case, you can easily
|
||||
achieve such standards by implementing a naming strategy.
|
||||
|
||||
You need to create a class which implements ``Doctrine\ORM\Mapping\NamingStrategy``.
|
||||
|
||||
|
||||
.. code-block:: php
|
||||
@@ -139,12 +135,3 @@ You need to implements NamingStrategy first. Following is an example
|
||||
($referencedColumnName ?: $this->referenceColumnName()));
|
||||
}
|
||||
}
|
||||
|
||||
Configuring the namingstrategy is easy if.
|
||||
Just set your naming strategy calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$namingStrategy = new MyAppNamingStrategy();
|
||||
$configuration()->setNamingStrategy($namingStrategy);
|
||||
|
||||
@@ -63,7 +63,7 @@ This has several benefits:
|
||||
- The API is much simpler than the usual ``ResultSetMapping`` API.
|
||||
|
||||
One downside is that the builder API does not yet support entities
|
||||
with inheritance hierachies.
|
||||
with inheritance hierarchies.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
||||
@@ -7,14 +7,14 @@ conditionally constructing a DQL query in several steps.
|
||||
It provides a set of classes and methods that is able to
|
||||
programmatically build queries, and also provides a fluent API.
|
||||
This means that you can change between one methodology to the other
|
||||
as you want, and also pick one if you prefer.
|
||||
as you want, or just pick a preferred one.
|
||||
|
||||
Constructing a new QueryBuilder object
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The same way you build a normal Query, you build a ``QueryBuilder``
|
||||
object, just providing the correct method name. Here is an example
|
||||
how to build a ``QueryBuilder`` object:
|
||||
object. Here is an example of how to build a ``QueryBuilder``
|
||||
object:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -24,9 +24,9 @@ how to build a ``QueryBuilder`` object:
|
||||
// example1: creating a QueryBuilder instance
|
||||
$qb = $em->createQueryBuilder();
|
||||
|
||||
Once you have created an instance of QueryBuilder, it provides a
|
||||
set of useful informative functions that you can use. One good
|
||||
example is to inspect what type of object the ``QueryBuilder`` is.
|
||||
An instance of QueryBuilder has several informative methods. One
|
||||
good example is to inspect what type of object the
|
||||
``QueryBuilder`` is.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -80,11 +80,11 @@ Working with QueryBuilder
|
||||
High level API methods
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To simplify even more the way you build a query in Doctrine, we can take
|
||||
advantage of what we call 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 ``QueryBuilder`` helper methods:
|
||||
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
|
||||
``QueryBuilder`` helper methods:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -97,8 +97,8 @@ re-written using ``QueryBuilder`` helper methods:
|
||||
->orderBy('u.name', 'ASC');
|
||||
|
||||
``QueryBuilder`` helper methods are considered the standard way to
|
||||
build DQL queries. Although it is supported, it should be avoided
|
||||
to use string based queries and greatly encouraged to use
|
||||
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:
|
||||
|
||||
@@ -113,7 +113,7 @@ suggested standard way to build queries:
|
||||
$qb->expr()->eq('u.id', '?1'),
|
||||
$qb->expr()->like('u.nickname', '?2')
|
||||
))
|
||||
->orderBy('u.surname', 'ASC'));
|
||||
->orderBy('u.surname', 'ASC');
|
||||
|
||||
Here is a complete list of helper methods available in ``QueryBuilder``:
|
||||
|
||||
@@ -126,7 +126,7 @@ Here is a complete list of helper methods available in ``QueryBuilder``:
|
||||
// Example - $qb->select(array('u', 'p'))
|
||||
// Example - $qb->select($qb->expr()->select('u', 'p'))
|
||||
public function select($select = null);
|
||||
|
||||
|
||||
// addSelect does not override previous calls to select
|
||||
//
|
||||
// Example - $qb->select('u');
|
||||
@@ -317,7 +317,7 @@ the Query object which can be retrieved from ``EntityManager#createQuery()``.
|
||||
Executing a Query
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The QueryBuilder is a builder object only, it has no means of actually
|
||||
The QueryBuilder is a builder object only - it has no means of actually
|
||||
executing the Query. Additionally a set of parameters such as query hints
|
||||
cannot be set on the QueryBuilder itself. This is why you always have to convert
|
||||
a querybuilder instance into a Query object:
|
||||
@@ -499,14 +499,32 @@ complete list of supported helper methods available:
|
||||
public function countDistinct($x); // Returns Expr\Func
|
||||
}
|
||||
|
||||
Adding a Criteria to a Query
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also add a :ref:`Criteria <filtering-collections>` to a QueryBuilder by
|
||||
using ``addCriteria``:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
// ...
|
||||
|
||||
$criteria = Criteria::create()
|
||||
->orderBy(['firstName', 'ASC']);
|
||||
|
||||
// $qb instanceof QueryBuilder
|
||||
$qb->addCriteria($criteria);
|
||||
// then execute your query like normal
|
||||
|
||||
Low Level API
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Now we have describe the low level (thought of as the
|
||||
hardcore method) of creating queries. It may be useful to work at
|
||||
this level for optimization purposes, but most of the time it is
|
||||
preferred to work at a higher level of abstraction.
|
||||
Now we will describe the low level method of creating queries.
|
||||
It may be useful to work at this level for optimization purposes,
|
||||
but most of the time it is preferred to work at a higher level of
|
||||
abstraction.
|
||||
|
||||
All helper methods in ``QueryBuilder`` actually rely on a single
|
||||
one: ``add()``. This method is responsible of building every piece
|
||||
@@ -559,7 +577,3 @@ same query of example 6 written using
|
||||
->add('where', new Expr\Comparison('u.id', '=', '?1'))
|
||||
->add('orderBy', new Expr\OrderBy('u.name', 'ASC'));
|
||||
|
||||
Of course this is the hardest way to build a DQL query in Doctrine.
|
||||
To simplify some of these efforts, we introduce what we call as
|
||||
``Expr`` helper class.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ The Second Level Cache is designed to reduce the amount of necessary database ac
|
||||
It sits between your application and the database to avoid the number of database hits as much as possible.
|
||||
|
||||
When turned on, entities will be first searched in cache and if they are not found,
|
||||
a database query will be fired an then the entity result will be stored in a cache provider.
|
||||
a database query will be fired and then the entity result will be stored in a cache provider.
|
||||
|
||||
There are some flavors of caching available, but is better to cache read-only data.
|
||||
|
||||
@@ -97,7 +97,7 @@ Defines a contract for accessing a particular region.
|
||||
|
||||
Defines a contract for accessing a particular cache region.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html/>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.Region.html>`_.
|
||||
|
||||
Concurrent cache region
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -111,7 +111,7 @@ If you want to use an ``READ_WRITE`` cache, you should consider providing your o
|
||||
|
||||
Defines contract for concurrently managed data region.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html/>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.ConcurrentRegion.html>`_.
|
||||
|
||||
Timestamp region
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -120,7 +120,7 @@ Timestamp region
|
||||
|
||||
Tracks the timestamps of the most recent updates to particular entity.
|
||||
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html/>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.TimestampRegion.html>`_.
|
||||
|
||||
.. _reference-second-level-cache-mode:
|
||||
|
||||
@@ -149,25 +149,25 @@ Caching mode
|
||||
|
||||
|
||||
Built-in cached persisters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Cached persisters are responsible to access cache regions.
|
||||
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| Cache Usage | Persister |
|
||||
+=======================+===============================================================================+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCachedEntityPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| READ_ONLY | Doctrine\\ORM\\Cache\\Persister\\ReadOnlyCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\ReadWriteCachedCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
| NONSTRICT_READ_WRITE | Doctrine\\ORM\\Cache\\Persister\\NonStrictReadWriteCacheCollectionPersister |
|
||||
+-----------------------+-------------------------------------------------------------------------------+
|
||||
+-----------------------+-------------------------------------------------------------------------------------------+
|
||||
| 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
|
||||
-------------
|
||||
@@ -209,7 +209,7 @@ It allows you to provide a specific implementation of the following components :
|
||||
* ``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/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html/>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.DefaultCacheFactory.html>`_.
|
||||
|
||||
Region Lifetime
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -220,7 +220,7 @@ To specify a default lifetime for all regions or specify a different lifetime fo
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
/* @var $cacheConfig \Doctrine\ORM\Configuration */
|
||||
/* @var $cacheConfig \Doctrine\ORM\Cache\CacheConfiguration */
|
||||
$cacheConfig = $config->getSecondLevelCacheConfiguration();
|
||||
$regionConfig = $cacheConfig->getRegionsConfiguration();
|
||||
|
||||
@@ -239,7 +239,7 @@ By providing a cache logger you should be able to get information about all cach
|
||||
|
||||
<?php
|
||||
/* @var $config \Doctrine\ORM\Configuration */
|
||||
$logger = \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
|
||||
$logger = new \Doctrine\ORM\Cache\Logging\StatisticsCacheLogger();
|
||||
|
||||
// Cache logger
|
||||
$config->setSecondLevelCacheEnabled(true);
|
||||
@@ -270,7 +270,7 @@ By providing a cache logger you should be able to get information about all cach
|
||||
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/2.5/class-Doctrine.ORM.Cache.CacheLogger.html/>`_.
|
||||
`See API Doc <http://www.doctrine-project.org/api/orm/2.5/class-Doctrine.ORM.Cache.CacheLogger.html>`_.
|
||||
|
||||
|
||||
Entity cache definition
|
||||
@@ -310,7 +310,7 @@ Entity cache definition
|
||||
.. code-block:: xml
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://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">
|
||||
@@ -386,7 +386,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 http://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" />
|
||||
@@ -619,7 +619,7 @@ Execute the ``UPDATE`` and invalidate ``a specific cache entry`` using the cache
|
||||
$em->getCache()->evictEntity('Entity\Country', 1);
|
||||
|
||||
Using the repository query cache
|
||||
---------------------
|
||||
--------------------------------
|
||||
|
||||
As well as ``Query Cache`` all persister queries store only identifier values for an individual query.
|
||||
All persister use a single timestamps cache region keeps track of the last update for each persister,
|
||||
|
||||
@@ -32,7 +32,7 @@ You can consider the following APIs to be safe from SQL injection:
|
||||
- Queries through the Criteria API on ``Doctrine\ORM\PersistentCollection`` and
|
||||
``Doctrine\ORM\EntityRepository``.
|
||||
|
||||
You are **NOT** save from SQL injection when using user input with:
|
||||
You are **NOT** safe from SQL injection when using user input with:
|
||||
|
||||
- Expression API of ``Doctrine\ORM\QueryBuilder``
|
||||
- Concatenating user input into DQL SELECT, UPDATE or DELETE statements or
|
||||
|
||||
@@ -205,7 +205,7 @@ tables of the current model to clean up with orphaned tables.
|
||||
You can also use database introspection to update your schema
|
||||
easily with the ``updateSchema()`` method. It will compare your
|
||||
existing database schema to the passed array of
|
||||
``ClassMetdataInfo`` instances.
|
||||
``ClassMetadataInfo`` instances.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -385,7 +385,7 @@ First you need to retrieve the metadata instances with the
|
||||
)
|
||||
);
|
||||
|
||||
$cmf = new DisconnectedClassMetadataFactory();
|
||||
$cmf = new \Doctrine\ORM\Tools\DisconnectedClassMetadataFactory();
|
||||
$cmf->setEntityManager($em);
|
||||
$metadata = $cmf->getAllMetadata();
|
||||
|
||||
@@ -395,6 +395,7 @@ 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();
|
||||
@@ -476,7 +477,7 @@ To include a new command on Doctrine Console, you need to do modify the
|
||||
|
||||
<?php
|
||||
// doctrine.php
|
||||
use Symfony\Component\Console\Helper\Application;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
// as before ...
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
Transactions and Concurrency
|
||||
============================
|
||||
|
||||
.. _transactions-and-concurrency_transaction-demarcation:
|
||||
|
||||
Transaction Demarcation
|
||||
-----------------------
|
||||
|
||||
@@ -26,6 +28,8 @@ and control transaction demarcation yourself.
|
||||
These are two ways to deal with transactions when using the
|
||||
Doctrine ORM and are now described in more detail.
|
||||
|
||||
.. _transactions-and-concurrency_approach-implicitly:
|
||||
|
||||
Approach 1: Implicitly
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -49,6 +53,8 @@ the DML operations by the Doctrine ORM and is sufficient if all the
|
||||
data manipulation that is part of a unit of work happens through
|
||||
the domain model and thus the ORM.
|
||||
|
||||
.. _transactions-and-concurrency_approach-explicitly:
|
||||
|
||||
Approach 2: Explicitly
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -69,7 +75,7 @@ looks like this:
|
||||
$em->flush();
|
||||
$em->getConnection()->commit();
|
||||
} catch (Exception $e) {
|
||||
$em->getConnection()->rollback();
|
||||
$em->getConnection()->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -98,12 +104,21 @@ functionally equivalent to the previously shown code looks as follows:
|
||||
$em->persist($user);
|
||||
});
|
||||
|
||||
.. warning::
|
||||
|
||||
For historical reasons, ``EntityManager#transactional($func)`` will return
|
||||
``true`` whenever the return value of ``$func`` is loosely false.
|
||||
Some examples of this include ``array()``, ``"0"``, ``""``, ``0``, and
|
||||
``null``.
|
||||
|
||||
The difference between ``Connection#transactional($func)`` and
|
||||
``EntityManager#transactional($func)`` is that the latter
|
||||
abstraction flushes the ``EntityManager`` prior to transaction
|
||||
commit and rolls back the transaction when an
|
||||
exception occurs.
|
||||
|
||||
.. _transactions-and-concurrency_exception-handling:
|
||||
|
||||
Exception Handling
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -134,6 +149,8 @@ knowing that their state is potentially no longer accurate.
|
||||
If you intend to start another unit of work after an exception has
|
||||
occurred you should do that with a new ``EntityManager``.
|
||||
|
||||
.. _transactions-and-concurrency_locking-support:
|
||||
|
||||
Locking Support
|
||||
---------------
|
||||
|
||||
@@ -142,6 +159,8 @@ strategies natively. This allows to take very fine-grained control
|
||||
over what kind of locking is required for your Entities in your
|
||||
application.
|
||||
|
||||
.. _transactions-and-concurrency_optimistic-locking:
|
||||
|
||||
Optimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -168,30 +187,68 @@ has been modified by someone else already.
|
||||
You designate a version field in an entity as follows. In this
|
||||
example we'll use an integer.
|
||||
|
||||
.. code-block:: php
|
||||
.. configuration-block::
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="integer") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="integer") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<field name="version" type="integer" version="true" />
|
||||
</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):
|
||||
|
||||
.. code-block:: php
|
||||
.. configuration-block::
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="datetime") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class User
|
||||
{
|
||||
// ...
|
||||
/** @Version @Column(type="datetime") */
|
||||
private $version;
|
||||
// ...
|
||||
}
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
<doctrine-mapping>
|
||||
<entity name="User">
|
||||
<field name="version" type="datetime" version="true" />
|
||||
</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
|
||||
@@ -305,6 +362,8 @@ And the change headline action (POST Request):
|
||||
|
||||
$post = $em->find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion);
|
||||
|
||||
.. _transactions-and-concurrency_pessimistic-locking:
|
||||
|
||||
Pessimistic Locking
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ Bidirectional Associations
|
||||
|
||||
The following rules apply to **bidirectional** associations:
|
||||
|
||||
- The inverse side has to use the ``mappedBy`` attribute of the OneToOne,
|
||||
- The inverse side has to have the ``mappedBy`` attribute of the OneToOne,
|
||||
OneToMany, or ManyToMany mapping declaration. The mappedBy
|
||||
attribute contains the name of the association-field on the owning side.
|
||||
- The owning side has to use the ``inversedBy`` attribute of the
|
||||
- The owning side has to have the ``inversedBy`` attribute of the
|
||||
OneToOne, ManyToOne, or ManyToMany mapping declaration.
|
||||
The inversedBy attribute contains the name of the association-field
|
||||
on the inverse-side.
|
||||
|
||||
@@ -15,7 +15,7 @@ with associations in Doctrine:
|
||||
removed, not the entity itself. A collection of entities always
|
||||
only represents the association to the containing entities, not the
|
||||
entity itself.
|
||||
- When a bidirectional assocation is updated, Doctrine only checks
|
||||
- When a bidirectional association is updated, Doctrine only checks
|
||||
on one of both sides for these changes. This is called the :doc:`owning side <unitofwork-associations>`
|
||||
of the association.
|
||||
- A property with a reference to many entities has to be instances of the
|
||||
@@ -238,14 +238,14 @@ the database permanently.
|
||||
|
||||
Notice how both sides of the bidirectional association are always
|
||||
updated. Unidirectional associations are consequently simpler to
|
||||
handle. Also note that if you use type-hinting in your methods, i.e.
|
||||
``setAddress(Address $address)``, PHP will only allow null
|
||||
values if ``null`` is set as default value. Otherwise
|
||||
setAddress(null) will fail for removing the association. If you
|
||||
insist on type-hinting a typical way to deal with this is to
|
||||
provide a special method, like ``removeAddress()``. This can also
|
||||
provide better encapsulation as it hides the internal meaning of
|
||||
not having an address.
|
||||
handle.
|
||||
|
||||
Also note that if you use type-hinting in your methods, you will
|
||||
have to specify a nullable type, i.e. ``setAddress(?Address $address)``,
|
||||
otherwise ``setAddress(null)`` will fail to remove the association.
|
||||
Another way to deal with this is to provide a special method, like
|
||||
``removeAddress()``. This can also provide better encapsulation as
|
||||
it hides the internal meaning of not having an address.
|
||||
|
||||
When working with collections, keep in mind that a Collection is
|
||||
essentially an ordered map (just like a PHP array). That is why the
|
||||
@@ -396,54 +396,25 @@ 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
|
||||
the next request Doctrine hydrates the consistent collection state
|
||||
again.
|
||||
2. Always keep the bidirectional collections in sync through
|
||||
association management methods. Reads of the Collections directly
|
||||
after changes are consistent then.
|
||||
|
||||
.. _transitive-persistence:
|
||||
|
||||
Transitive persistence / Cascade Operations
|
||||
-------------------------------------------
|
||||
|
||||
Persisting, removing, detaching, refreshing and merging individual entities can
|
||||
become pretty cumbersome, especially when a highly interweaved object graph
|
||||
is involved. Therefore Doctrine 2 provides a
|
||||
mechanism for transitive persistence through cascading of these
|
||||
operations. Each association to another entity or a collection of
|
||||
entities can be configured to automatically cascade certain
|
||||
operations. By default, no operations are cascaded.
|
||||
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``.
|
||||
|
||||
The following cascade options exist:
|
||||
|
||||
|
||||
- persist : Cascades persist operations to the associated
|
||||
entities.
|
||||
- remove : Cascades remove operations to the associated entities.
|
||||
- merge : Cascades merge operations to the associated entities.
|
||||
- detach : Cascades detach operations to the associated entities.
|
||||
- refresh : Cascades refresh operations to the associated entities.
|
||||
- all : Cascades persist, remove, merge, refresh and detach operations to
|
||||
associated entities.
|
||||
|
||||
.. note::
|
||||
|
||||
Cascade operations are performed in memory. That means collections and related entities
|
||||
are fetched into memory, even if they are still marked as lazy when
|
||||
the cascade operation is about to be performed. However this approach allows
|
||||
entity lifecycle events to be performed for each of these operations.
|
||||
|
||||
However, pulling objects graph into memory on cascade can cause considerable performance
|
||||
overhead, especially when cascading collections are large. Makes sure
|
||||
to weigh the benefits and downsides of each cascade operation that you define.
|
||||
|
||||
To rely on the database level cascade operations for the delete operation instead, you can
|
||||
configure each join column with the **onDelete** option. See the respective
|
||||
mapping driver chapters for more information.
|
||||
|
||||
The following example is an extension to the User-Comment example
|
||||
of this chapter. Suppose in our application a user is created
|
||||
whenever he writes his first comment. In this case we would use the
|
||||
following code:
|
||||
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
|
||||
comment might look like in your controller (without ``cascade: persist``):
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -453,37 +424,39 @@ following code:
|
||||
$user->addComment($myFirstComment);
|
||||
|
||||
$em->persist($user);
|
||||
$em->persist($myFirstComment);
|
||||
$em->persist($myFirstComment); // required, if `cascade: persist` is not set
|
||||
$em->flush();
|
||||
|
||||
Even if you *persist* a new User that contains our new Comment this
|
||||
code would fail if you removed the call to
|
||||
``EntityManager#persist($myFirstComment)``. Doctrine 2 does not
|
||||
cascade the persist operation to all nested entities that are new
|
||||
as well.
|
||||
|
||||
More complicated is the deletion of all of a user's comments when he is
|
||||
removed from the system:
|
||||
Note that the Comment entity is instantiated right here in the controller.
|
||||
To avoid this, ``cascade: persist`` allows you to "hide" the Comment entity from the controller,
|
||||
only accessing it through the User entity:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = $em->find('User', $deleteUserId);
|
||||
|
||||
foreach ($user->getAuthoredComments() as $comment) {
|
||||
$em->remove($comment);
|
||||
// User entity
|
||||
class User
|
||||
{
|
||||
private $id;
|
||||
private $comments;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = User::new();
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function comment(string $text, DateTimeInterface $time) : void
|
||||
{
|
||||
$newComment = Comment::create($text, $time);
|
||||
$newComment->setUser($this);
|
||||
$this->comments->add($newComment);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
|
||||
Without the loop over all the authored comments Doctrine would use
|
||||
an UPDATE statement only to set the foreign key to NULL and only
|
||||
the User would be deleted from the database during the
|
||||
flush()-Operation.
|
||||
|
||||
To have Doctrine handle both cases automatically we can change the
|
||||
``User#commentsAuthored`` property to cascade both the "persist"
|
||||
and the "remove" operation.
|
||||
If you then set up the cascading to the ``User#commentsAuthored`` property...
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -500,10 +473,51 @@ and the "remove" operation.
|
||||
//...
|
||||
}
|
||||
|
||||
Even though automatic cascading is convenient it should be used
|
||||
with care. Do not blindly apply cascade=all to all associations as
|
||||
...you can now create a user and an associated comment like this:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = new User();
|
||||
$user->comment('Lorem ipsum', new DateTime());
|
||||
|
||||
$em->persist($user);
|
||||
$em->flush();
|
||||
|
||||
.. note::
|
||||
|
||||
The idea of ``cascade: persist`` is not to save you any lines of code in the controller.
|
||||
If you instantiate the comment object in the controller (i.e. don't set up the user entity as shown above),
|
||||
even with ``cascade: persist`` you still have to call ``$myFirstComment->setUser($user);``.
|
||||
|
||||
Thanks to ``cascade: remove``, you can easily delete a user and all linked comments without having to loop through them:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$user = $em->find('User', $deleteUserId);
|
||||
|
||||
$em->remove($user);
|
||||
$em->flush();
|
||||
|
||||
.. note::
|
||||
|
||||
Cascade operations are performed in memory. That means collections and related entities
|
||||
are fetched into memory (even if they are marked as lazy) when
|
||||
the cascade operation is about to be performed. This approach allows
|
||||
entity lifecycle events to be performed for each of these operations.
|
||||
|
||||
However, pulling object graphs into memory on cascade can cause considerable performance
|
||||
overhead, especially when the cascaded collections are large. Make sure
|
||||
to weigh the benefits and downsides of each cascade operation that you define.
|
||||
|
||||
To rely on the database level cascade operations for the delete operation instead, you can
|
||||
configure each join column with :doc:`the onDelete option <working-with-objects>`.
|
||||
|
||||
Even though automatic cascading is convenient, it should be used
|
||||
with care. Do not blindly apply ``cascade=all`` to all associations as
|
||||
it will unnecessarily degrade the performance of your application.
|
||||
For each cascade operation that gets activated Doctrine also
|
||||
For each cascade operation that gets activated, Doctrine also
|
||||
applies that operation to the association, be it single or
|
||||
collection valued.
|
||||
|
||||
@@ -511,21 +525,20 @@ Persistence by Reachability: Cascade Persist
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are additional semantics that apply to the Cascade Persist
|
||||
operation. During each flush() operation Doctrine detects if there
|
||||
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
|
||||
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
|
||||
produce an Exception and rollback the flush() operation.
|
||||
2. New entities in a collection not marked as ``cascade: persist`` will
|
||||
produce an Exception and rollback the ``flush()`` operation.
|
||||
3. Collections without new entities are skipped.
|
||||
|
||||
This concept is called Persistence by Reachability: New entities
|
||||
that are found on already managed entities are automatically
|
||||
persisted as long as the association is defined as cascade
|
||||
persist.
|
||||
persisted as long as the association is defined as ``cascade: persist``.
|
||||
|
||||
Orphan Removal
|
||||
--------------
|
||||
@@ -603,11 +616,11 @@ address reference. When flush is called not only are the references removed
|
||||
but both the old standing data and the one address entity are also deleted
|
||||
from the database.
|
||||
|
||||
.. _filtering-collections:
|
||||
|
||||
Filtering Collections
|
||||
---------------------
|
||||
|
||||
.. filtering-collections:
|
||||
|
||||
Collections have a filtering API that allows to slice parts of data from
|
||||
a collection. If the collection has not been loaded from the database yet,
|
||||
the filtering API can work on the SQL level to make optimized access to
|
||||
@@ -703,6 +716,8 @@ methods:
|
||||
* ``in($field, array $values)``
|
||||
* ``notIn($field, array $values)``
|
||||
* ``contains($field, $value)``
|
||||
* ``startsWith($field, $value)``
|
||||
* ``endsWith($field, $value)``
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -25,6 +25,13 @@ Work that have not yet been persisted are lost.
|
||||
Not calling ``EntityManager#flush()`` will lead to all changes
|
||||
during that request being lost.
|
||||
|
||||
.. note::
|
||||
|
||||
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,
|
||||
any code put in your get/set methods won't be implicitly taken into account.
|
||||
|
||||
Entities and the Identity Map
|
||||
-----------------------------
|
||||
@@ -238,7 +245,7 @@ as follows:
|
||||
persist operation. However, the persist operation is cascaded to
|
||||
entities referenced by X, if the relationships from X to these
|
||||
other entities are mapped with cascade=PERSIST or cascade=ALL (see
|
||||
"Transitive Persistence").
|
||||
":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
- If X is a removed entity, it becomes managed.
|
||||
- If X is a detached entity, an exception will be thrown on
|
||||
flush.
|
||||
@@ -279,12 +286,12 @@ 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
|
||||
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
|
||||
with cascade=REMOVE or cascade=ALL (see ":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
- If X is a managed entity, the remove operation causes it to
|
||||
become removed. The remove operation is cascaded to entities
|
||||
referenced by X, if the relationships from X to these other
|
||||
entities is mapped with cascade=REMOVE or cascade=ALL (see
|
||||
"Transitive Persistence").
|
||||
":ref:`Transitive Persistence <transitive-persistence>`").
|
||||
- If X is a detached entity, an InvalidArgumentException will be
|
||||
thrown.
|
||||
- If X is a removed entity, it is ignored by the remove operation.
|
||||
@@ -350,14 +357,14 @@ as follows:
|
||||
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
|
||||
"Transitive Persistence"). Entities which previously referenced X
|
||||
":ref:`Transitive Persistence <transitive-persistence>`"). 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
|
||||
"Transitive Persistence"). Entities which previously referenced X
|
||||
":ref:`Transitive Persistence <transitive-persistence>`"). Entities which previously referenced X
|
||||
will continue to reference X.
|
||||
|
||||
There are several situations in which an entity is detached
|
||||
@@ -416,8 +423,7 @@ as follows:
|
||||
- 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 "Transitive
|
||||
Persistence").
|
||||
the cascade element value MERGE or ALL (see ":ref:`Transitive Persistence <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
|
||||
@@ -699,8 +705,6 @@ You can also load by owning side associations through the repository:
|
||||
$number = $em->find('MyProject\Domain\Phonenumber', 1234);
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
|
||||
|
||||
Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself.
|
||||
|
||||
The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -729,6 +733,14 @@ examples are equivalent:
|
||||
// A single user by its nickname (__call magic)
|
||||
$user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
|
||||
|
||||
Additionally, you can just count the result of the provided conditions when you don't really need the data:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// Check there is no user with nickname
|
||||
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
|
||||
|
||||
By Criteria
|
||||
~~~~~~~~~~~
|
||||
|
||||
@@ -738,8 +750,7 @@ The Repository implement the ``Doctrine\Common\Collections\Selectable``
|
||||
interface. That means you can build ``Doctrine\Common\Collections\Criteria``
|
||||
and pass them to the ``matching($criteria)`` method.
|
||||
|
||||
See the :ref:`Working with Associations: Filtering collections
|
||||
<filtering-collections>`.
|
||||
See section `Filtering collections` of chapter :doc:`Working with Associations <working-with-associations>`
|
||||
|
||||
By Eager Loading
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -7,7 +7,7 @@ form of XML documents.
|
||||
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
|
||||
`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
`https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd <https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd>`_.
|
||||
In order to point to the latest version of the document of a
|
||||
particular stable release branch, just append the release number,
|
||||
i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with
|
||||
@@ -21,7 +21,7 @@ setup for the latest code in trunk.
|
||||
<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://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
...
|
||||
|
||||
@@ -107,7 +107,7 @@ of several common elements:
|
||||
<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
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||
|
||||
@@ -321,12 +321,12 @@ Using the simplified definition above Doctrine will use no
|
||||
identifier strategy for this entity. That means you have to
|
||||
manually set the identifier before calling
|
||||
``EntityManager#persist($entity)``. This is the so called
|
||||
``ASSIGNED`` strategy.
|
||||
``NONE`` strategy.
|
||||
|
||||
If you want to switch the identifier generation strategy you have
|
||||
to nest a ``<generator />`` element inside the id-element. This of
|
||||
course only works for surrogate keys. For composite keys you always
|
||||
have to use the ``ASSIGNED`` strategy.
|
||||
have to use the ``NONE`` strategy.
|
||||
|
||||
.. code-block:: xml
|
||||
|
||||
@@ -768,7 +768,7 @@ entity relationship. You can define this in XML with the "association-key" attri
|
||||
<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
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
YAML Mapping
|
||||
============
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
The YAML mapping driver enables you to provide the ORM metadata in
|
||||
form of YAML documents.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ This tutorial shows how the semantics of composite primary keys work and how the
|
||||
General Considerations
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means
|
||||
Every entity with a composite key cannot use an id generator other than "NONE". That means
|
||||
the ID fields have to have their values set before you call ``EntityManager#persist($entity)``.
|
||||
|
||||
Primitive Types only
|
||||
@@ -38,7 +38,7 @@ and year of production as primary keys:
|
||||
/** @Id @Column(type="string") */
|
||||
private $name;
|
||||
/** @Id @Column(type="integer") */
|
||||
private $year
|
||||
private $year;
|
||||
|
||||
public function __construct($name, $year)
|
||||
{
|
||||
@@ -63,7 +63,7 @@ and year of production as primary keys:
|
||||
<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
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="VehicleCatalogue\Model\Car">
|
||||
<id field="name" type="string" />
|
||||
@@ -203,7 +203,7 @@ We keep up the example of an Article with arbitrary attributes, the mapping look
|
||||
<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
|
||||
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Application\Model\ArticleAttribute">
|
||||
<id name="article" association-key="true" />
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
Separating Concerns using Embeddables
|
||||
-------------------------------------
|
||||
|
||||
Embeddables are classes which are not entities themself, but are embedded
|
||||
Embeddables are classes which are not entities themselves, but are embedded
|
||||
in entities and can also be queried in DQL. You'll mostly want to use them
|
||||
to reduce duplication or separating concerns. Value objects such as date range
|
||||
or address are the primary use case for this feature. Embeddables can only
|
||||
contain properties with basic ``@Column`` mapping.
|
||||
or address are the primary use case for this feature.
|
||||
|
||||
.. note::
|
||||
|
||||
Embeddables can only contain properties with basic ``@Column`` mapping.
|
||||
|
||||
For the purposes of this tutorial, we will assume that you have a ``User``
|
||||
class in your application and you would like to store an address in
|
||||
@@ -76,6 +79,20 @@ In terms of your database schema, Doctrine will automatically inline all
|
||||
columns from the ``Address`` class into the table of the ``User`` class,
|
||||
just as if you had declared them directly there.
|
||||
|
||||
Initializing embeddables
|
||||
------------------------
|
||||
|
||||
In case all fields in the embeddable are ``nullable``, you might want
|
||||
to initialize the embeddable, to avoid getting a null value instead of
|
||||
the embedded object.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->address = new Address();
|
||||
}
|
||||
|
||||
Column Prefixing
|
||||
----------------
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ switch to extra lazy as shown in these examples:
|
||||
<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
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
|
||||
<!-- ... -->
|
||||
|
||||
@@ -14,10 +14,10 @@ Guide Assumptions
|
||||
-----------------
|
||||
|
||||
This guide is designed for beginners that haven't worked with Doctrine ORM
|
||||
before. There are some prerequesites for the tutorial that have to be
|
||||
before. There are some prerequisites for the tutorial that have to be
|
||||
installed:
|
||||
|
||||
- PHP 5.4 or above
|
||||
- PHP (latest stable version)
|
||||
- Composer Package Manager (`Install Composer
|
||||
<http://getcomposer.org/doc/00-intro.md>`_)
|
||||
|
||||
@@ -25,7 +25,7 @@ The code of this tutorial is `available on Github <https://github.com/doctrine/d
|
||||
|
||||
.. note::
|
||||
|
||||
This tutorial assumes you work with **Doctrine 2.4** and above.
|
||||
This tutorial assumes you work with **Doctrine 2.6** and above.
|
||||
Some of the code will not work with lower versions.
|
||||
|
||||
What is Doctrine?
|
||||
@@ -51,7 +51,7 @@ Entities are PHP Objects that can be identified over many requests
|
||||
by a unique identifier or primary key. These classes don't need to extend any
|
||||
abstract base class or interface. An entity class must not be final
|
||||
or contain final methods. Additionally it must not implement
|
||||
**clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
**clone** nor **wakeup**, unless it :doc:`does so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||
|
||||
An entity contains persistable properties. A persistable property
|
||||
is an instance variable of the entity that is saved into and retrieved from the database
|
||||
@@ -62,7 +62,7 @@ An Example Model: Bug Tracker
|
||||
|
||||
For this Getting Started Guide for Doctrine we will implement the
|
||||
Bug Tracker domain model from the
|
||||
`Zend\_Db\_Table <http://framework.zend.com/manual/en/zend.db.table.html>`_
|
||||
`Zend\_Db\_Table <http://framework.zend.com/manual/1.12/en/zend.db.adapter.html>`_
|
||||
documentation. Reading their documentation we can extract the
|
||||
requirements:
|
||||
|
||||
@@ -80,14 +80,14 @@ Project Setup
|
||||
-------------
|
||||
|
||||
Create a new empty folder for this tutorial project, for example
|
||||
``doctrine2-tutorial`` and create a new file ``composer.json`` with
|
||||
the following contents:
|
||||
``doctrine2-tutorial`` and create a new file ``composer.json`` inside
|
||||
that directory with the following contents:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"require": {
|
||||
"doctrine/orm": "2.4.*",
|
||||
"doctrine/orm": "^2.6.2",
|
||||
"symfony/yaml": "2.*"
|
||||
},
|
||||
"autoload": {
|
||||
@@ -103,23 +103,26 @@ Install Doctrine using the Composer Dependency Management tool, by calling:
|
||||
$ composer install
|
||||
|
||||
This will install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM,
|
||||
Symfony YAML and Symfony Console into the `vendor` directory. The Symfony
|
||||
dependencies are not required by Doctrine but will be used in this tutorial.
|
||||
into the ``vendor`` directory.
|
||||
|
||||
Add the following directories:
|
||||
::
|
||||
|
||||
doctrine2-tutorial
|
||||
|-- config
|
||||
| |-- xml
|
||||
| `-- xml
|
||||
| `-- yaml
|
||||
`-- src
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
Obtaining the EntityManager
|
||||
---------------------------
|
||||
|
||||
Doctrine's public interface is the EntityManager, it provides the
|
||||
access point to the complete lifecycle management of your entities
|
||||
Doctrine's public interface is through the ``EntityManager``. This class
|
||||
provides access points to the complete lifecycle management for your entities,
|
||||
and transforms entities from and back to persistence. You have to
|
||||
configure and create it to use your entities with Doctrine 2. I
|
||||
will show the configuration steps and then discuss them step by
|
||||
@@ -150,8 +153,12 @@ step:
|
||||
// obtaining the entity manager
|
||||
$entityManager = EntityManager::create($conn, $config);
|
||||
|
||||
The first require statement sets up the autoloading capabilities of Doctrine
|
||||
using the Composer autoload.
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
The ``require_once`` statement sets up the class autoloading for Doctrine and
|
||||
its dependencies using Composer's autoloader.
|
||||
|
||||
The second block consists of the instantiation of the ORM
|
||||
``Configuration`` object using the Setup helper. It assumes a bunch
|
||||
@@ -159,10 +166,10 @@ of defaults that you don't have to bother about for now. You can
|
||||
read up on the configuration details in the
|
||||
:doc:`reference chapter on configuration <../reference/configuration>`.
|
||||
|
||||
The third block shows the configuration options required to connect
|
||||
to a database, in my case a file-based sqlite database. All the
|
||||
The third block shows the configuration options required to connect to
|
||||
a database. In this case, we'll use a file-based SQLite database. All the
|
||||
configuration options for all the shipped drivers are given in the
|
||||
`DBAL Configuration section of the manual <http://www.doctrine-project.org/documentation/manual/2_0/en/dbal>`_.
|
||||
`DBAL Configuration section of the manual <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/>`_.
|
||||
|
||||
The last block shows how the ``EntityManager`` is obtained from a
|
||||
factory method.
|
||||
@@ -170,15 +177,10 @@ factory method.
|
||||
Generating the Database Schema
|
||||
------------------------------
|
||||
|
||||
Now that we have defined the Metadata mappings and bootstrapped the
|
||||
EntityManager we want to generate the relational database schema
|
||||
from it. Doctrine has a Command-Line Interface that allows you to
|
||||
access the SchemaTool, a component that generates the required
|
||||
tables to work with the metadata.
|
||||
|
||||
For the command-line tool to work a cli-config.php file has to be
|
||||
present in the project root directory, where you will execute the
|
||||
doctrine command. Its a fairly simple file:
|
||||
Doctrine has a command-line interface that allows you to access the SchemaTool,
|
||||
a component that can generate a relational database schema based entirely on the
|
||||
defined entity classes and their metadata. For this tool to work, a
|
||||
``cli-config.php`` file must exist in the project root directory:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -188,40 +190,37 @@ doctrine command. Its a fairly simple file:
|
||||
|
||||
return \Doctrine\ORM\Tools\Console\ConsoleRunner::createHelperSet($entityManager);
|
||||
|
||||
You can then change into your project directory and call the
|
||||
Doctrine command-line tool:
|
||||
Now call the Doctrine command-line tool:
|
||||
|
||||
::
|
||||
|
||||
$ cd project/
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
|
||||
At this point no entity metadata exists in `src` so you will see a message like
|
||||
"No Metadata Classes to process." Don't worry, we'll create a Product entity and
|
||||
corresponding metadata in the next section.
|
||||
Since we haven't added any entity metadata in ``src`` yet, you'll see a message
|
||||
stating "No Metadata Classes to process." In the next section, we'll create a
|
||||
Product entity along with the corresponding metadata, and run this command again.
|
||||
|
||||
You should be aware that during the development process you'll periodically need
|
||||
to update your database schema to be in sync with your Entities metadata.
|
||||
|
||||
You can easily recreate the database:
|
||||
Note that as you modify your entities' metadata during the development process,
|
||||
you'll need to update your database schema to stay in sync with the metadata.
|
||||
You can easily recreate the database using the following commands:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:drop --force
|
||||
$ vendor/bin/doctrine orm:schema-tool:create
|
||||
|
||||
Or use the update functionality:
|
||||
Or you can use the update functionality:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
|
||||
The updating of databases uses a Diff Algorithm for a given
|
||||
Database Schema, a cornerstone of the ``Doctrine\DBAL`` package,
|
||||
The updating of databases uses a diff algorithm for a given
|
||||
database schema. This is a cornerstone of the ``Doctrine\DBAL`` package,
|
||||
which can even be used without the Doctrine ORM package.
|
||||
|
||||
Starting with the Product
|
||||
-------------------------
|
||||
Starting with the Product Entity
|
||||
--------------------------------
|
||||
|
||||
We start with the simplest entity, the Product. Create a ``src/Product.php`` file to contain the ``Product``
|
||||
entity definition:
|
||||
@@ -257,16 +256,16 @@ entity definition:
|
||||
}
|
||||
}
|
||||
|
||||
Note that all fields are set to protected (not public) with a
|
||||
mutator (getter and setter) defined for every field except $id.
|
||||
The use of mutators allows Doctrine to hook into calls which
|
||||
manipulate the entities in ways that it could not if you just
|
||||
When creating entity classes, all of the fields should be ``protected`` or ``private``
|
||||
(not ``public``), with getter and setter methods for each one (except ``$id``).
|
||||
The use of mutators allows Doctrine to hook into calls which
|
||||
manipulate the entities in ways that it could not if you just
|
||||
directly set the values with ``entity#field = foo;``
|
||||
|
||||
The id field has no setter since, generally speaking, your code
|
||||
should not set this value since it represents a database id value.
|
||||
(Note that Doctrine itself can still set the value using the
|
||||
Reflection API instead of a defined setter function)
|
||||
The id field has no setter since, generally speaking, your code
|
||||
should not set this value since it represents a database id value.
|
||||
(Note that Doctrine itself can still set the value using the
|
||||
Reflection API instead of a defined setter function.)
|
||||
|
||||
The next step for persistence with Doctrine is to describe the
|
||||
structure of the ``Product`` entity to Doctrine using a metadata
|
||||
@@ -274,9 +273,10 @@ language. The metadata language describes how entities, their
|
||||
properties and references should be persisted and what constraints
|
||||
should be applied to them.
|
||||
|
||||
Metadata for entities are configured using a XML, YAML or Docblock Annotations.
|
||||
This Getting Started Guide will show the mappings for all Mapping Drivers.
|
||||
References in the text will be made to the XML mapping.
|
||||
Metadata for an Entity can be configured using DocBlock annotations directly
|
||||
in the Entity class itself, or in an external XML or YAML file. This Getting
|
||||
Started guide will demonstrate metadata mappings using all three methods,
|
||||
but you only need to choose one.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -303,7 +303,7 @@ References in the text will be made to the XML mapping.
|
||||
<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
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Product" table="products">
|
||||
<id name="id" type="integer">
|
||||
@@ -314,6 +314,10 @@ References in the text will be made to the XML mapping.
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# config/yaml/Product.dcm.yml
|
||||
@@ -329,30 +333,31 @@ References in the text will be made to the XML mapping.
|
||||
name:
|
||||
type: string
|
||||
|
||||
The top-level ``entity`` definition tag specifies information about
|
||||
the class and table-name. The primitive type ``Product#name`` is
|
||||
The top-level ``entity`` definition specifies information about
|
||||
the class and table name. The primitive type ``Product#name`` is
|
||||
defined as a ``field`` attribute. The ``id`` property is defined with
|
||||
the ``id`` tag, this has a ``generator`` tag nested inside which
|
||||
defines that the primary key generation mechanism automatically
|
||||
uses the database platforms native id generation strategy (for
|
||||
example AUTO INCREMENT in the case of MySql or Sequences in the
|
||||
the ``id`` tag. It has a ``generator`` tag nested inside, which
|
||||
specifies that the primary key generation mechanism should automatically
|
||||
use the database platform's native id generation strategy (for
|
||||
example, AUTO INCREMENT in the case of MySql, or Sequences in the
|
||||
case of PostgreSql and Oracle).
|
||||
|
||||
Now that we have defined our first entity, let's update the database:
|
||||
Now that we have defined our first entity and its metadata,
|
||||
let's update the database schema:
|
||||
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force --dump-sql
|
||||
|
||||
Specifying both flags ``--force`` and ``-dump-sql`` prints and executes the DDL
|
||||
statements.
|
||||
Specifying both flags ``--force`` and ``--dump-sql`` will cause the DDL
|
||||
statements to be executed and then printed to the screen.
|
||||
|
||||
Now create a new script that will insert products into the database:
|
||||
Now, we'll create a new script to insert products into the database:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// create_product.php
|
||||
// create_product.php <name>
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$newProductName = $argv[1];
|
||||
@@ -372,22 +377,19 @@ Call this script from the command-line to see how new products are created:
|
||||
$ php create_product.php ORM
|
||||
$ php create_product.php DBAL
|
||||
|
||||
What is happening here? Using the ``Product`` is pretty standard OOP.
|
||||
What is happening here? Using the ``Product`` class is pretty standard OOP.
|
||||
The interesting bits are the use of the ``EntityManager`` service. To
|
||||
notify the EntityManager that a new entity should be inserted into the database
|
||||
you have to call ``persist()``. To initiate a transaction to actually perform
|
||||
the insertion, You have to explicitly call ``flush()`` on the ``EntityManager``.
|
||||
notify the EntityManager that a new entity should be inserted into the database,
|
||||
you have to call ``persist()``. To initiate a transaction to actually *perform*
|
||||
the insertion, you have to explicitly call ``flush()`` on the ``EntityManager``.
|
||||
|
||||
This distinction between persist and flush is allows to aggregate all writes
|
||||
(INSERT, UPDATE, DELETE) into one single transaction, which is executed when
|
||||
flush is called. Using this approach the write-performance is significantly
|
||||
better than in a scenario where updates are done for each entity in isolation.
|
||||
This distinction between persist and flush is what allows the aggregation of
|
||||
all database writes (INSERT, UPDATE, DELETE) into one single transaction, which
|
||||
is executed when ``flush()`` is called. Using this approach, the write-performance
|
||||
is significantly better than in a scenario in which writes are performed on
|
||||
each entity in isolation.
|
||||
|
||||
Doctrine follows the UnitOfWork pattern which additionally detects all entities
|
||||
that were fetched and have changed during the request. You don't have to keep track of
|
||||
entities yourself, when Doctrine already knows about them.
|
||||
|
||||
As a next step we want to fetch a list of all the Products. Let's create a
|
||||
Next, we'll fetch a list of all the Products in the database. Let's create a
|
||||
new script for this:
|
||||
|
||||
.. code-block:: php
|
||||
@@ -404,10 +406,10 @@ new script for this:
|
||||
}
|
||||
|
||||
The ``EntityManager#getRepository()`` method can create a finder object (called
|
||||
a repository) for every entity. It is provided by Doctrine and contains some
|
||||
finder methods such as ``findAll()``.
|
||||
a repository) for every type of entity. It is provided by Doctrine and contains
|
||||
some finder methods like ``findAll()``.
|
||||
|
||||
Let's continue with displaying the name of a product based on its ID:
|
||||
Let's continue by creating a script to display the name of a product based on its ID:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -425,9 +427,13 @@ Let's continue with displaying the name of a product based on its ID:
|
||||
|
||||
echo sprintf("-%s\n", $product->getName());
|
||||
|
||||
Updating a product name demonstrates the functionality UnitOfWork of pattern
|
||||
discussed before. We only need to find a product entity and all changes to its
|
||||
properties are written to the database:
|
||||
Next we'll update a product's name, given its id. This simple example will
|
||||
help demonstrate Doctrine's implementation of the UnitOfWork pattern. Doctrine
|
||||
keeps track of all the entities that were retrieved from the Entity Manager,
|
||||
and can detect when any of those entities' properties have been modified.
|
||||
As a result, rather than needing to call ``persist($entity)`` for each individual
|
||||
entity whose properties were changed, a single call to ``flush()`` at the end of a
|
||||
request is sufficient to update the database for all of the modified entities.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -455,9 +461,8 @@ product name changed by calling the ``show_product.php`` script.
|
||||
Adding Bug and User Entities
|
||||
----------------------------
|
||||
|
||||
We continue with the bug tracker domain, by creating the missing classes
|
||||
``Bug`` and ``User`` and putting them into ``src/Bug.php`` and
|
||||
``src/User.php`` respectively.
|
||||
We continue with the bug tracker example by creating the ``Bug`` and ``User``
|
||||
classes. We'll store them in ``src/Bug.php`` and ``src/User.php``, respectively.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -561,14 +566,15 @@ We continue with the bug tracker domain, by creating the missing classes
|
||||
}
|
||||
}
|
||||
|
||||
All of the properties discussed so far are simple string and integer values,
|
||||
for example the id fields of the entities, their names, description, status and
|
||||
change dates. Next we will model the dynamic relationships between the entities
|
||||
by defining the references between entities.
|
||||
All of the properties we've seen so far are of simple types (integer, string,
|
||||
and datetime). But now, we'll add properties that will store objects of
|
||||
specific *entity types* in order to model the relationships between different
|
||||
entities.
|
||||
|
||||
References between objects are foreign keys in the database. You never have to
|
||||
(and never should) work with the foreign keys directly, only with the objects
|
||||
that represent the foreign key through their own identity.
|
||||
At the database level, relationships between entities are represented by foreign
|
||||
keys. But with Doctrine, you'll never have to (and never should) work with
|
||||
the foreign keys directly. You should only work with objects that represent
|
||||
foreign keys through their own identities.
|
||||
|
||||
For every foreign key you either have a Doctrine ManyToOne or OneToOne
|
||||
association. On the inverse sides of these foreign keys you can have
|
||||
@@ -602,6 +608,7 @@ domain model to match the requirements:
|
||||
<?php
|
||||
// src/User.php
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
class User
|
||||
{
|
||||
// ... (previous code)
|
||||
@@ -616,12 +623,13 @@ domain model to match the requirements:
|
||||
}
|
||||
}
|
||||
|
||||
Whenever an entity is recreated from the database, an Collection
|
||||
implementation of the type Doctrine is injected into your entity
|
||||
instead of an array. Compared to the ArrayCollection this
|
||||
implementation helps the Doctrine ORM understand the changes that
|
||||
have happened to the collection which are noteworthy for
|
||||
persistence.
|
||||
.. note::
|
||||
|
||||
Whenever an entity is created from the database, a ``Collection``
|
||||
implementation of the type ``PersistentCollection`` will be injected into
|
||||
your entity instead of an ``ArrayCollection``. This helps Doctrine ORM
|
||||
understand the changes that have happened to the collection that are
|
||||
noteworthy for persistence.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -644,24 +652,22 @@ able to work with Doctrine 2. These assumptions are not unique to
|
||||
Doctrine 2 but are best practices in handling database relations
|
||||
and Object-Relational Mapping.
|
||||
|
||||
|
||||
- In a one-to-one relation, the entity holding the foreign key of
|
||||
the related entity on its own database table is *always* the owning
|
||||
side of the relation.
|
||||
- In a many-to-one relation, the Many-side is the owning side by
|
||||
default because it holds the foreign key. Accordingly, the One-side
|
||||
is the inverse side by default.
|
||||
- In a many-to-one relation, the One-side can only be the owning side if
|
||||
the relation is implemented as a ManyToMany with a join table, and the
|
||||
One-side is restricted to allow only UNIQUE values per database constraint.
|
||||
- In a many-to-many relation, both sides can be the owning side of
|
||||
the relation. However, in a bi-directional many-to-many relation,
|
||||
only one side is allowed to be the owning side.
|
||||
- Changes to Collections are saved or updated, when the entity on
|
||||
the *owning* side of the collection is saved or updated.
|
||||
- Saving an Entity at the inverse side of a relation never
|
||||
triggers a persist operation to changes to the collection.
|
||||
- In a one-to-one relation the entity holding the foreign key of
|
||||
the related entity on its own database table is *always* the owning
|
||||
side of the relation.
|
||||
- In a many-to-many relation, both sides can be the owning side of
|
||||
the relation. However in a bi-directional many-to-many relation
|
||||
only one is allowed to be.
|
||||
- In a many-to-one relation the Many-side is the owning side by
|
||||
default, because it holds the foreign key.
|
||||
- The OneToMany side of a relation is inverse by default, since
|
||||
the foreign key is saved on the Many side. A OneToMany relation can
|
||||
only be the owning side, if its implemented using a ManyToMany
|
||||
relation with join table and restricting the one side to allow only
|
||||
UNIQUE values per database constraint.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -686,13 +692,13 @@ the bi-directional reference:
|
||||
protected $engineer;
|
||||
protected $reporter;
|
||||
|
||||
public function setEngineer($engineer)
|
||||
public function setEngineer(User $engineer)
|
||||
{
|
||||
$engineer->assignedToBug($this);
|
||||
$this->engineer = $engineer;
|
||||
}
|
||||
|
||||
public function setReporter($reporter)
|
||||
public function setReporter(User $reporter)
|
||||
{
|
||||
$reporter->addReportedBug($this);
|
||||
$this->reporter = $reporter;
|
||||
@@ -717,15 +723,15 @@ the bi-directional reference:
|
||||
{
|
||||
// ... (previous code)
|
||||
|
||||
private $reportedBugs = null;
|
||||
private $assignedBugs = null;
|
||||
protected $reportedBugs;
|
||||
protected $assignedBugs;
|
||||
|
||||
public function addReportedBug($bug)
|
||||
public function addReportedBug(Bug $bug)
|
||||
{
|
||||
$this->reportedBugs[] = $bug;
|
||||
}
|
||||
|
||||
public function assignedToBug($bug)
|
||||
public function assignedToBug(Bug $bug)
|
||||
{
|
||||
$this->assignedBugs[] = $bug;
|
||||
}
|
||||
@@ -741,7 +747,7 @@ You can see from ``User#addReportedBug()`` and
|
||||
``User#assignedToBug()`` that using this method in userland alone
|
||||
would not add the Bug to the collection of the owning side in
|
||||
``Bug#reporter`` or ``Bug#engineer``. Using these methods and
|
||||
calling Doctrine for persistence would not update the collections
|
||||
calling Doctrine for persistence would not update the Collections'
|
||||
representation in the database.
|
||||
|
||||
Only using ``Bug#setEngineer()`` or ``Bug#setReporter()``
|
||||
@@ -749,7 +755,7 @@ correctly saves the relation information.
|
||||
|
||||
The ``Bug#reporter`` and ``Bug#engineer`` properties are
|
||||
Many-To-One relations, which point to a User. In a normalized
|
||||
relational model the foreign key is saved on the Bug's table, hence
|
||||
relational model, the foreign key is saved on the Bug's table, hence
|
||||
in our object-relation model the Bug is at the owning side of the
|
||||
relation. You should always make sure that the use-cases of your
|
||||
domain model should drive which side is an inverse or owning one in
|
||||
@@ -758,7 +764,7 @@ or an engineer is assigned to the bug, we don't want to update the
|
||||
User to persist the reference, but the Bug. This is the case with
|
||||
the Bug being at the owning side of the relation.
|
||||
|
||||
Bugs reference Products by an uni-directional ManyToMany relation in
|
||||
Bugs reference Products by a uni-directional ManyToMany relation in
|
||||
the database that points from Bugs to Products.
|
||||
|
||||
.. code-block:: php
|
||||
@@ -771,7 +777,7 @@ the database that points from Bugs to Products.
|
||||
|
||||
protected $products = null;
|
||||
|
||||
public function assignToProduct($product)
|
||||
public function assignToProduct(Product $product)
|
||||
{
|
||||
$this->products[] = $product;
|
||||
}
|
||||
@@ -783,7 +789,7 @@ the database that points from Bugs to Products.
|
||||
}
|
||||
|
||||
We are now finished with the domain model given the requirements.
|
||||
Lets add metadata mappings for the ``User`` and ``Bug`` as we did for
|
||||
Lets add metadata mappings for the ``Bug`` entity, as we did for
|
||||
the ``Product`` before:
|
||||
|
||||
.. configuration-block::
|
||||
@@ -837,7 +843,7 @@ the ``Product`` before:
|
||||
<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
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Bug" table="bugs">
|
||||
<id name="id" type="integer">
|
||||
@@ -855,6 +861,10 @@ the ``Product`` before:
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# config/yaml/Bug.dcm.yml
|
||||
@@ -890,13 +900,13 @@ For the "created" field we have used the ``datetime`` type,
|
||||
which translates the YYYY-mm-dd HH:mm:ss database format
|
||||
into a PHP DateTime instance and back.
|
||||
|
||||
After the field definitions the two qualified references to the
|
||||
After the field definitions, the two qualified references to the
|
||||
user entity are defined. They are created by the ``many-to-one``
|
||||
tag. The class name of the related entity has to be specified with
|
||||
the ``target-entity`` attribute, which is enough information for
|
||||
the database mapper to access the foreign-table. Since
|
||||
``reporter`` and ``engineer`` are on the owning side of a
|
||||
bi-directional relation we also have to specify the ``inversed-by``
|
||||
bi-directional relation, we also have to specify the ``inversed-by``
|
||||
attribute. They have to point to the field names on the inverse
|
||||
side of the relationship. We will see in the next example that the ``inversed-by``
|
||||
attribute has a counterpart ``mapped-by`` which makes that
|
||||
@@ -907,7 +917,7 @@ holds all products where the specific bug occurs. Again
|
||||
you have to define the ``target-entity`` and ``field`` attributes
|
||||
on the ``many-to-many`` tag.
|
||||
|
||||
The last missing definition is that of the User entity:
|
||||
Finally, we'll add metadata mappings for the ``User`` entity.
|
||||
|
||||
.. configuration-block::
|
||||
|
||||
@@ -934,13 +944,13 @@ The last missing definition is that of the User entity:
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="reporter")
|
||||
* @var Bug[]
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
**/
|
||||
protected $reportedBugs = null;
|
||||
|
||||
/**
|
||||
* @OneToMany(targetEntity="Bug", mappedBy="engineer")
|
||||
* @var Bug[]
|
||||
* @var Bug[] An ArrayCollection of Bug objects.
|
||||
**/
|
||||
protected $assignedBugs = null;
|
||||
|
||||
@@ -953,7 +963,7 @@ The last missing definition is that of the User entity:
|
||||
<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
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="User" table="users">
|
||||
<id name="id" type="integer">
|
||||
@@ -967,9 +977,13 @@ The last missing definition is that of the User entity:
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# config/xml/User.dcm.yml
|
||||
# config/yaml/User.dcm.yml
|
||||
User:
|
||||
type: entity
|
||||
table: users
|
||||
@@ -996,10 +1010,7 @@ means the join details have already been defined on the owning
|
||||
side. Therefore we only have to specify the property on the Bug
|
||||
class that holds the owning sides.
|
||||
|
||||
This example has a fair overview of the most basic features of the
|
||||
metadata definition language.
|
||||
|
||||
Update your database running:
|
||||
Update your database schema by running:
|
||||
::
|
||||
|
||||
$ vendor/bin/doctrine orm:schema-tool:update --force
|
||||
@@ -1008,7 +1019,8 @@ Update your database running:
|
||||
Implementing more Requirements
|
||||
------------------------------
|
||||
|
||||
For starters we need a create user entities:
|
||||
So far, we've seen the most basic features of the metadata definition language.
|
||||
To explore additional functionality, let's first create new ``User`` entities:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
@@ -1032,23 +1044,22 @@ Now call:
|
||||
|
||||
$ php create_user.php beberlei
|
||||
|
||||
We now have the data to create a bug and the code for this scenario may look
|
||||
like this:
|
||||
We now have the necessary data to create a new Bug entity:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// create_bug.php
|
||||
// create_bug.php <reporter-id> <engineer-id> <product-ids>
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theReporterId = $argv[1];
|
||||
$theDefaultEngineerId = $argv[2];
|
||||
$reporterId = $argv[1];
|
||||
$engineerId = $argv[2];
|
||||
$productIds = explode(",", $argv[3]);
|
||||
|
||||
$reporter = $entityManager->find("User", $theReporterId);
|
||||
$engineer = $entityManager->find("User", $theDefaultEngineerId);
|
||||
$reporter = $entityManager->find("User", $reporterId);
|
||||
$engineer = $entityManager->find("User", $engineerId);
|
||||
if (!$reporter || !$engineer) {
|
||||
echo "No reporter and/or engineer found for the input.\n";
|
||||
echo "No reporter and/or engineer found for the given id(s).\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -1070,22 +1081,17 @@ like this:
|
||||
|
||||
echo "Your new Bug Id: ".$bug->getId()."\n";
|
||||
|
||||
Since we only have one user and product, probably with the ID of 1, we can call this script with:
|
||||
Since we only have one user and product, probably with the ID of 1, we can
|
||||
call this script as follows:
|
||||
|
||||
::
|
||||
|
||||
php create_bug.php 1 1 1
|
||||
|
||||
This is the first contact with the read API of the EntityManager,
|
||||
showing that a call to ``EntityManager#find($name, $id)`` returns a
|
||||
single instance of an entity queried by primary key. Besides this
|
||||
we see the persist + flush pattern again to save the Bug into the
|
||||
database.
|
||||
|
||||
See how simple relating Bug, Reporter, Engineer and Products is
|
||||
done by using the discussed methods in the "A first prototype"
|
||||
section. The UnitOfWork will detect this relations when flush is
|
||||
called and relate them in the database appropriately.
|
||||
See how simple it is to relate a Bug, Reporter, Engineer and Products?
|
||||
Also recall that thanks to the UnitOfWork pattern, Doctrine will detect
|
||||
these relations and update all of the modified entities in the database
|
||||
automatically when ``flush()`` is called.
|
||||
|
||||
Queries for Application Use-Cases
|
||||
---------------------------------
|
||||
@@ -1094,7 +1100,7 @@ List of Bugs
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Using the previous examples we can fill up the database quite a
|
||||
bit, however we now need to discuss how to query the underlying
|
||||
bit. However, we now need to discuss how to query the underlying
|
||||
mapper for the required view representations. When opening the
|
||||
application, bugs can be paginated through a list-view, which is
|
||||
the first read-only use-case:
|
||||
@@ -1150,7 +1156,7 @@ The console output of this script is then:
|
||||
|
||||
An important reason why DQL is favourable to the Query API of most
|
||||
ORMs is its similarity to SQL. The DQL language allows query
|
||||
constructs that most ORMs don't, GROUP BY even with HAVING,
|
||||
constructs that most ORMs don't: GROUP BY even with HAVING,
|
||||
Sub-selects, Fetch-Joins of nested classes, mixed results with
|
||||
entities and scalar data such as COUNT() results and much more.
|
||||
Using DQL you should seldom come to the point where you want to
|
||||
@@ -1225,7 +1231,7 @@ write scenarios:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// show_bug.php
|
||||
// show_bug.php <id>
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theBugId = $argv[1];
|
||||
@@ -1300,7 +1306,7 @@ and usage of bound parameters:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// dashboard.php
|
||||
// dashboard.php <user-id>
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theUserId = $argv[1];
|
||||
@@ -1366,7 +1372,7 @@ should be able to close a bug. This looks like:
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
// close_bug.php
|
||||
// close_bug.php <bug-id>
|
||||
require_once "bootstrap.php";
|
||||
|
||||
$theBugId = $argv[1];
|
||||
@@ -1498,13 +1504,17 @@ we have to adjust the metadata slightly.
|
||||
<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
|
||||
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Bug" table="bugs" repository-class="BugRepository">
|
||||
|
||||
</entity>
|
||||
</doctrine-mapping>
|
||||
|
||||
.. note::
|
||||
The YAML driver is deprecated and will be removed in version 3.0.
|
||||
It is strongly recommended to switch to one of the other mappings.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Bug:
|
||||
@@ -1535,6 +1545,16 @@ As an example here is the code of the first use case "List of Bugs":
|
||||
Using EntityRepositories you can avoid coupling your model with specific query logic.
|
||||
You can also re-use query logic easily throughout your application.
|
||||
|
||||
The method ``count()`` takes an array of fields or association keys and the values to match against.
|
||||
This provides you with a convenient and lightweight way to count a resultset when you don't need to
|
||||
deal with it:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
$productCount = $entityManager->getRepository(Product::class)
|
||||
->count(['name' => $productName]);
|
||||
|
||||
Conclusion
|
||||
----------
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ which has mapping metadata that is overridden by the annotation above:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
/**
|
||||
* Trait class
|
||||
*/
|
||||
@@ -82,6 +83,7 @@ The case for just extending a class would be just the same but:
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
<?php
|
||||
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
|
||||
{
|
||||
// ...
|
||||
|
||||
@@ -3,7 +3,7 @@ Working with Indexed Associations
|
||||
|
||||
.. note::
|
||||
|
||||
This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series.
|
||||
This feature is available from version 2.1 of Doctrine.
|
||||
|
||||
Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in
|
||||
the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY``
|
||||
@@ -13,8 +13,8 @@ The feature works like an implicit ``INDEX BY`` for the selected association but
|
||||
downsides also:
|
||||
|
||||
- You have to manage both the key and field if you want to change the index by field value.
|
||||
- On each request the keys are regenerated from the field value not from the previous collection key.
|
||||
- Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes.
|
||||
- On each request the keys are regenerated from the field value, and not from the previous collection key.
|
||||
- Values of the Index-By keys are never considered during persistence. They only exist for accessing purposes.
|
||||
- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities
|
||||
with the same index-by field value is undefined.
|
||||
|
||||
@@ -107,7 +107,7 @@ The code and mappings for the Market entity looks like this:
|
||||
<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
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Market">
|
||||
<id name="id" type="integer">
|
||||
@@ -164,8 +164,6 @@ here are the code and mappings for it:
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* For real this column would have to be unique=true. But I want to test behavior of non-unique overrides.
|
||||
*
|
||||
* @Column(type="string", unique=true)
|
||||
*/
|
||||
private $symbol;
|
||||
@@ -195,7 +193,7 @@ here are the code and mappings for it:
|
||||
<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
|
||||
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||
|
||||
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
|
||||
<id name="id" type="integer">
|
||||
@@ -227,7 +225,7 @@ here are the code and mappings for it:
|
||||
Querying indexed associations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now that we defined the stocks collection to be indexed by symbol we can take a look at some code,
|
||||
Now that we defined the stocks collection to be indexed by symbol, we can take a look at some code
|
||||
that makes use of the indexing.
|
||||
|
||||
First we will populate our database with two example stocks traded on a single market:
|
||||
@@ -263,7 +261,7 @@ now query for the market:
|
||||
|
||||
echo $stock->getSymbol(); // will print "AAPL"
|
||||
|
||||
The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection
|
||||
The implementation of ``Market::addStock()``, in combination with ``indexBy``, allows us to access the collection
|
||||
consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not.
|
||||
|
||||
The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association.
|
||||
@@ -285,8 +283,8 @@ The same applies to DQL queries: The ``indexBy`` configuration acts as implicit
|
||||
|
||||
echo $stock->getSymbol(); // will print "AAPL"
|
||||
|
||||
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally
|
||||
indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as
|
||||
If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally,
|
||||
indexed associations also work with the ``Collection::slice()`` functionality, even if the association's fetch mode is
|
||||
LAZY or EXTRA_LAZY.
|
||||
|
||||
Outlook into the Future
|
||||
@@ -294,5 +292,5 @@ Outlook into the Future
|
||||
|
||||
For the inverse side of a many-to-many associations there will be a way to persist the keys and the order
|
||||
as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||
This feature cannot be implemented for One-To-Many associations, because they are never the owning side.
|
||||
This feature cannot be implemented for one-to-many associations, because they are never the owning side.
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="result-class" type="xs:string" />
|
||||
<xs:attribute name="result-class" type="orm:fqcn" />
|
||||
<xs:attribute name="result-set-mapping" type="xs:string" />
|
||||
</xs:complexType>
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
<xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="class" type="xs:string"/>
|
||||
<xs:attribute name="class" type="orm:fqcn"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="entity-listeners">
|
||||
@@ -139,7 +139,7 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="field-result" type="orm:field-result" minOccurs="0" maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
<xs:attribute name="entity-class" type="xs:string" use="required" />
|
||||
<xs:attribute name="entity-class" type="orm:fqcn" use="required" />
|
||||
<xs:attribute name="discriminator-column" type="xs:string" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="entity">
|
||||
<xs:sequence>
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="cache" type="orm:cache" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="options" type="orm:options" minOccurs="0" />
|
||||
<xs:element name="indexes" type="orm:indexes" minOccurs="0"/>
|
||||
@@ -189,17 +189,24 @@
|
||||
<xs:element name="association-overrides" type="orm:association-overrides" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:element name="attribute-overrides" type="orm:attribute-overrides" minOccurs="0" maxOccurs="unbounded" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
</xs:choice>
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="table" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="table" type="orm:tablename" />
|
||||
<xs:attribute name="schema" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="repository-class" type="xs:string"/>
|
||||
<xs:attribute name="repository-class" type="orm:fqcn"/>
|
||||
<xs:attribute name="inheritance-type" type="orm:inheritance-type"/>
|
||||
<xs:attribute name="change-tracking-policy" type="orm:change-tracking-policy" />
|
||||
<xs:attribute name="read-only" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="tablename" id="tablename">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="[a-zA-Z_u01-uff.]+" id="tablename.pattern">
|
||||
</xs:pattern>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="option" mixed="true">
|
||||
<xs:sequence minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element name="option" type="orm:option"/>
|
||||
@@ -302,7 +309,7 @@
|
||||
|
||||
<xs:complexType name="embedded">
|
||||
<xs:attribute name="name" type="xs:string" use="required" />
|
||||
<xs:attribute name="class" type="xs:string" use="required" />
|
||||
<xs:attribute name="class" type="orm:fqcn" use="required" />
|
||||
<xs:attribute name="column-prefix" type="xs:string" use="optional" />
|
||||
<xs:attribute name="use-column-prefix" type="xs:boolean" default="true" use="optional" />
|
||||
</xs:complexType>
|
||||
@@ -361,7 +368,7 @@
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="value" type="xs:NMTOKEN" use="required"/>
|
||||
<xs:attribute name="class" type="xs:string" use="required"/>
|
||||
<xs:attribute name="class" type="orm:fqcn" use="required"/>
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -412,9 +419,16 @@
|
||||
<xs:sequence>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="class" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="class" type="orm:fqcn" use="required" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="fqcn" id="fqcn">
|
||||
<xs:restriction base="xs:token">
|
||||
<xs:pattern value="[a-zA-Z_u01-uff][a-zA-Z0-9_u01-uff]+" id="fqcn.pattern">
|
||||
</xs:pattern>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="inverse-join-columns">
|
||||
<xs:sequence>
|
||||
<xs:element name="join-column" type="orm:join-column" minOccurs="1" maxOccurs="unbounded" />
|
||||
@@ -487,11 +501,11 @@
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
@@ -504,12 +518,12 @@
|
||||
<xs:element name="order-by" type="orm:order-by" minOccurs="0" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="index-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -524,11 +538,10 @@
|
||||
</xs:choice>
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -547,8 +560,8 @@
|
||||
<xs:attribute name="target-entity" type="xs:string" use="required" />
|
||||
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" default="LAZY" />
|
||||
<xs:attribute name="orphan-removal" type="xs:boolean" default="false" />
|
||||
<xs:anyAttribute namespace="##other"/>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -563,9 +576,15 @@
|
||||
<xs:sequence>
|
||||
<xs:element name="join-table" type="orm:join-table" minOccurs="0" />
|
||||
<xs:element name="join-columns" type="orm:join-columns" minOccurs="0" />
|
||||
<xs:element name="inversed-by" type="orm:inversed-by-override" minOccurs="0" maxOccurs="1" />
|
||||
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
<xs:attribute name="fetch" type="orm:fetch-type" use="optional" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="inversed-by-override">
|
||||
<xs:attribute name="name" type="xs:NMTOKEN" use="required" />
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="attribute-overrides">
|
||||
|
||||
@@ -19,17 +19,16 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Persistence\Mapping\MappingException;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
|
||||
use Doctrine\ORM\Query\Parameter;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
|
||||
use Doctrine\ORM\Cache;
|
||||
use Doctrine\ORM\Query\QueryException;
|
||||
|
||||
/**
|
||||
* Base contract for ORM queries. Base class for Query and NativeQuery.
|
||||
*
|
||||
@@ -96,17 +95,17 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_hints = array();
|
||||
protected $_hints = [];
|
||||
|
||||
/**
|
||||
* The hydration mode.
|
||||
*
|
||||
* @var integer
|
||||
* @var string|int
|
||||
*/
|
||||
protected $_hydrationMode = self::HYDRATE_OBJECT;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
*/
|
||||
protected $_queryCacheProfile;
|
||||
|
||||
@@ -118,7 +117,7 @@ abstract class AbstractQuery
|
||||
protected $_expireResultCache = false;
|
||||
|
||||
/**
|
||||
* @param \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
* @var \Doctrine\DBAL\Cache\QueryCacheProfile
|
||||
*/
|
||||
protected $_hydrationCacheProfile;
|
||||
|
||||
@@ -178,7 +177,6 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Enable/disable second level query (result) caching for this query.
|
||||
*
|
||||
* @param boolean $cacheable
|
||||
@@ -215,7 +213,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Obtain the name of the second level query cache region in which query results will be stored
|
||||
*
|
||||
* @return The cache region name; NULL indicates the default region.
|
||||
* @return string|null The cache region name; NULL indicates the default region.
|
||||
*/
|
||||
public function getCacheRegion()
|
||||
{
|
||||
@@ -243,7 +241,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param integer $lifetime
|
||||
*
|
||||
* @return static This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setLifetime($lifetime)
|
||||
{
|
||||
@@ -263,7 +261,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* @param integer $cacheMode
|
||||
*
|
||||
* @return static This query instance.
|
||||
* @return \Doctrine\ORM\AbstractQuery This query instance.
|
||||
*/
|
||||
public function setCacheMode($cacheMode)
|
||||
{
|
||||
@@ -325,14 +323,14 @@ abstract class AbstractQuery
|
||||
public function getParameter($key)
|
||||
{
|
||||
$filteredParameters = $this->parameters->filter(
|
||||
function ($parameter) use ($key)
|
||||
{
|
||||
// Must not be identical because of string to integer conversion
|
||||
return ($key == $parameter->getName());
|
||||
function (Query\Parameter $parameter) use ($key) : bool {
|
||||
$parameterName = $parameter->getName();
|
||||
|
||||
return $key === $parameterName || (string) $key === (string) $parameterName;
|
||||
}
|
||||
);
|
||||
|
||||
return count($filteredParameters) ? $filteredParameters->first() : null;
|
||||
return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,17 +371,10 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setParameter($key, $value, $type = null)
|
||||
{
|
||||
$filteredParameters = $this->parameters->filter(
|
||||
function ($parameter) use ($key)
|
||||
{
|
||||
// Must not be identical because of string to integer conversion
|
||||
return ($key == $parameter->getName());
|
||||
}
|
||||
);
|
||||
$existingParameter = $this->getParameter($key);
|
||||
|
||||
if (count($filteredParameters)) {
|
||||
$parameter = $filteredParameters->first();
|
||||
$parameter->setValue($value, $type);
|
||||
if ($existingParameter !== null) {
|
||||
$existingParameter->setValue($value, $type);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -398,7 +389,7 @@ abstract class AbstractQuery
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array
|
||||
* @return array|string
|
||||
*
|
||||
* @throws \Doctrine\ORM\ORMInvalidArgumentException
|
||||
*/
|
||||
@@ -421,16 +412,24 @@ abstract class AbstractQuery
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_object($value) && $this->_em->getMetadataFactory()->hasMetadataFor(ClassUtils::getClass($value))) {
|
||||
if ($value instanceof Mapping\ClassMetadata) {
|
||||
return $value->name;
|
||||
}
|
||||
|
||||
if (! is_object($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
|
||||
|
||||
if ($value === null) {
|
||||
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
|
||||
}
|
||||
}
|
||||
|
||||
if ($value instanceof Mapping\ClassMetadata) {
|
||||
return $value->name;
|
||||
} catch (MappingException | ORMMappingException $e) {
|
||||
// Silence any mapping exceptions. These can occur if the object in
|
||||
// question is not a mapped entity, in which case we just don't do
|
||||
// any preparation on the value.
|
||||
}
|
||||
|
||||
return $value;
|
||||
@@ -502,7 +501,7 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setHydrationCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ( ! $profile->getResultCacheDriver()) {
|
||||
if ($profile !== null && ! $profile->getResultCacheDriver()) {
|
||||
$resultCacheDriver = $this->_em->getConfiguration()->getHydrationCacheImpl();
|
||||
$profile = $profile->setResultCacheDriver($resultCacheDriver);
|
||||
}
|
||||
@@ -532,7 +531,7 @@ abstract class AbstractQuery
|
||||
*/
|
||||
public function setResultCacheProfile(QueryCacheProfile $profile = null)
|
||||
{
|
||||
if ( ! $profile->getResultCacheDriver()) {
|
||||
if ($profile !== null && ! $profile->getResultCacheDriver()) {
|
||||
$resultCacheDriver = $this->_em->getConfiguration()->getResultCacheImpl();
|
||||
$profile = $profile->setResultCacheDriver($resultCacheDriver);
|
||||
}
|
||||
@@ -691,8 +690,8 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Defines the processing mode to be used during hydration / result set transformation.
|
||||
*
|
||||
* @param integer $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
* @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
|
||||
* One of the Query::HYDRATE_* constants.
|
||||
*
|
||||
* @return static This query instance.
|
||||
*/
|
||||
@@ -706,7 +705,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Gets the hydration mode currently used by the query.
|
||||
*
|
||||
* @return integer
|
||||
* @return string|int
|
||||
*/
|
||||
public function getHydrationMode()
|
||||
{
|
||||
@@ -718,9 +717,9 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return array
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResult($hydrationMode = self::HYDRATE_OBJECT)
|
||||
{
|
||||
@@ -754,7 +753,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Get exactly one result or null.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
@@ -792,12 +791,12 @@ abstract class AbstractQuery
|
||||
* If the result is not unique, a NonUniqueResultException is thrown.
|
||||
* If there is no result, a NoResultException is thrown.
|
||||
*
|
||||
* @param integer $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
* @throws NoResultException If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
|
||||
*/
|
||||
public function getSingleResult($hydrationMode = null)
|
||||
{
|
||||
@@ -823,10 +822,9 @@ abstract class AbstractQuery
|
||||
*
|
||||
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
|
||||
*
|
||||
* @return mixed
|
||||
* @return mixed The scalar result, or NULL if the query returned no result.
|
||||
*
|
||||
* @throws NonUniqueResultException If the query result is not unique.
|
||||
* @throws NoResultException If the query returned no result.
|
||||
*/
|
||||
public function getSingleScalarResult()
|
||||
{
|
||||
@@ -863,7 +861,7 @@ abstract class AbstractQuery
|
||||
/**
|
||||
* Check if the query has a hint
|
||||
*
|
||||
* @param string $name The name of the hint
|
||||
* @param string $name The name of the hint
|
||||
*
|
||||
* @return bool False if the query does not have any hint
|
||||
*/
|
||||
@@ -887,7 +885,7 @@ abstract class AbstractQuery
|
||||
* iterate over the result.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters The query parameters.
|
||||
* @param integer|null $hydrationMode The hydration mode to use.
|
||||
* @param string|int|null $hydrationMode The hydration mode to use.
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\IterableResult
|
||||
*/
|
||||
@@ -911,7 +909,7 @@ abstract class AbstractQuery
|
||||
* Executes the query.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters Query parameters.
|
||||
* @param integer|null $hydrationMode Processing mode to be used during the hydration process.
|
||||
* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -928,7 +926,7 @@ abstract class AbstractQuery
|
||||
* Execute query ignoring second level cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param integer|null $hydrationMode
|
||||
* @param string|int|null $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -956,7 +954,7 @@ abstract class AbstractQuery
|
||||
}
|
||||
|
||||
if ( ! $result) {
|
||||
$result = array();
|
||||
$result = [];
|
||||
}
|
||||
|
||||
$setCacheEntry = function($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) {
|
||||
@@ -986,39 +984,61 @@ abstract class AbstractQuery
|
||||
* Load from second level cache or executes the query and put into cache.
|
||||
*
|
||||
* @param ArrayCollection|array|null $parameters
|
||||
* @param integer|null $hydrationMode
|
||||
* @param string|int|null $hydrationMode
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
|
||||
{
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($this->getHash(), $this->lifetime, $this->cacheMode ?: Cache::MODE_NORMAL);
|
||||
$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
|
||||
$result = $queryCache->get($querykey, $rsm, $this->_hints);
|
||||
$queryKey = new QueryCacheKey(
|
||||
$this->getHash(),
|
||||
$this->lifetime,
|
||||
$this->cacheMode ?: Cache::MODE_NORMAL,
|
||||
$this->getTimestampKey()
|
||||
);
|
||||
|
||||
$result = $queryCache->get($queryKey, $rsm, $this->_hints);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $querykey);
|
||||
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result, $this->_hints);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $querykey);
|
||||
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\TimestampCacheKey|null
|
||||
*/
|
||||
private function getTimestampKey()
|
||||
{
|
||||
$entityName = reset($this->_resultSetMapping->aliasMap);
|
||||
|
||||
if (empty($entityName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$metadata = $this->_em->getClassMetadata($entityName);
|
||||
|
||||
return new Cache\TimestampCacheKey($metadata->rootEntityName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result cache id to use to store the result set cache entry.
|
||||
* Will return the configured id if it exists otherwise a hash will be
|
||||
@@ -1028,7 +1048,7 @@ abstract class AbstractQuery
|
||||
*/
|
||||
protected function getHydrationCacheId()
|
||||
{
|
||||
$parameters = array();
|
||||
$parameters = [];
|
||||
|
||||
foreach ($this->getParameters() as $parameter) {
|
||||
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
|
||||
@@ -1090,7 +1110,7 @@ abstract class AbstractQuery
|
||||
{
|
||||
$this->parameters = new ArrayCollection();
|
||||
|
||||
$this->_hints = array();
|
||||
$this->_hints = [];
|
||||
$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ class AssociationCacheEntry implements CacheEntry
|
||||
public $class;
|
||||
|
||||
/**
|
||||
* @param string $class The entity class.
|
||||
* @param array $identifier The entity identifier.
|
||||
* @param string $class The entity class.
|
||||
* @param array $identifier The entity identifier.
|
||||
*/
|
||||
public function __construct($class, array $identifier)
|
||||
{
|
||||
@@ -58,6 +58,8 @@ class AssociationCacheEntry implements CacheEntry
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return AssociationCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -110,7 +110,9 @@ class CacheConfiguration
|
||||
public function getQueryValidator()
|
||||
{
|
||||
if ($this->queryValidator === null) {
|
||||
$this->queryValidator = new TimestampQueryCacheValidator();
|
||||
$this->queryValidator = new TimestampQueryCacheValidator(
|
||||
$this->cacheFactory->getTimestampRegion()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->queryValidator;
|
||||
|
||||
@@ -62,8 +62,9 @@ class CacheException extends ORMException
|
||||
|
||||
/**
|
||||
* @param string $entityName
|
||||
* @param string $field
|
||||
*
|
||||
* @return \Doctrine\ORM\Cache\CacheException
|
||||
* @return CacheException
|
||||
*/
|
||||
public static function nonCacheableEntityAssociation($entityName, $field)
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ class CollectionCacheEntry implements CacheEntry
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return self
|
||||
* @return CollectionCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ class DefaultCache implements Cache
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCache[]
|
||||
*/
|
||||
private $queryCaches = array();
|
||||
private $queryCaches = [];
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\QueryCache
|
||||
@@ -314,7 +314,7 @@ class DefaultCache implements Cache
|
||||
private function buildCollectionCacheKey(ClassMetadata $metadata, $association, $ownerIdentifier)
|
||||
{
|
||||
if ( ! is_array($ownerIdentifier)) {
|
||||
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);;
|
||||
$ownerIdentifier = $this->toIdentifierArray($metadata, $ownerIdentifier);
|
||||
}
|
||||
|
||||
return new CollectionCacheKey($metadata->rootEntityName, $association, $ownerIdentifier);
|
||||
@@ -336,7 +336,7 @@ class DefaultCache implements Cache
|
||||
}
|
||||
}
|
||||
|
||||
return array($metadata->identifier[0] => $identifier);
|
||||
return [$metadata->identifier[0] => $identifier];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ use Doctrine\ORM\Cache\Persister\Collection\ReadWriteCachedCollectionPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\NonStrictReadWriteCachedEntityPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister;
|
||||
use Doctrine\ORM\Cache\Persister\Entity\ReadWriteCachedEntityPersister;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\Region\DefaultMultiGetRegion;
|
||||
use Doctrine\ORM\Cache\Region\DefaultRegion;
|
||||
use Doctrine\ORM\Cache\Region\FileLockRegion;
|
||||
@@ -64,7 +63,7 @@ class DefaultCacheFactory implements CacheFactory
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region[]
|
||||
*/
|
||||
private $regions = array();
|
||||
private $regions = [];
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
@@ -167,10 +166,10 @@ class DefaultCacheFactory implements CacheFactory
|
||||
return new DefaultQueryCache(
|
||||
$em,
|
||||
$this->getRegion(
|
||||
array(
|
||||
[
|
||||
'region' => $regionName ?: Cache::DEFAULT_QUERY_REGION_NAME,
|
||||
'usage' => ClassMetadata::CACHE_USAGE_NONSTRICT_READ_WRITE
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -200,14 +199,9 @@ class DefaultCacheFactory implements CacheFactory
|
||||
return $this->regions[$cache['region']];
|
||||
}
|
||||
|
||||
$cacheAdapter = clone $this->cache;
|
||||
|
||||
if ($cacheAdapter instanceof CacheProvider) {
|
||||
$cacheAdapter->setNamespace($cache['region']);
|
||||
}
|
||||
|
||||
$name = $cache['region'];
|
||||
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
|
||||
$name = $cache['region'];
|
||||
$cacheAdapter = $this->createRegionCache($name);
|
||||
$lifetime = $this->regionsConfig->getLifetime($cache['region']);
|
||||
|
||||
$region = ($cacheAdapter instanceof MultiGetCache)
|
||||
? new DefaultMultiGetRegion($name, $cacheAdapter, $lifetime)
|
||||
@@ -215,10 +209,13 @@ class DefaultCacheFactory implements CacheFactory
|
||||
|
||||
if ($cache['usage'] === ClassMetadata::CACHE_USAGE_READ_WRITE) {
|
||||
|
||||
if ( ! $this->fileLockRegionDirectory) {
|
||||
if (
|
||||
'' === $this->fileLockRegionDirectory ||
|
||||
null === $this->fileLockRegionDirectory
|
||||
) {
|
||||
throw new \LogicException(
|
||||
'If you what to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
|
||||
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you what to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
|
||||
'If you want to use a "READ_WRITE" cache an implementation of "Doctrine\ORM\Cache\ConcurrentRegion" is required, ' .
|
||||
'The default implementation provided by doctrine is "Doctrine\ORM\Cache\Region\FileLockRegion" if you want to use it please provide a valid directory, DefaultCacheFactory#setFileLockRegionDirectory(). '
|
||||
);
|
||||
}
|
||||
|
||||
@@ -229,6 +226,30 @@ class DefaultCacheFactory implements CacheFactory
|
||||
return $this->regions[$cache['region']] = $region;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return CacheAdapter
|
||||
*/
|
||||
private function createRegionCache($name)
|
||||
{
|
||||
$cacheAdapter = clone $this->cache;
|
||||
|
||||
if (!$cacheAdapter instanceof CacheProvider) {
|
||||
return $cacheAdapter;
|
||||
}
|
||||
|
||||
$namespace = $cacheAdapter->getNamespace();
|
||||
|
||||
if ('' !== $namespace) {
|
||||
$namespace .= ':';
|
||||
}
|
||||
|
||||
$cacheAdapter->setNamespace($namespace . $name);
|
||||
|
||||
return $cacheAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -46,7 +46,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
@@ -62,11 +62,12 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
*/
|
||||
public function buildCacheEntry(ClassMetadata $metadata, CollectionCacheKey $key, $collection)
|
||||
{
|
||||
$data = array();
|
||||
$data = [];
|
||||
|
||||
foreach ($collection as $index => $entity) {
|
||||
$data[$index] = new EntityCacheKey($metadata->name, $this->uow->getEntityIdentifier($entity));
|
||||
$data[$index] = new EntityCacheKey($metadata->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
}
|
||||
|
||||
return new CollectionCacheEntry($data);
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ class DefaultCollectionHydrator implements CollectionHydrator
|
||||
/* @var $targetPersister \Doctrine\ORM\Cache\Persister\CachedPersister */
|
||||
$targetPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$list = array();
|
||||
$list = [];
|
||||
|
||||
$entityEntries = $targetRegion->getMultiple($entry);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ use Doctrine\ORM\Utility\IdentifierFlattener;
|
||||
class DefaultEntityHydrator implements EntityHydrator
|
||||
{
|
||||
/**
|
||||
* @var \Doctrine\ORM\EntityManager
|
||||
* @var \Doctrine\ORM\EntityManagerInterface
|
||||
*/
|
||||
private $em;
|
||||
|
||||
@@ -55,7 +55,7 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
@@ -75,35 +75,48 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
$data = $this->uow->getOriginalEntityData($entity);
|
||||
$data = array_merge($data, $metadata->getIdentifierValues($entity)); // why update has no identifier values ?
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
if ($metadata->isVersioned) {
|
||||
$data[$metadata->versionField] = $metadata->getFieldValue($entity, $metadata->versionField);
|
||||
}
|
||||
|
||||
foreach ($metadata->associationMappings as $name => $assoc) {
|
||||
if ( ! isset($data[$name])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! ($assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
if ( ! ($assoc['type'] & ClassMetadata::TO_ONE)) {
|
||||
unset($data[$name]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset($assoc['cache'])) {
|
||||
$targetClassMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$associationIds = $this->identifierFlattener->flattenIdentifier($targetClassMetadata, $targetClassMetadata->getIdentifierValues($data[$name]));
|
||||
$owningAssociation = ( ! $assoc['isOwningSide'])
|
||||
? $targetClassMetadata->associationMappings[$assoc['mappedBy']]
|
||||
: $assoc;
|
||||
$associationIds = $this->identifierFlattener->flattenIdentifier(
|
||||
$targetClassMetadata,
|
||||
$targetClassMetadata->getIdentifierValues($data[$name])
|
||||
);
|
||||
|
||||
unset($data[$name]);
|
||||
|
||||
foreach ($associationIds as $fieldName => $fieldValue) {
|
||||
if (isset($targetClassMetadata->fieldMappings[$fieldName])) {
|
||||
$fieldMapping = $targetClassMetadata->fieldMappings[$fieldName];
|
||||
|
||||
if (isset($targetClassMetadata->associationMappings[$fieldName])){
|
||||
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
|
||||
$data[$owningAssociation['targetToSourceKeyColumns'][$fieldMapping['columnName']]] = $fieldValue;
|
||||
|
||||
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
|
||||
$data[$localColumn] = $fieldValue;
|
||||
}
|
||||
$targetAssoc = $targetClassMetadata->associationMappings[$fieldName];
|
||||
|
||||
foreach($assoc['targetToSourceKeyColumns'] as $referencedColumn => $localColumn) {
|
||||
if (isset($targetAssoc['sourceToTargetKeyColumns'][$referencedColumn])) {
|
||||
$data[$localColumn] = $fieldValue;
|
||||
}
|
||||
}else{
|
||||
$data[$assoc['targetToSourceKeyColumns'][$targetClassMetadata->columnNames[$fieldName]]] = $fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,11 +139,10 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
// @TODO - fix it !
|
||||
// handle UnitOfWork#createEntity hash generation
|
||||
if ( ! is_array($targetId)) {
|
||||
|
||||
$data[reset($assoc['joinColumnFieldNames'])] = $targetId;
|
||||
|
||||
$targetEntity = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$targetId = array($targetEntity->identifier[0] => $targetId);
|
||||
$targetId = [$targetEntity->identifier[0] => $targetId];
|
||||
}
|
||||
|
||||
$data[$name] = new AssociationCacheEntry($assoc['targetEntity'], $targetId);
|
||||
@@ -167,7 +179,8 @@ class DefaultEntityHydrator implements EntityHydrator
|
||||
continue;
|
||||
}
|
||||
|
||||
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocEntry = $assocRegion->get($assocKey);
|
||||
|
||||
@@ -66,7 +66,7 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $hints = array(Query::HINT_CACHE_ENABLED => true);
|
||||
private static $hints = [Query::HINT_CACHE_ENABLED => true];
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em The entity manager.
|
||||
@@ -86,49 +86,57 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array())
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
|
||||
{
|
||||
if ( ! ($key->cacheMode & Cache::MODE_GET)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$entry = $this->region->get($key);
|
||||
$cacheEntry = $this->region->get($key);
|
||||
|
||||
if ( ! $entry instanceof QueryCacheEntry) {
|
||||
if ( ! $cacheEntry instanceof QueryCacheEntry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! $this->validator->isValid($key, $entry)) {
|
||||
if ( ! $this->validator->isValid($key, $cacheEntry)) {
|
||||
$this->region->evict($key);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$result = [];
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
$region = $persister->getCacheRegion();
|
||||
$regionName = $region->getName();
|
||||
|
||||
$cm = $this->em->getClassMetadata($entityName);
|
||||
|
||||
$generateKeys = function (array $entry) use ($cm): EntityCacheKey {
|
||||
return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
|
||||
};
|
||||
|
||||
$cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result));
|
||||
$entries = $region->getMultiple($cacheKeys);
|
||||
|
||||
// @TODO - move to cache hydration component
|
||||
foreach ($entry->result as $index => $entry) {
|
||||
|
||||
if (($entityEntry = $region->get($entityKey = new EntityCacheKey($entityName, $entry['identifier']))) === null) {
|
||||
foreach ($cacheEntry->result as $index => $entry) {
|
||||
$entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
|
||||
|
||||
if ($entityEntry === null) {
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($regionName, $entityKey);
|
||||
$this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheHit($regionName, $entityKey);
|
||||
$this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
|
||||
}
|
||||
|
||||
if ( ! $hasRelation) {
|
||||
|
||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
|
||||
|
||||
continue;
|
||||
@@ -137,13 +145,13 @@ class DefaultQueryCache implements QueryCache
|
||||
$data = $entityEntry->data;
|
||||
|
||||
foreach ($entry['associations'] as $name => $assoc) {
|
||||
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assoc['identifier']))) === null) {
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
@@ -167,15 +175,20 @@ class DefaultQueryCache implements QueryCache
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
|
||||
$collection = new PersistentCollection($this->em, $targetClass, new ArrayCollection());
|
||||
$generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
|
||||
return new EntityCacheKey($assocMetadata->rootEntityName, $id);
|
||||
};
|
||||
|
||||
$collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
|
||||
$assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
|
||||
$assocEntries = $assocRegion->getMultiple($assocKeys);
|
||||
|
||||
foreach ($assoc['list'] as $assocIndex => $assocId) {
|
||||
$assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
|
||||
|
||||
if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId))) === null) {
|
||||
|
||||
if ($assocEntry === null) {
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
|
||||
$this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
|
||||
}
|
||||
|
||||
$this->uow->hydrationComplete();
|
||||
@@ -188,7 +201,7 @@ class DefaultQueryCache implements QueryCache
|
||||
$collection->hydrateSet($assocIndex, $element);
|
||||
|
||||
if ($this->cacheLogger !== null) {
|
||||
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
|
||||
$this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +210,25 @@ class DefaultQueryCache implements QueryCache
|
||||
$collection->setInitialized(true);
|
||||
}
|
||||
|
||||
foreach ($data as $fieldName => $unCachedAssociationData) {
|
||||
// In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the
|
||||
// cache key information in `$cacheEntry` will not contain details
|
||||
// for fields that are associations.
|
||||
//
|
||||
// This means that `$data` keys for some associations that may
|
||||
// actually not be cached will not be converted to actual association
|
||||
// data, yet they contain L2 cache AssociationCacheEntry objects.
|
||||
//
|
||||
// We need to unwrap those associations into proxy references,
|
||||
// since we don't have actual data for them except for identifiers.
|
||||
if ($unCachedAssociationData instanceof AssociationCacheEntry) {
|
||||
$data[$fieldName] = $this->em->getReference(
|
||||
$unCachedAssociationData->class,
|
||||
$unCachedAssociationData->identifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
|
||||
}
|
||||
|
||||
@@ -208,7 +240,7 @@ class DefaultQueryCache implements QueryCache
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array())
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
|
||||
{
|
||||
if ($rsm->scalarMappings) {
|
||||
throw new CacheException("Second level cache does not support scalar results.");
|
||||
@@ -230,10 +262,9 @@ class DefaultQueryCache implements QueryCache
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = array();
|
||||
$data = [];
|
||||
$entityName = reset($rsm->aliasMap);
|
||||
$hasRelation = ( ! empty($rsm->relationMap));
|
||||
$metadata = $this->em->getClassMetadata($entityName);
|
||||
$rootAlias = key($rsm->aliasMap);
|
||||
$persister = $this->uow->getEntityPersister($entityName);
|
||||
|
||||
if ( ! ($persister instanceof CachedPersister)) {
|
||||
@@ -244,86 +275,185 @@ class DefaultQueryCache implements QueryCache
|
||||
|
||||
foreach ($result as $index => $entity) {
|
||||
$identifier = $this->uow->getEntityIdentifier($entity);
|
||||
$data[$index]['identifier'] = $identifier;
|
||||
$data[$index]['associations'] = array();
|
||||
$entityKey = new EntityCacheKey($entityName, $identifier);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey = new EntityCacheKey($entityName, $identifier))) {
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
|
||||
// Cancel put result if entity put fail
|
||||
if ( ! $persister->storeEntityCache($entity, $entityKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $hasRelation) {
|
||||
continue;
|
||||
}
|
||||
$data[$index]['identifier'] = $identifier;
|
||||
$data[$index]['associations'] = [];
|
||||
|
||||
// @TODO - move to cache hydration components
|
||||
foreach ($rsm->relationMap as $name) {
|
||||
$assoc = $metadata->associationMappings[$name];
|
||||
foreach ($rsm->relationMap as $alias => $name) {
|
||||
$parentAlias = $rsm->parentAliasMap[$alias];
|
||||
$parentClass = $rsm->aliasMap[$parentAlias];
|
||||
$metadata = $this->em->getClassMetadata($parentClass);
|
||||
$assoc = $metadata->associationMappings[$name];
|
||||
$assocValue = $this->getAssociationValue($rsm, $alias, $entity);
|
||||
|
||||
if (($assocValue = $metadata->getFieldValue($entity, $name)) === null || $assocValue instanceof Proxy) {
|
||||
if ($assocValue === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! isset($assoc['cache'])) {
|
||||
throw CacheException::nonCacheableEntityAssociation($entityName, $name);
|
||||
}
|
||||
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
$assocMetadata = $assocPersister->getClassMetadata();
|
||||
|
||||
// Handle *-to-one associations
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
|
||||
|
||||
// Cancel put result if association entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
|
||||
return false;
|
||||
}
|
||||
// root entity association
|
||||
if ($rootAlias === $parentAlias) {
|
||||
// Cancel put result if association put fail
|
||||
if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data[$index]['associations'][$name] = array(
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'identifier' => $assocIdentifier,
|
||||
'type' => $assoc['type']
|
||||
);
|
||||
$data[$index]['associations'][$name] = $assocInfo;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle *-to-many associations
|
||||
$list = array();
|
||||
|
||||
foreach ($assocValue as $assocItemIndex => $assocItem) {
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier))) {
|
||||
|
||||
// Cancel put result if entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
|
||||
return false;
|
||||
}
|
||||
// store single nested association
|
||||
if ( ! is_array($assocValue)) {
|
||||
// Cancel put result if association put fail
|
||||
if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$list[$assocItemIndex] = $assocIdentifier;
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$index]['associations'][$name] = array(
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'type' => $assoc['type'],
|
||||
'list' => $list,
|
||||
);
|
||||
// store array of nested association
|
||||
foreach ($assocValue as $aVal) {
|
||||
// Cancel put result if association put fail
|
||||
if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->region->put($key, new QueryCacheEntry($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
* @param array $assoc
|
||||
* @param mixed $assocValue
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
|
||||
{
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
$assocMetadata = $assocPersister->getClassMetadata();
|
||||
$assocRegion = $assocPersister->getCacheRegion();
|
||||
|
||||
// Handle *-to-one associations
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
|
||||
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
|
||||
|
||||
if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
|
||||
// Entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'identifier' => $assocIdentifier,
|
||||
'type' => $assoc['type']
|
||||
];
|
||||
}
|
||||
|
||||
// Handle *-to-many associations
|
||||
$list = [];
|
||||
|
||||
foreach ($assocValue as $assocItemIndex => $assocItem) {
|
||||
$assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
|
||||
$entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
|
||||
|
||||
if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
|
||||
// Entity put fail
|
||||
if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$list[$assocItemIndex] = $assocIdentifier;
|
||||
}
|
||||
|
||||
return [
|
||||
'targetEntity' => $assocMetadata->rootEntityName,
|
||||
'type' => $assoc['type'],
|
||||
'list' => $list,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Query\ResultSetMapping $rsm
|
||||
* @param string $assocAlias
|
||||
* @param object $entity
|
||||
*
|
||||
* @return array|object
|
||||
*/
|
||||
private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
|
||||
{
|
||||
$path = [];
|
||||
$alias = $assocAlias;
|
||||
|
||||
while (isset($rsm->parentAliasMap[$alias])) {
|
||||
$parent = $rsm->parentAliasMap[$alias];
|
||||
$field = $rsm->relationMap[$alias];
|
||||
$class = $rsm->aliasMap[$parent];
|
||||
|
||||
array_unshift($path, [
|
||||
'field' => $field,
|
||||
'class' => $class
|
||||
]
|
||||
);
|
||||
|
||||
$alias = $parent;
|
||||
}
|
||||
|
||||
return $this->getAssociationPathValue($entity, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param array $path
|
||||
*
|
||||
* @return array|object|null
|
||||
*/
|
||||
private function getAssociationPathValue($value, array $path)
|
||||
{
|
||||
$mapping = array_shift($path);
|
||||
$metadata = $this->em->getClassMetadata($mapping['class']);
|
||||
$assoc = $metadata->associationMappings[$mapping['field']];
|
||||
$value = $metadata->getFieldValue($value, $mapping['field']);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($path)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// Handle *-to-one associations
|
||||
if ($assoc['type'] & ClassMetadata::TO_ONE) {
|
||||
return $this->getAssociationPathValue($value, $path);
|
||||
}
|
||||
|
||||
$values = [];
|
||||
|
||||
foreach ($value as $item) {
|
||||
$values[] = $this->getAssociationPathValue($item, $path);
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
||||
@@ -60,6 +60,8 @@ class EntityCacheEntry implements CacheEntry
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return EntityCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
@@ -69,7 +71,7 @@ class EntityCacheEntry implements CacheEntry
|
||||
/**
|
||||
* Retrieves the entity data resolving cache entries
|
||||
*
|
||||
* @param \Doctrine\ORM\EntityManagerInterfac $em
|
||||
* @param \Doctrine\ORM\EntityManagerInterface $em
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
@@ -53,6 +53,6 @@ class Lock
|
||||
*/
|
||||
public static function createLockRead()
|
||||
{
|
||||
return new self(uniqid(time()));
|
||||
return new self(uniqid(time(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class CacheLoggerChain implements CacheLogger
|
||||
/**
|
||||
* @var array<\Doctrine\ORM\Cache\Logging\CacheLogger>
|
||||
*/
|
||||
private $loggers = array();
|
||||
private $loggers = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
|
||||
@@ -35,17 +35,17 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cacheMissCountMap = array();
|
||||
private $cacheMissCountMap = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cacheHitCountMap = array();
|
||||
private $cacheHitCountMap = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $cachePutCountMap = array();
|
||||
private $cachePutCountMap = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@@ -214,9 +214,9 @@ class StatisticsCacheLogger implements CacheLogger
|
||||
*/
|
||||
public function clearStats()
|
||||
{
|
||||
$this->cachePutCountMap = array();
|
||||
$this->cacheHitCountMap = array();
|
||||
$this->cacheMissCountMap = array();
|
||||
$this->cachePutCountMap = [];
|
||||
$this->cacheHitCountMap = [];
|
||||
$this->cacheMissCountMap = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
@@ -69,7 +70,7 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queuedCache = array();
|
||||
protected $queuedCache = [];
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
@@ -164,10 +165,18 @@ abstract class AbstractCollectionPersister implements CachedCollectionPersister
|
||||
public function storeCollectionCache(CollectionCacheKey $key, $elements)
|
||||
{
|
||||
/* @var $targetPersister CachedEntityPersister */
|
||||
$associationMapping = $this->sourceEntity->associationMappings[$key->association];
|
||||
$targetPersister = $this->uow->getEntityPersister($this->targetEntity->rootEntityName);
|
||||
$targetRegion = $targetPersister->getCacheRegion();
|
||||
$targetHydrator = $targetPersister->getEntityHydrator();
|
||||
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
|
||||
|
||||
// Only preserve ordering if association configured it
|
||||
if ( ! (isset($associationMapping['indexBy']) && $associationMapping['indexBy'])) {
|
||||
// Elements may be an array or a Collection
|
||||
$elements = array_values(is_array($elements) ? $elements : $elements->getValues());
|
||||
}
|
||||
|
||||
$entry = $this->hydrator->buildCacheEntry($this->targetEntity, $key, $elements);
|
||||
|
||||
foreach ($entry->identifiers as $index => $entityKey) {
|
||||
if ($targetRegion->contains($entityKey)) {
|
||||
|
||||
@@ -46,7 +46,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +54,7 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,9 +96,9 @@ class NonStrictReadWriteCachedCollectionPersister extends AbstractCollectionPers
|
||||
|
||||
$this->persister->update($collection);
|
||||
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = array(
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = [
|
||||
'key' => $key,
|
||||
'list' => $collection
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class ReadOnlyCachedCollectionPersister extends NonStrictReadWriteCachedCollecti
|
||||
*/
|
||||
public function update(PersistentCollection $collection)
|
||||
{
|
||||
if ($collection->isDirty() && count($collection->getSnapshot()) > 0) {
|
||||
if ($collection->isDirty() && $collection->getSnapshot()) {
|
||||
throw CacheException::updateReadOnlyCollection(ClassUtils::getClass($collection->getOwner()), $this->association['fieldName']);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +80,7 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,10 +98,10 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][spl_object_hash($collection)] = array(
|
||||
$this->queuedCache['delete'][spl_object_hash($collection)] = [
|
||||
'key' => $key,
|
||||
'lock' => $lock
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,9 +126,9 @@ class ReadWriteCachedCollectionPersister extends AbstractCollectionPersister
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = array(
|
||||
$this->queuedCache['update'][spl_object_hash($collection)] = [
|
||||
'key' => $key,
|
||||
'lock' => $lock
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ use Doctrine\ORM\Cache\CollectionCacheKey;
|
||||
use Doctrine\ORM\Cache\TimestampCacheKey;
|
||||
use Doctrine\ORM\Cache\QueryCacheKey;
|
||||
use Doctrine\ORM\Cache\Persister\CachedPersister;
|
||||
use Doctrine\ORM\Cache\CacheException;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@@ -65,7 +64,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $queuedCache = array();
|
||||
protected $queuedCache = [];
|
||||
|
||||
/**
|
||||
* @var \Doctrine\ORM\Cache\Region
|
||||
@@ -105,7 +104,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* Associations configured as FETCH_EAGER, as well as all inverse one-to-one associations.
|
||||
*
|
||||
* @var array
|
||||
* @var array|null
|
||||
*/
|
||||
protected $joinedAssociations;
|
||||
|
||||
@@ -131,7 +130,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$this->cacheLogger = $cacheConfig->getCacheLogger();
|
||||
$this->timestampRegion = $cacheFactory->getTimestampRegion();
|
||||
$this->hydrator = $cacheFactory->buildEntityHydrator($em, $class);
|
||||
$this->timestampKey = new TimestampCacheKey($this->class->getTableName());
|
||||
$this->timestampKey = new TimestampCacheKey($this->class->rootEntityName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,7 +160,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getCountSQL($criteria = array())
|
||||
public function getCountSQL($criteria = [])
|
||||
{
|
||||
return $this->persister->getCountSQL($criteria);
|
||||
}
|
||||
@@ -234,14 +233,6 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$class = $this->metadataFactory->getMetadataFor($className);
|
||||
}
|
||||
|
||||
if ($class->containsForeignIdentifier) {
|
||||
foreach ($class->associationMappings as $name => $assoc) {
|
||||
if (!empty($assoc['id']) && !isset($assoc['cache'])) {
|
||||
throw CacheException::nonCacheableEntityAssociation($class->name, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entry = $this->hydrator->buildCacheEntry($class, $key, $entity);
|
||||
$cached = $this->region->put($key, $entry);
|
||||
|
||||
@@ -258,7 +249,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
private function storeJoinedAssociations($entity)
|
||||
{
|
||||
if ($this->joinedAssociations === null) {
|
||||
$associations = array();
|
||||
$associations = [];
|
||||
|
||||
foreach ($this->class->associationMappings as $name => $assoc) {
|
||||
if (isset($assoc['cache']) &&
|
||||
@@ -281,7 +272,8 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
}
|
||||
|
||||
$assocId = $this->uow->getEntityIdentifier($assocEntity);
|
||||
$assocKey = new EntityCacheKey($assoc['targetEntity'], $assocId);
|
||||
$assocMetadata = $this->metadataFactory->getMetadataFor($assoc['targetEntity']);
|
||||
$assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId);
|
||||
$assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
|
||||
|
||||
$assocPersister->storeEntityCache($assocEntity, $assocKey);
|
||||
@@ -291,22 +283,21 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* Generates a string of currently query
|
||||
*
|
||||
* @param array $query
|
||||
* @param string $criteria
|
||||
* @param array $orderBy
|
||||
* @param array $query
|
||||
* @param string $criteria
|
||||
* @param array $orderBy
|
||||
* @param integer $limit
|
||||
* @param integer $offset
|
||||
* @param integer $timestamp
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null, $timestamp = null)
|
||||
protected function getHash($query, $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
list($params) = ($criteria instanceof Criteria)
|
||||
? $this->persister->expandCriteriaParameters($criteria)
|
||||
: $this->persister->expandParameters($criteria);
|
||||
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset . $timestamp);
|
||||
return sha1($query . serialize($params) . serialize($orderBy) . $limit . $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,24 +361,23 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(array $criteria, $entity = null, $assoc = null, array $hints = array(), $lockMode = null, $limit = null, array $orderBy = null)
|
||||
public function load(array $criteria, $entity = null, $assoc = null, array $hints = [], $lockMode = null, $limit = null, array $orderBy = null)
|
||||
{
|
||||
if ($entity !== null || $assoc !== null || ! empty($hints) || $lockMode !== null) {
|
||||
return $this->persister->load($criteria, $entity, $assoc, $hints, $lockMode, $limit, $orderBy);
|
||||
}
|
||||
|
||||
//handle only EntityRepository#findOneBy
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, null, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
$result = $queryCache->get($queryKey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
return $result[0];
|
||||
@@ -397,15 +387,15 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
return null;
|
||||
}
|
||||
|
||||
$cached = $queryCache->put($querykey, $rsm, array($result));
|
||||
$cached = $queryCache->put($queryKey, $rsm, [$result]);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,34 +405,33 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAll(array $criteria = array(), array $orderBy = null, $limit = null, $offset = null)
|
||||
public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$query = $this->persister->getSelectSQL($criteria, null, null, $limit, $offset, $orderBy);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$result = $queryCache->get($querykey, $rsm);
|
||||
$result = $queryCache->get($queryKey, $rsm);
|
||||
|
||||
if ($result !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadAll($criteria, $orderBy, $limit, $offset);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,7 +477,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$cacheEntry = $this->hydrator->buildCacheEntry($class, $cacheKey, $entity);
|
||||
$cached = $this->region->put($cacheKey, $cacheEntry);
|
||||
|
||||
if ($cached && ($this->joinedAssociations === null || count($this->joinedAssociations) > 0)) {
|
||||
if ($cached && (null === $this->joinedAssociations || $this->joinedAssociations)) {
|
||||
$this->storeJoinedAssociations($entity);
|
||||
}
|
||||
|
||||
@@ -506,7 +495,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function count($criteria = array())
|
||||
public function count($criteria = [])
|
||||
{
|
||||
return $this->persister->count($criteria);
|
||||
}
|
||||
@@ -516,32 +505,34 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
*/
|
||||
public function loadCriteria(Criteria $criteria)
|
||||
{
|
||||
$orderBy = $criteria->getOrderings();
|
||||
$limit = $criteria->getMaxResults();
|
||||
$offset = $criteria->getFirstResult();
|
||||
$query = $this->persister->getSelectSQL($criteria);
|
||||
$timestamp = $this->timestampRegion->get($this->timestampKey);
|
||||
$hash = $this->getHash($query, $criteria, null, null, null, $timestamp ? $timestamp->time : null);
|
||||
$hash = $this->getHash($query, $criteria, $orderBy, $limit, $offset);
|
||||
$rsm = $this->getResultSetMapping();
|
||||
$querykey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL);
|
||||
$queryKey = new QueryCacheKey($hash, 0, Cache::MODE_NORMAL, $this->timestampKey);
|
||||
$queryCache = $this->cache->getQueryCache($this->regionName);
|
||||
$cacheResult = $queryCache->get($querykey, $rsm);
|
||||
$cacheResult = $queryCache->get($queryKey, $rsm);
|
||||
|
||||
if ($cacheResult !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheHit($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
return $cacheResult;
|
||||
}
|
||||
|
||||
$result = $this->persister->loadCriteria($criteria);
|
||||
$cached = $queryCache->put($querykey, $rsm, $result);
|
||||
$cached = $queryCache->put($queryKey, $rsm, $result);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
if ($result) {
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCacheMiss($this->regionName, $queryKey);
|
||||
}
|
||||
|
||||
if ($cached) {
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $querykey);
|
||||
$this->cacheLogger->queryCachePut($this->regionName, $queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,28 +548,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$hasCache = ($persister instanceof CachedPersister);
|
||||
$key = null;
|
||||
|
||||
if ($hasCache) {
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
if ( ! $hasCache) {
|
||||
return $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
|
||||
}
|
||||
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
|
||||
return $list;
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
$list = $this->persister->loadManyToManyCollection($assoc, $sourceEntity, $coll);
|
||||
|
||||
if ($hasCache) {
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
|
||||
return $list;
|
||||
@@ -592,28 +583,28 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$persister = $this->uow->getCollectionPersister($assoc);
|
||||
$hasCache = ($persister instanceof CachedPersister);
|
||||
|
||||
if ($hasCache) {
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = new CollectionCacheKey($assoc['sourceEntity'], $assoc['fieldName'], $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
if ( ! $hasCache) {
|
||||
return $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
|
||||
}
|
||||
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
$ownerId = $this->uow->getEntityIdentifier($coll->getOwner());
|
||||
$key = $this->buildCollectionCacheKey($assoc, $ownerId);
|
||||
$list = $persister->loadCollectionCache($coll, $key);
|
||||
|
||||
return $list;
|
||||
if ($list !== null) {
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheHit($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
$list = $this->persister->loadOneToManyCollection($assoc, $sourceEntity, $coll);
|
||||
|
||||
if ($hasCache) {
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
$persister->storeCollectionCache($key, $list);
|
||||
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
if ($this->cacheLogger) {
|
||||
$this->cacheLogger->collectionCacheMiss($persister->getCacheRegion()->getName(), $key);
|
||||
}
|
||||
|
||||
return $list;
|
||||
@@ -622,7 +613,7 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = array())
|
||||
public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
|
||||
{
|
||||
return $this->persister->loadOneToOneEntity($assoc, $sourceEntity, $identifier);
|
||||
}
|
||||
@@ -643,4 +634,17 @@ abstract class AbstractEntityPersister implements CachedEntityPersister
|
||||
$this->persister->refresh($id, $entity, $lockMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $association
|
||||
* @param array $ownerId
|
||||
*
|
||||
* @return CollectionCacheKey
|
||||
*/
|
||||
protected function buildCollectionCacheKey(array $association, $ownerId)
|
||||
{
|
||||
/** @var ClassMetadata $metadata */
|
||||
$metadata = $this->metadataFactory->getMetadataFor($association['sourceEntity']);
|
||||
|
||||
return new CollectionCacheKey($metadata->rootEntityName, $association['fieldName'], $ownerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,9 @@ interface CachedEntityPersister extends CachedPersister, EntityPersister
|
||||
public function getEntityHydrator();
|
||||
|
||||
/**
|
||||
* @param object $entity
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
|
||||
* @param object $entity
|
||||
* @param \Doctrine\ORM\Cache\EntityCacheKey $key
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function storeEntityCache($entity, EntityCacheKey $key);
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
|
||||
namespace Doctrine\ORM\Cache\Persister\Entity;
|
||||
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
|
||||
/**
|
||||
* Specific non-strict read/write cached entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
@@ -62,7 +62,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
$this->timestampRegion->update($this->timestampKey);
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +70,7 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function afterTransactionRolledBack()
|
||||
{
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,13 +78,16 @@ class NonStrictReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$deleted = $this->persister->delete($entity);
|
||||
|
||||
if ($this->persister->delete($entity)) {
|
||||
if ($deleted) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = $key;
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,7 @@ use Doctrine\ORM\Cache\EntityCacheKey;
|
||||
* Specific read-write entity persister
|
||||
*
|
||||
* @author Fabio B. Silva <fabio.bat.silva@gmail.com>
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @since 2.5
|
||||
*/
|
||||
class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
@@ -72,7 +73,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
$this->timestampRegion->update($this->timestampKey);
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +93,7 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
}
|
||||
}
|
||||
|
||||
$this->queuedCache = array();
|
||||
$this->queuedCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,21 +101,24 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
*/
|
||||
public function delete($entity)
|
||||
{
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
$key = new EntityCacheKey($this->class->rootEntityName, $this->uow->getEntityIdentifier($entity));
|
||||
$lock = $this->region->lock($key);
|
||||
$deleted = $this->persister->delete($entity);
|
||||
|
||||
if ($this->persister->delete($entity)) {
|
||||
if ($deleted) {
|
||||
$this->region->evict($key);
|
||||
}
|
||||
|
||||
if ($lock === null) {
|
||||
return;
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
$this->queuedCache['delete'][] = array(
|
||||
$this->queuedCache['delete'][] = [
|
||||
'lock' => $lock,
|
||||
'key' => $key
|
||||
);
|
||||
];
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,9 +135,9 @@ class ReadWriteCachedEntityPersister extends AbstractEntityPersister
|
||||
return;
|
||||
}
|
||||
|
||||
$this->queuedCache['update'][] = array(
|
||||
$this->queuedCache['update'][] = [
|
||||
'lock' => $lock,
|
||||
'key' => $key
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ interface QueryCache
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = array());
|
||||
public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []);
|
||||
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\QueryCacheKey $key
|
||||
@@ -53,7 +53,7 @@ interface QueryCache
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = array());
|
||||
public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []);
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\Cache\Region
|
||||
|
||||
@@ -38,22 +38,24 @@ class QueryCacheEntry implements CacheEntry
|
||||
/**
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var integer Time creation of this cache entry
|
||||
* @var float Time creation of this cache entry
|
||||
*/
|
||||
public $time;
|
||||
|
||||
/**
|
||||
* @param array $result
|
||||
* @param integer $time
|
||||
* @param float $time
|
||||
*/
|
||||
public function __construct($result, $time = null)
|
||||
{
|
||||
$this->result = $result;
|
||||
$this->time = $time ?: time();
|
||||
$this->time = $time ?: microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $values
|
||||
*
|
||||
* @return QueryCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -45,14 +45,27 @@ class QueryCacheKey extends CacheKey
|
||||
public $cacheMode;
|
||||
|
||||
/**
|
||||
* @param string $hash Result cache id
|
||||
* @param integer $lifetime Query lifetime
|
||||
* @param integer $cacheMode Query cache mode
|
||||
* READ-ONLY: Public only for performance reasons, it should be considered immutable.
|
||||
*
|
||||
* @var TimestampCacheKey|null
|
||||
*/
|
||||
public function __construct($hash, $lifetime = 0, $cacheMode = Cache::MODE_NORMAL)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->cacheMode = $cacheMode;
|
||||
public $timestampKey;
|
||||
|
||||
/**
|
||||
* @param string $hash Result cache id
|
||||
* @param integer $lifetime Query lifetime
|
||||
* @param int $cacheMode Query cache mode
|
||||
* @param TimestampCacheKey|null $timestampKey
|
||||
*/
|
||||
public function __construct(
|
||||
$hash,
|
||||
$lifetime = 0,
|
||||
$cacheMode = Cache::MODE_NORMAL,
|
||||
TimestampCacheKey $timestampKey = null
|
||||
) {
|
||||
$this->hash = $hash;
|
||||
$this->lifetime = $lifetime;
|
||||
$this->cacheMode = $cacheMode;
|
||||
$this->timestampKey = $timestampKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
namespace Doctrine\ORM\Cache\Region;
|
||||
|
||||
use Doctrine\Common\Cache\MultiGetCache;
|
||||
use Doctrine\ORM\Cache\Region;
|
||||
use Doctrine\ORM\Cache\CollectionCacheEntry;
|
||||
|
||||
/**
|
||||
@@ -56,7 +55,7 @@ class DefaultMultiGetRegion extends DefaultRegion
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection)
|
||||
{
|
||||
$keysToRetrieve = array();
|
||||
$keysToRetrieve = [];
|
||||
|
||||
foreach ($collection->identifiers as $index => $key) {
|
||||
$keysToRetrieve[$index] = $this->getCacheEntryKey($key);
|
||||
@@ -67,7 +66,7 @@ class DefaultMultiGetRegion extends DefaultRegion
|
||||
return null;
|
||||
}
|
||||
|
||||
$returnableItems = array();
|
||||
$returnableItems = [];
|
||||
foreach ($keysToRetrieve as $index => $key) {
|
||||
$returnableItems[$index] = $items[$key];
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class DefaultRegion implements Region
|
||||
*/
|
||||
public function getMultiple(CollectionCacheEntry $collection)
|
||||
{
|
||||
$result = array();
|
||||
$result = [];
|
||||
|
||||
foreach ($collection->identifiers as $key) {
|
||||
$entryKey = $this->getCacheEntryKey($key);
|
||||
|
||||
@@ -114,7 +114,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
/**
|
||||
* @param \Doctrine\ORM\Cache\CacheKey $key
|
||||
*
|
||||
* return string
|
||||
* @return string
|
||||
*/
|
||||
private function getLockFileName(CacheKey $key)
|
||||
{
|
||||
@@ -124,7 +124,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
/**
|
||||
* @param string $filename
|
||||
*
|
||||
* return string
|
||||
* @return string
|
||||
*/
|
||||
private function getLockContent($filename)
|
||||
{
|
||||
@@ -134,7 +134,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
/**
|
||||
* @param string $filename
|
||||
*
|
||||
* return integer
|
||||
* @return integer
|
||||
*/
|
||||
private function getLockTime($filename)
|
||||
{
|
||||
@@ -142,7 +142,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
@@ -150,7 +150,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function contains(CacheKey $key)
|
||||
{
|
||||
@@ -162,7 +162,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(CacheKey $key)
|
||||
{
|
||||
@@ -186,7 +186,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(CacheKey $key, CacheEntry $entry, Lock $lock = null)
|
||||
{
|
||||
@@ -198,7 +198,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evict(CacheKey $key)
|
||||
{
|
||||
@@ -210,7 +210,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function evictAll()
|
||||
{
|
||||
@@ -228,7 +228,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lock(CacheKey $key)
|
||||
{
|
||||
@@ -248,7 +248,7 @@ class FileLockRegion implements ConcurrentRegion
|
||||
}
|
||||
|
||||
/**
|
||||
* {inheritdoc}
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unlock(CacheKey $key, Lock $lock)
|
||||
{
|
||||
|
||||
@@ -31,12 +31,12 @@ class RegionsConfiguration
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lifetimes = array();
|
||||
private $lifetimes = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $lockLifetimes = array();
|
||||
private $lockLifetimes = [];
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
|
||||
@@ -40,7 +40,7 @@ class TimestampCacheEntry implements CacheEntry
|
||||
*/
|
||||
public function __construct($time = null)
|
||||
{
|
||||
$this->time = $time ? (float)$time : microtime(true);
|
||||
$this->time = $time ? (float) $time : microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,6 +49,8 @@ class TimestampCacheEntry implements CacheEntry
|
||||
* This method allow Doctrine\Common\Cache\PhpFileCache compatibility
|
||||
*
|
||||
* @param array $values array containing property values
|
||||
*
|
||||
* @return TimestampCacheEntry
|
||||
*/
|
||||
public static function __set_state(array $values)
|
||||
{
|
||||
|
||||
@@ -26,15 +26,49 @@ namespace Doctrine\ORM\Cache;
|
||||
*/
|
||||
class TimestampQueryCacheValidator implements QueryCacheValidator
|
||||
{
|
||||
/**
|
||||
* @var TimestampRegion
|
||||
*/
|
||||
private $timestampRegion;
|
||||
|
||||
/**
|
||||
* @param TimestampRegion $timestampRegion
|
||||
*/
|
||||
public function __construct(TimestampRegion $timestampRegion)
|
||||
{
|
||||
$this->timestampRegion = $timestampRegion;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isValid(QueryCacheKey $key, QueryCacheEntry $entry)
|
||||
{
|
||||
if ($this->regionUpdated($key, $entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($key->lifetime == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ($entry->time + $key->lifetime) > time();
|
||||
return ($entry->time + $key->lifetime) > microtime(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param QueryCacheKey $key
|
||||
* @param QueryCacheEntry $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function regionUpdated(QueryCacheKey $key, QueryCacheEntry $entry)
|
||||
{
|
||||
if ($key->timestampKey === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timestamp = $this->timestampRegion->get($key->timestampKey);
|
||||
|
||||
return $timestamp && $timestamp->time > $entry->time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ use Doctrine\Common\Annotations\CachedReader;
|
||||
use Doctrine\Common\Annotations\SimpleAnnotationReader;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Doctrine\Common\Cache\Cache as CacheDriver;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\Common\Proxy\AbstractProxyFactory;
|
||||
use Doctrine\ORM\Cache\CacheConfiguration;
|
||||
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataFactory;
|
||||
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
|
||||
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
|
||||
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
|
||||
@@ -98,7 +100,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setAutoGenerateProxyClasses($autoGenerate)
|
||||
{
|
||||
$this->_attributes['autoGenerateProxyClasses'] = (int)$autoGenerate;
|
||||
$this->_attributes['autoGenerateProxyClasses'] = (int) $autoGenerate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +151,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @return AnnotationDriver
|
||||
*/
|
||||
public function newDefaultAnnotationDriver($paths = array(), $useSimpleAnnotationReader = true)
|
||||
public function newDefaultAnnotationDriver($paths = [], $useSimpleAnnotationReader = true)
|
||||
{
|
||||
AnnotationRegistry::registerFile(__DIR__ . '/Mapping/Driver/DoctrineAnnotations.php');
|
||||
|
||||
@@ -349,7 +351,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function addNamedNativeQuery($name, $sql, Query\ResultSetMapping $rsm)
|
||||
{
|
||||
$this->_attributes['namedNativeQueries'][$name] = array($sql, $rsm);
|
||||
$this->_attributes['namedNativeQueries'][$name] = [$sql, $rsm];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -418,15 +420,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomStringFunction($name, $className)
|
||||
{
|
||||
if (Query\Parser::isInternalFunction($name)) {
|
||||
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
|
||||
}
|
||||
|
||||
$this->_attributes['customStringFunctions'][strtolower($name)] = $className;
|
||||
}
|
||||
|
||||
@@ -476,15 +472,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomNumericFunction($name, $className)
|
||||
{
|
||||
if (Query\Parser::isInternalFunction($name)) {
|
||||
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
|
||||
}
|
||||
|
||||
$this->_attributes['customNumericFunctions'][strtolower($name)] = $className;
|
||||
}
|
||||
|
||||
@@ -534,15 +524,9 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
* @param string|callable $className Class name or a callable that returns the function.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function addCustomDatetimeFunction($name, $className)
|
||||
{
|
||||
if (Query\Parser::isInternalFunction($name)) {
|
||||
throw ORMException::overwriteInternalDQLFunctionNotAllowed($name);
|
||||
}
|
||||
|
||||
$this->_attributes['customDatetimeFunctions'][strtolower($name)] = $className;
|
||||
}
|
||||
|
||||
@@ -590,7 +574,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function setCustomHydrationModes($modes)
|
||||
{
|
||||
$this->_attributes['customHydrationModes'] = array();
|
||||
$this->_attributes['customHydrationModes'] = [];
|
||||
|
||||
foreach ($modes as $modeName => $hydrator) {
|
||||
$this->addCustomHydrationMode($modeName, $hydrator);
|
||||
@@ -642,7 +626,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
public function getClassMetadataFactoryName()
|
||||
{
|
||||
if ( ! isset($this->_attributes['classMetadataFactoryName'])) {
|
||||
$this->_attributes['classMetadataFactoryName'] = 'Doctrine\ORM\Mapping\ClassMetadataFactory';
|
||||
$this->_attributes['classMetadataFactoryName'] = ClassMetadataFactory::class;
|
||||
}
|
||||
|
||||
return $this->_attributes['classMetadataFactoryName'];
|
||||
@@ -664,7 +648,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*
|
||||
* @param string $name The name of the filter.
|
||||
*
|
||||
* @return string The class name of the filter, or null of it is not
|
||||
* @return string The class name of the filter, or null if it is not
|
||||
* defined.
|
||||
*/
|
||||
public function getFilterClassName($name)
|
||||
@@ -689,7 +673,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
$reflectionClass = new \ReflectionClass($className);
|
||||
|
||||
if ( ! $reflectionClass->implementsInterface('Doctrine\Common\Persistence\ObjectRepository')) {
|
||||
if ( ! $reflectionClass->implementsInterface(ObjectRepository::class)) {
|
||||
throw ORMException::invalidEntityRepository($className);
|
||||
}
|
||||
|
||||
@@ -707,7 +691,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
{
|
||||
return isset($this->_attributes['defaultRepositoryClassName'])
|
||||
? $this->_attributes['defaultRepositoryClassName']
|
||||
: 'Doctrine\ORM\EntityRepository';
|
||||
: EntityRepository::class;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -881,7 +865,7 @@ class Configuration extends \Doctrine\DBAL\Configuration
|
||||
*/
|
||||
public function getDefaultQueryHints()
|
||||
{
|
||||
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : array();
|
||||
return isset($this->_attributes['defaultQueryHints']) ? $this->_attributes['defaultQueryHints'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Exception;
|
||||
use Doctrine\Common\EventManager;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Proxy\ProxyFactory;
|
||||
use Doctrine\ORM\Query\FilterCollection;
|
||||
use Doctrine\Common\Util\ClassUtils;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* The EntityManager is the central access point to ORM functionality.
|
||||
@@ -236,9 +238,9 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
$this->conn->commit();
|
||||
|
||||
return $return ?: true;
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->close();
|
||||
$this->conn->rollback();
|
||||
$this->conn->rollBack();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
@@ -257,7 +259,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
$this->conn->rollback();
|
||||
$this->conn->rollBack();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -289,7 +291,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
$query = new Query($this);
|
||||
|
||||
if ( ! empty($dql)) {
|
||||
$query->setDql($dql);
|
||||
$query->setDQL($dql);
|
||||
}
|
||||
|
||||
return $query;
|
||||
@@ -310,7 +312,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
{
|
||||
$query = new NativeQuery($this);
|
||||
|
||||
$query->setSql($sql);
|
||||
$query->setSQL($sql);
|
||||
$query->setResultSetMapping($rsm);
|
||||
|
||||
return $query;
|
||||
@@ -348,6 +350,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
*
|
||||
* @throws \Doctrine\ORM\OptimisticLockException If a version check on an entity that
|
||||
* makes use of optimistic locking fails.
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function flush($entity = null)
|
||||
{
|
||||
@@ -378,12 +381,16 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
{
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
if ($lockMode !== null) {
|
||||
$this->checkLockRequirements($lockMode, $class);
|
||||
}
|
||||
|
||||
if ( ! is_array($id)) {
|
||||
if ($class->isIdentifierComposite) {
|
||||
throw ORMInvalidArgumentException::invalidCompositeIdentifier();
|
||||
}
|
||||
|
||||
$id = array($class->identifier[0] => $id);
|
||||
$id = [$class->identifier[0] => $id];
|
||||
}
|
||||
|
||||
foreach ($id as $i => $value) {
|
||||
@@ -396,7 +403,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
}
|
||||
}
|
||||
|
||||
$sortedId = array();
|
||||
$sortedId = [];
|
||||
|
||||
foreach ($class->identifier as $identifier) {
|
||||
if ( ! isset($id[$identifier])) {
|
||||
@@ -439,24 +446,15 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
|
||||
switch (true) {
|
||||
case LockMode::OPTIMISTIC === $lockMode:
|
||||
if ( ! $class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($class->name);
|
||||
}
|
||||
|
||||
$entity = $persister->load($sortedId);
|
||||
|
||||
$unitOfWork->lock($entity, $lockMode, $lockVersion);
|
||||
|
||||
return $entity;
|
||||
|
||||
case LockMode::NONE === $lockMode:
|
||||
case LockMode::PESSIMISTIC_READ === $lockMode:
|
||||
case LockMode::PESSIMISTIC_WRITE === $lockMode:
|
||||
if ( ! $this->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
|
||||
return $persister->load($sortedId, null, null, array(), $lockMode);
|
||||
return $persister->load($sortedId, null, null, [], $lockMode);
|
||||
|
||||
default:
|
||||
return $persister->loadById($sortedId);
|
||||
@@ -471,10 +469,10 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
$class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
|
||||
|
||||
if ( ! is_array($id)) {
|
||||
$id = array($class->identifier[0] => $id);
|
||||
$id = [$class->identifier[0] => $id];
|
||||
}
|
||||
|
||||
$sortedId = array();
|
||||
$sortedId = [];
|
||||
|
||||
foreach ($class->identifier as $identifier) {
|
||||
if ( ! isset($id[$identifier])) {
|
||||
@@ -482,6 +480,11 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
}
|
||||
|
||||
$sortedId[$identifier] = $id[$identifier];
|
||||
unset($id[$identifier]);
|
||||
}
|
||||
|
||||
if ($id) {
|
||||
throw ORMException::unrecognizedIdentifierFields($class->name, array_keys($id));
|
||||
}
|
||||
|
||||
// Check identity map first, if its already in there just return it.
|
||||
@@ -493,13 +496,9 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
return $this->find($entityName, $sortedId);
|
||||
}
|
||||
|
||||
if ( ! is_array($sortedId)) {
|
||||
$sortedId = array($class->identifier[0] => $sortedId);
|
||||
}
|
||||
|
||||
$entity = $this->proxyFactory->getProxy($class->name, $sortedId);
|
||||
|
||||
$this->unitOfWork->registerManaged($entity, $sortedId, array());
|
||||
$this->unitOfWork->registerManaged($entity, $sortedId, []);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
@@ -517,14 +516,14 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
}
|
||||
|
||||
if ( ! is_array($identifier)) {
|
||||
$identifier = array($class->identifier[0] => $identifier);
|
||||
$identifier = [$class->identifier[0] => $identifier];
|
||||
}
|
||||
|
||||
$entity = $class->newInstance();
|
||||
|
||||
$class->setIdentifierValues($entity, $identifier);
|
||||
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, array());
|
||||
$this->unitOfWork->registerManaged($entity, $identifier, []);
|
||||
$this->unitOfWork->markReadOnly($entity);
|
||||
|
||||
return $entity;
|
||||
@@ -537,10 +536,22 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
* @param string|null $entityName if given, only entities of this type will get detached
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException if a non-null non-string value is given
|
||||
* @throws \Doctrine\Common\Persistence\Mapping\MappingException if a $entityName is given, but that entity is not
|
||||
* found in the mappings
|
||||
*/
|
||||
public function clear($entityName = null)
|
||||
{
|
||||
$this->unitOfWork->clear($entityName);
|
||||
if (null !== $entityName && ! is_string($entityName)) {
|
||||
throw ORMInvalidArgumentException::invalidEntityName($entityName);
|
||||
}
|
||||
|
||||
$this->unitOfWork->clear(
|
||||
null === $entityName
|
||||
? null
|
||||
: $this->metadataFactory->getMetadataFor($entityName)->getName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,11 +578,12 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function persist($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()' , $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()', $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -590,11 +602,12 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function remove($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()' , $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#remove()', $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -611,11 +624,12 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
* @return void
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function refresh($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()' , $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#refresh()', $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -639,7 +653,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
public function detach($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()' , $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#detach()', $entity);
|
||||
}
|
||||
|
||||
$this->unitOfWork->detach($entity);
|
||||
@@ -655,11 +669,12 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
* @return object The managed copy of the entity.
|
||||
*
|
||||
* @throws ORMInvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function merge($entity)
|
||||
{
|
||||
if ( ! is_object($entity)) {
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()' , $entity);
|
||||
throw ORMInvalidArgumentException::invalidObject('EntityManager#merge()', $entity);
|
||||
}
|
||||
|
||||
$this->errorIfClosed();
|
||||
@@ -691,7 +706,7 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
*
|
||||
* @param string $entityName The name of the entity.
|
||||
*
|
||||
* @return \Doctrine\ORM\EntityRepository The repository class.
|
||||
* @return \Doctrine\Common\Persistence\ObjectRepository|\Doctrine\ORM\EntityRepository The repository class.
|
||||
*/
|
||||
public function getRepository($entityName)
|
||||
{
|
||||
@@ -815,39 +830,59 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
/**
|
||||
* Factory method to create EntityManager instances.
|
||||
*
|
||||
* @param mixed $conn An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
*
|
||||
* @return EntityManager The created EntityManager.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public static function create($conn, Configuration $config, EventManager $eventManager = null)
|
||||
public static function create($connection, Configuration $config, EventManager $eventManager = null)
|
||||
{
|
||||
if ( ! $config->getMetadataDriverImpl()) {
|
||||
throw ORMException::missingMappingDriverImpl();
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case (is_array($conn)):
|
||||
$conn = \Doctrine\DBAL\DriverManager::getConnection(
|
||||
$conn, $config, ($eventManager ?: new EventManager())
|
||||
);
|
||||
break;
|
||||
$connection = static::createConnection($connection, $config, $eventManager);
|
||||
|
||||
case ($conn instanceof Connection):
|
||||
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
|
||||
throw ORMException::mismatchedEventManager();
|
||||
}
|
||||
break;
|
||||
return new EntityManager($connection, $config, $connection->getEventManager());
|
||||
}
|
||||
|
||||
default:
|
||||
throw new \InvalidArgumentException("Invalid argument: " . $conn);
|
||||
/**
|
||||
* Factory method to create Connection instances.
|
||||
*
|
||||
* @param array|Connection $connection An array with the connection parameters or an existing Connection instance.
|
||||
* @param Configuration $config The Configuration instance to use.
|
||||
* @param EventManager $eventManager The EventManager instance to use.
|
||||
*
|
||||
* @return Connection
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws ORMException
|
||||
*/
|
||||
protected static function createConnection($connection, Configuration $config, EventManager $eventManager = null)
|
||||
{
|
||||
if (is_array($connection)) {
|
||||
return DriverManager::getConnection($connection, $config, $eventManager ?: new EventManager());
|
||||
}
|
||||
|
||||
return new EntityManager($conn, $config, $conn->getEventManager());
|
||||
if ( ! $connection instanceof Connection) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Invalid $connection argument of type %s given%s.',
|
||||
is_object($connection) ? get_class($connection) : gettype($connection),
|
||||
is_object($connection) ? '' : ': "' . $connection . '"'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($eventManager !== null && $connection->getEventManager() !== $eventManager) {
|
||||
throw ORMException::mismatchedEventManager();
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -877,4 +912,26 @@ use Doctrine\Common\Util\ClassUtils;
|
||||
{
|
||||
return null !== $this->filterCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lockMode
|
||||
* @param ClassMetadata $class
|
||||
* @throws OptimisticLockException
|
||||
* @throws TransactionRequiredException
|
||||
*/
|
||||
private function checkLockRequirements(int $lockMode, ClassMetadata $class): void
|
||||
{
|
||||
switch ($lockMode) {
|
||||
case LockMode::OPTIMISTIC:
|
||||
if (!$class->isVersioned) {
|
||||
throw OptimisticLockException::notVersioned($class->name);
|
||||
}
|
||||
break;
|
||||
case LockMode::PESSIMISTIC_READ:
|
||||
case LockMode::PESSIMISTIC_WRITE:
|
||||
if (!$this->getConnection()->isTransactionActive()) {
|
||||
throw TransactionRequiredException::transactionRequired();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION); HOWEVER CAUSED AND ON ANY
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE); ARISING IN ANY WAY OUT OF THE USE
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
@@ -150,7 +150,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $id The entity identifier.
|
||||
*
|
||||
* @return object The entity reference.
|
||||
* @return object|null The entity reference.
|
||||
*
|
||||
* @throws ORMException
|
||||
*/
|
||||
@@ -174,7 +174,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
* @param string $entityName The name of the entity type.
|
||||
* @param mixed $identifier The entity identifier.
|
||||
*
|
||||
* @return object The (partial) entity reference.
|
||||
* @return object|null The (partial) entity reference.
|
||||
*/
|
||||
public function getPartialReference($entityName, $identifier);
|
||||
|
||||
@@ -249,7 +249,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*/
|
||||
@@ -258,7 +258,7 @@ interface EntityManagerInterface extends ObjectManager
|
||||
/**
|
||||
* Create a new instance for the given hydration mode.
|
||||
*
|
||||
* @param int $hydrationMode
|
||||
* @param string|int $hydrationMode
|
||||
*
|
||||
* @return \Doctrine\ORM\Internal\Hydration\AbstractHydrator
|
||||
*
|
||||
|
||||
@@ -37,7 +37,7 @@ class EntityNotFoundException extends ORMException
|
||||
*/
|
||||
public static function fromClassNameAndIdentifier($className, array $id)
|
||||
{
|
||||
$ids = array();
|
||||
$ids = [];
|
||||
|
||||
foreach ($id as $key => $value) {
|
||||
$ids[] = $key . '(' . $value . ')';
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
namespace Doctrine\ORM;
|
||||
|
||||
use Doctrine\Common\Inflector\Inflector;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\Common\Collections\Selectable;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* An EntityRepository serves as a repository for entities with generic as well as
|
||||
@@ -61,7 +61,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
* @param EntityManager $em The EntityManager to use.
|
||||
* @param Mapping\ClassMetadata $class The class descriptor.
|
||||
*/
|
||||
public function __construct($em, Mapping\ClassMetadata $class)
|
||||
public function __construct(EntityManagerInterface $em, Mapping\ClassMetadata $class)
|
||||
{
|
||||
$this->_entityName = $class->name;
|
||||
$this->_em = $em;
|
||||
@@ -161,7 +161,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
*/
|
||||
public function findAll()
|
||||
{
|
||||
return $this->findBy(array());
|
||||
return $this->findBy([]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +184,7 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
/**
|
||||
* Finds a single entity by a set of criteria.
|
||||
*
|
||||
* @param array $criteria
|
||||
* @param array $criteria
|
||||
* @param array|null $orderBy
|
||||
*
|
||||
* @return object|null The entity instance or NULL if the entity can not be found.
|
||||
@@ -193,68 +193,52 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
{
|
||||
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
|
||||
|
||||
return $persister->load($criteria, null, null, array(), null, 1, $orderBy);
|
||||
return $persister->load($criteria, null, null, [], null, 1, $orderBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds support for magic finders.
|
||||
* Counts entities by a set of criteria.
|
||||
*
|
||||
* @todo Add this method to `ObjectRepository` interface in the next major release
|
||||
*
|
||||
* @param array $criteria
|
||||
*
|
||||
* @return int The cardinality of the objects that match the given criteria.
|
||||
*/
|
||||
public function count(array $criteria)
|
||||
{
|
||||
return $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName)->count($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds support for magic method calls.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return array|object The found entity/entities.
|
||||
* @return mixed The returned value from the resolved method.
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws \BadMethodCallException If the method called is an invalid find* method
|
||||
* or no find* method at all and therefore an invalid
|
||||
* method call.
|
||||
* @throws \BadMethodCallException If the method called is invalid
|
||||
*/
|
||||
public function __call($method, $arguments)
|
||||
{
|
||||
switch (true) {
|
||||
case (0 === strpos($method, 'findBy')):
|
||||
$by = substr($method, 6);
|
||||
$method = 'findBy';
|
||||
break;
|
||||
|
||||
case (0 === strpos($method, 'findOneBy')):
|
||||
$by = substr($method, 9);
|
||||
$method = 'findOneBy';
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \BadMethodCallException(
|
||||
"Undefined method '$method'. The method name must start with ".
|
||||
"either findBy or findOneBy!"
|
||||
);
|
||||
if (0 === strpos($method, 'findBy')) {
|
||||
return $this->resolveMagicCall('findBy', substr($method, 6), $arguments);
|
||||
}
|
||||
|
||||
if (empty($arguments)) {
|
||||
throw ORMException::findByRequiresParameter($method . $by);
|
||||
if (0 === strpos($method, 'findOneBy')) {
|
||||
return $this->resolveMagicCall('findOneBy', substr($method, 9), $arguments);
|
||||
}
|
||||
|
||||
$fieldName = lcfirst(\Doctrine\Common\Util\Inflector::classify($by));
|
||||
|
||||
if ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName)) {
|
||||
switch (count($arguments)) {
|
||||
case 1:
|
||||
return $this->$method(array($fieldName => $arguments[0]));
|
||||
|
||||
case 2:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1]);
|
||||
|
||||
case 3:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2]);
|
||||
|
||||
case 4:
|
||||
return $this->$method(array($fieldName => $arguments[0]), $arguments[1], $arguments[2], $arguments[3]);
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
if (0 === strpos($method, 'countBy')) {
|
||||
return $this->resolveMagicCall('count', substr($method, 7), $arguments);
|
||||
}
|
||||
|
||||
throw ORMException::invalidFindByCall($this->_entityName, $fieldName, $method.$by);
|
||||
throw new \BadMethodCallException(
|
||||
"Undefined method '$method'. The method name must start with ".
|
||||
"either findBy, findOneBy or countBy!"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,4 +287,30 @@ class EntityRepository implements ObjectRepository, Selectable
|
||||
|
||||
return new LazyCriteriaCollection($persister, $criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a magic method call to the proper existent method at `EntityRepository`.
|
||||
*
|
||||
* @param string $method The method to call
|
||||
* @param string $by The property name used as condition
|
||||
* @param array $arguments The arguments to pass at method call
|
||||
*
|
||||
* @throws ORMException If the method called is invalid or the requested field/association does not exist
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveMagicCall($method, $by, array $arguments)
|
||||
{
|
||||
if (! $arguments) {
|
||||
throw ORMException::findByRequiresParameter($method . $by);
|
||||
}
|
||||
|
||||
$fieldName = lcfirst(Inflector::classify($by));
|
||||
|
||||
if (! ($this->_class->hasField($fieldName) || $this->_class->hasAssociation($fieldName))) {
|
||||
throw ORMException::invalidMagicCall($this->_entityName, $fieldName, $method . $by);
|
||||
}
|
||||
|
||||
return $this->$method([$fieldName => $arguments[0]], ...array_slice($arguments, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
*/
|
||||
|
||||
namespace Doctrine\ORM\Event;
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ class ListenersInvoker
|
||||
/**
|
||||
* Get the subscribed event systems
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
*
|
||||
* @return integer Bitmask of subscribed event systems.
|
||||
*/
|
||||
@@ -89,21 +89,21 @@ class ListenersInvoker
|
||||
/**
|
||||
* Dispatches the lifecycle event of the given entity.
|
||||
*
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
* @param object $entity The Entity on which the event occurred.
|
||||
* @param \Doctrine\Common\EventArgs $event The Event args.
|
||||
* @param integer $invoke Bitmask to invoke listeners.
|
||||
* @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
|
||||
* @param string $eventName The entity lifecycle event.
|
||||
* @param object $entity The Entity on which the event occurred.
|
||||
* @param \Doctrine\Common\EventArgs $event The Event args.
|
||||
* @param integer $invoke Bitmask to invoke listeners.
|
||||
*/
|
||||
public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
|
||||
{
|
||||
if($invoke & self::INVOKE_CALLBACKS) {
|
||||
if ($invoke & self::INVOKE_CALLBACKS) {
|
||||
foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) {
|
||||
$entity->$callback($event);
|
||||
}
|
||||
}
|
||||
|
||||
if($invoke & self::INVOKE_LISTENERS) {
|
||||
if ($invoke & self::INVOKE_LISTENERS) {
|
||||
foreach ($metadata->entityListeners[$eventName] as $listener) {
|
||||
$class = $listener['class'];
|
||||
$method = $listener['method'];
|
||||
@@ -113,8 +113,8 @@ class ListenersInvoker
|
||||
}
|
||||
}
|
||||
|
||||
if($invoke & self::INVOKE_MANAGER) {
|
||||
if ($invoke & self::INVOKE_MANAGER) {
|
||||
$this->eventManager->dispatchEvent($eventName, $event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ use Doctrine\Common\Persistence\Event\LoadClassMetadataEventArgs as BaseLoadClas
|
||||
* Note: method annotations are used instead of method overrides (due to BC policy)
|
||||
*
|
||||
* @method __construct(\Doctrine\ORM\Mapping\ClassMetadata $classMetadata, \Doctrine\ORM\EntityManager $objectManager)
|
||||
* @method \Doctrine\ORM\EntityManager getClassMetadata()
|
||||
* @method \Doctrine\ORM\Mapping\ClassMetadata getClassMetadata()
|
||||
*/
|
||||
class LoadClassMetadataEventArgs extends BaseLoadClassMetadataEventArgs
|
||||
{
|
||||
|
||||
@@ -26,8 +26,8 @@ abstract class AbstractIdGenerator
|
||||
/**
|
||||
* Generates an identifier for an entity.
|
||||
*
|
||||
* @param EntityManager|EntityManager $em
|
||||
* @param \Doctrine\ORM\Mapping\Entity $entity
|
||||
* @param EntityManager $em
|
||||
* @param object|null $entity
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function generate(EntityManager $em, $entity);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user